assemblyai 4.5.0-beta.0 → 4.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/CHANGELOG.md +34 -0
- package/README.md +53 -9
- package/dist/assemblyai.streaming.umd.js +288 -0
- package/dist/assemblyai.streaming.umd.min.js +1 -0
- package/dist/assemblyai.umd.js +131 -12
- package/dist/assemblyai.umd.min.js +1 -1
- package/dist/browser.mjs +125 -11
- package/dist/bun.mjs +125 -11
- package/dist/deno.mjs +125 -11
- package/dist/exports/index.d.ts +2 -0
- package/dist/exports/streaming.d.ts +4 -0
- package/dist/index.cjs +131 -12
- package/dist/index.mjs +131 -12
- package/dist/node.cjs +125 -11
- package/dist/node.mjs +125 -11
- package/dist/polyfills/fetch/default.d.ts +1 -0
- package/dist/polyfills/fetch/workerd.d.ts +1 -0
- package/dist/services/base.d.ts +1 -0
- package/dist/services/lemur/index.d.ts +8 -1
- package/dist/services/transcripts/index.d.ts +16 -3
- package/dist/streaming.browser.mjs +239 -0
- package/dist/streaming.cjs +277 -0
- package/dist/streaming.mjs +274 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/openapi.generated.d.ts +60 -23
- package/dist/types/services/index.d.ts +7 -0
- package/dist/types/transcripts/index.d.ts +9 -0
- package/dist/utils/userAgent.d.ts +2 -0
- package/dist/workerd.mjs +722 -0
- package/docs/reference-types-from-js.md +27 -0
- package/package.json +39 -19
- package/src/exports/index.ts +2 -0
- package/src/exports/streaming.ts +4 -0
- package/src/polyfills/fetch/default.ts +3 -0
- package/src/polyfills/fetch/workerd.ts +1 -0
- package/src/services/base.ts +27 -7
- package/src/services/files/index.ts +19 -2
- package/src/services/index.ts +2 -1
- package/src/services/lemur/index.ts +12 -0
- package/src/services/transcripts/index.ts +41 -4
- package/src/types/index.ts +9 -0
- package/src/types/openapi.generated.ts +198 -41
- package/src/types/services/index.ts +8 -0
- package/src/types/transcripts/index.ts +10 -0
- package/src/utils/path.ts +1 -0
- package/src/utils/userAgent.ts +51 -0
package/dist/node.mjs
CHANGED
|
@@ -3,6 +3,45 @@ import ws from 'ws';
|
|
|
3
3
|
import { createReadStream } from 'fs';
|
|
4
4
|
import { Readable } from 'stream';
|
|
5
5
|
|
|
6
|
+
const DEFAULT_FETCH_INIT = {
|
|
7
|
+
cache: "no-store",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const buildUserAgent = (userAgent) => defaultUserAgentString +
|
|
11
|
+
(userAgent === false
|
|
12
|
+
? ""
|
|
13
|
+
: " AssemblyAI/1.0 (" +
|
|
14
|
+
Object.entries({ ...defaultUserAgent, ...userAgent })
|
|
15
|
+
.map(([key, item]) => item ? `${key}=${item.name}/${item.version}` : "")
|
|
16
|
+
.join(" ") +
|
|
17
|
+
")");
|
|
18
|
+
let defaultUserAgentString = "";
|
|
19
|
+
if (typeof navigator !== "undefined" && navigator.userAgent) {
|
|
20
|
+
defaultUserAgentString += navigator.userAgent;
|
|
21
|
+
}
|
|
22
|
+
const defaultUserAgent = {
|
|
23
|
+
sdk: { name: "JavaScript", version: "4.5.0" },
|
|
24
|
+
};
|
|
25
|
+
if (typeof process !== "undefined") {
|
|
26
|
+
if (process.versions.node && defaultUserAgentString.indexOf("Node") === -1) {
|
|
27
|
+
defaultUserAgent.runtime_env = {
|
|
28
|
+
name: "Node",
|
|
29
|
+
version: process.versions.node,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (process.versions.bun && defaultUserAgentString.indexOf("Bun") === -1) {
|
|
33
|
+
defaultUserAgent.runtime_env = {
|
|
34
|
+
name: "Bun",
|
|
35
|
+
version: process.versions.bun,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (typeof Deno !== "undefined") {
|
|
40
|
+
if (process.versions.bun && defaultUserAgentString.indexOf("Deno") === -1) {
|
|
41
|
+
defaultUserAgent.runtime_env = { name: "Deno", version: Deno.version.deno };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
6
45
|
/**
|
|
7
46
|
* Base class for services that communicate with the API.
|
|
8
47
|
*/
|
|
@@ -13,15 +52,32 @@ class BaseService {
|
|
|
13
52
|
*/
|
|
14
53
|
constructor(params) {
|
|
15
54
|
this.params = params;
|
|
55
|
+
if (params.userAgent === false) {
|
|
56
|
+
this.userAgent = undefined;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
this.userAgent = buildUserAgent(params.userAgent || {});
|
|
60
|
+
}
|
|
16
61
|
}
|
|
17
62
|
async fetch(input, init) {
|
|
18
|
-
init = init
|
|
19
|
-
|
|
20
|
-
init.headers = {
|
|
63
|
+
init = { ...DEFAULT_FETCH_INIT, ...init };
|
|
64
|
+
let headers = {
|
|
21
65
|
Authorization: this.params.apiKey,
|
|
22
66
|
"Content-Type": "application/json",
|
|
23
|
-
...init.headers,
|
|
24
67
|
};
|
|
68
|
+
if (DEFAULT_FETCH_INIT?.headers)
|
|
69
|
+
headers = { ...headers, ...DEFAULT_FETCH_INIT.headers };
|
|
70
|
+
if (init?.headers)
|
|
71
|
+
headers = { ...headers, ...init.headers };
|
|
72
|
+
if (this.userAgent) {
|
|
73
|
+
headers["User-Agent"] = this.userAgent;
|
|
74
|
+
// chromium browsers have a bug where the user agent can't be modified
|
|
75
|
+
if (typeof window !== "undefined" && "chrome" in window) {
|
|
76
|
+
headers["AssemblyAI-Agent"] =
|
|
77
|
+
this.userAgent;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
init.headers = headers;
|
|
25
81
|
if (!input.startsWith("http"))
|
|
26
82
|
input = this.params.baseUrl + input;
|
|
27
83
|
const response = await fetch(input, init);
|
|
@@ -74,6 +130,9 @@ class LemurService extends BaseService {
|
|
|
74
130
|
body: JSON.stringify(params),
|
|
75
131
|
});
|
|
76
132
|
}
|
|
133
|
+
getResponse(id) {
|
|
134
|
+
return this.fetchJson(`/lemur/v3/${id}`);
|
|
135
|
+
}
|
|
77
136
|
/**
|
|
78
137
|
* Delete the data for a previously submitted LeMUR request.
|
|
79
138
|
* @param id - ID of the LeMUR request
|
|
@@ -348,6 +407,8 @@ function getPath(path) {
|
|
|
348
407
|
return null;
|
|
349
408
|
if (path.startsWith("https"))
|
|
350
409
|
return null;
|
|
410
|
+
if (path.startsWith("data:"))
|
|
411
|
+
return null;
|
|
351
412
|
if (path.startsWith("file://"))
|
|
352
413
|
return path.substring(7);
|
|
353
414
|
if (path.startsWith("file:"))
|
|
@@ -389,8 +450,13 @@ class TranscriptService extends BaseService {
|
|
|
389
450
|
audioUrl = await this.files.upload(path);
|
|
390
451
|
}
|
|
391
452
|
else {
|
|
392
|
-
|
|
393
|
-
|
|
453
|
+
if (audio.startsWith("data:")) {
|
|
454
|
+
audioUrl = await this.files.upload(audio);
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
// audio is not a local path, and not a data-URI, assume it's a normal URL
|
|
458
|
+
audioUrl = audio;
|
|
459
|
+
}
|
|
394
460
|
}
|
|
395
461
|
}
|
|
396
462
|
else {
|
|
@@ -541,13 +607,43 @@ class TranscriptService extends BaseService {
|
|
|
541
607
|
return await response.text();
|
|
542
608
|
}
|
|
543
609
|
/**
|
|
544
|
-
* Retrieve
|
|
610
|
+
* Retrieve the redacted audio URL of a transcript.
|
|
545
611
|
* @param id - The identifier of the transcript.
|
|
546
|
-
* @returns A promise that resolves to the
|
|
612
|
+
* @returns A promise that resolves to the details of the redacted audio.
|
|
613
|
+
* @deprecated Use `redactedAudio` instead.
|
|
547
614
|
*/
|
|
548
615
|
redactions(id) {
|
|
616
|
+
return this.redactedAudio(id);
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Retrieve the redacted audio URL of a transcript.
|
|
620
|
+
* @param id - The identifier of the transcript.
|
|
621
|
+
* @returns A promise that resolves to the details of the redacted audio.
|
|
622
|
+
*/
|
|
623
|
+
redactedAudio(id) {
|
|
549
624
|
return this.fetchJson(`/v2/transcript/${id}/redacted-audio`);
|
|
550
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* Retrieve the redacted audio file of a transcript.
|
|
628
|
+
* @param id - The identifier of the transcript.
|
|
629
|
+
* @returns A promise that resolves to the fetch HTTP response of the redacted audio file.
|
|
630
|
+
*/
|
|
631
|
+
async redactedAudioFile(id) {
|
|
632
|
+
const { redacted_audio_url, status } = await this.redactedAudio(id);
|
|
633
|
+
if (status !== "redacted_audio_ready") {
|
|
634
|
+
throw new Error(`Redacted audio status is ${status}`);
|
|
635
|
+
}
|
|
636
|
+
const response = await fetch(redacted_audio_url);
|
|
637
|
+
if (!response.ok) {
|
|
638
|
+
throw new Error(`Failed to fetch redacted audio: ${response.statusText}`);
|
|
639
|
+
}
|
|
640
|
+
return {
|
|
641
|
+
arrayBuffer: response.arrayBuffer.bind(response),
|
|
642
|
+
blob: response.blob.bind(response),
|
|
643
|
+
body: response.body,
|
|
644
|
+
bodyUsed: response.bodyUsed,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
551
647
|
}
|
|
552
648
|
function deprecateConformer2(params) {
|
|
553
649
|
if (!params)
|
|
@@ -567,8 +663,14 @@ class FileService extends BaseService {
|
|
|
567
663
|
*/
|
|
568
664
|
async upload(input) {
|
|
569
665
|
let fileData;
|
|
570
|
-
if (typeof input === "string")
|
|
571
|
-
|
|
666
|
+
if (typeof input === "string") {
|
|
667
|
+
if (input.startsWith("data:")) {
|
|
668
|
+
fileData = dataUrlToBlob(input);
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
fileData = await readFile(input);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
572
674
|
else
|
|
573
675
|
fileData = input;
|
|
574
676
|
const data = await this.fetchJson("/v2/upload", {
|
|
@@ -582,6 +684,17 @@ class FileService extends BaseService {
|
|
|
582
684
|
return data.upload_url;
|
|
583
685
|
}
|
|
584
686
|
}
|
|
687
|
+
function dataUrlToBlob(dataUrl) {
|
|
688
|
+
const arr = dataUrl.split(",");
|
|
689
|
+
const mime = arr[0].match(/:(.*?);/)[1];
|
|
690
|
+
const bstr = atob(arr[1]);
|
|
691
|
+
let n = bstr.length;
|
|
692
|
+
const u8arr = new Uint8Array(n);
|
|
693
|
+
while (n--) {
|
|
694
|
+
u8arr[n] = bstr.charCodeAt(n);
|
|
695
|
+
}
|
|
696
|
+
return new Blob([u8arr], { type: mime });
|
|
697
|
+
}
|
|
585
698
|
|
|
586
699
|
const defaultBaseUrl = "https://api.assemblyai.com";
|
|
587
700
|
class AssemblyAI {
|
|
@@ -591,8 +704,9 @@ class AssemblyAI {
|
|
|
591
704
|
*/
|
|
592
705
|
constructor(params) {
|
|
593
706
|
params.baseUrl = params.baseUrl || defaultBaseUrl;
|
|
594
|
-
if (params.baseUrl && params.baseUrl.endsWith("/"))
|
|
707
|
+
if (params.baseUrl && params.baseUrl.endsWith("/")) {
|
|
595
708
|
params.baseUrl = params.baseUrl.slice(0, -1);
|
|
709
|
+
}
|
|
596
710
|
this.files = new FileService(params);
|
|
597
711
|
this.transcripts = new TranscriptService(params, this.files);
|
|
598
712
|
this.lemur = new LemurService(params);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const DEFAULT_FETCH_INIT: Record<string, unknown>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const DEFAULT_FETCH_INIT: Record<string, unknown>;
|
package/dist/services/base.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import { LemurSummaryParams, LemurActionItemsParams, LemurQuestionAnswerParams, LemurTaskParams, LemurSummaryResponse, LemurQuestionAnswerResponse, LemurActionItemsResponse, LemurTaskResponse, PurgeLemurRequestDataResponse } from "../..";
|
|
1
|
+
import { LemurSummaryParams, LemurActionItemsParams, LemurQuestionAnswerParams, LemurTaskParams, LemurSummaryResponse, LemurQuestionAnswerResponse, LemurActionItemsResponse, LemurTaskResponse, PurgeLemurRequestDataResponse, LemurResponse } from "../..";
|
|
2
2
|
import { BaseService } from "../base";
|
|
3
3
|
export declare class LemurService extends BaseService {
|
|
4
4
|
summary(params: LemurSummaryParams): Promise<LemurSummaryResponse>;
|
|
5
5
|
questionAnswer(params: LemurQuestionAnswerParams): Promise<LemurQuestionAnswerResponse>;
|
|
6
6
|
actionItems(params: LemurActionItemsParams): Promise<LemurActionItemsResponse>;
|
|
7
7
|
task(params: LemurTaskParams): Promise<LemurTaskResponse>;
|
|
8
|
+
/**
|
|
9
|
+
* Retrieve a LeMUR response that was previously generated.
|
|
10
|
+
* @param id - The ID of the LeMUR request you previously made. This would be found in the response of the original request.
|
|
11
|
+
* @returns The LeMUR response.
|
|
12
|
+
*/
|
|
13
|
+
getResponse<T extends LemurResponse>(id: string): Promise<T>;
|
|
14
|
+
getResponse(id: string): Promise<LemurResponse>;
|
|
8
15
|
/**
|
|
9
16
|
* Delete the data for a previously submitted LeMUR request.
|
|
10
17
|
* @param id - ID of the LeMUR request
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BaseService } from "../base";
|
|
2
|
-
import { ParagraphsResponse, SentencesResponse, Transcript, TranscriptList, TranscriptParams, CreateTranscriptOptions, SubtitleFormat, RedactedAudioResponse, ListTranscriptParams, WordSearchResponse, BaseServiceParams, PollingOptions, TranscribeParams, TranscribeOptions, SubmitParams } from "../..";
|
|
2
|
+
import { ParagraphsResponse, SentencesResponse, Transcript, TranscriptList, TranscriptParams, CreateTranscriptOptions, SubtitleFormat, RedactedAudioResponse, ListTranscriptParams, WordSearchResponse, BaseServiceParams, PollingOptions, TranscribeParams, TranscribeOptions, SubmitParams, RedactedAudioFile } from "../..";
|
|
3
3
|
import { FileService } from "../files";
|
|
4
4
|
export declare class TranscriptService extends BaseService {
|
|
5
5
|
private files;
|
|
@@ -78,9 +78,22 @@ export declare class TranscriptService extends BaseService {
|
|
|
78
78
|
*/
|
|
79
79
|
subtitles(id: string, format?: SubtitleFormat, chars_per_caption?: number): Promise<string>;
|
|
80
80
|
/**
|
|
81
|
-
* Retrieve
|
|
81
|
+
* Retrieve the redacted audio URL of a transcript.
|
|
82
82
|
* @param id - The identifier of the transcript.
|
|
83
|
-
* @returns A promise that resolves to the
|
|
83
|
+
* @returns A promise that resolves to the details of the redacted audio.
|
|
84
|
+
* @deprecated Use `redactedAudio` instead.
|
|
84
85
|
*/
|
|
85
86
|
redactions(id: string): Promise<RedactedAudioResponse>;
|
|
87
|
+
/**
|
|
88
|
+
* Retrieve the redacted audio URL of a transcript.
|
|
89
|
+
* @param id - The identifier of the transcript.
|
|
90
|
+
* @returns A promise that resolves to the details of the redacted audio.
|
|
91
|
+
*/
|
|
92
|
+
redactedAudio(id: string): Promise<RedactedAudioResponse>;
|
|
93
|
+
/**
|
|
94
|
+
* Retrieve the redacted audio file of a transcript.
|
|
95
|
+
* @param id - The identifier of the transcript.
|
|
96
|
+
* @returns A promise that resolves to the fetch HTTP response of the redacted audio file.
|
|
97
|
+
*/
|
|
98
|
+
redactedAudioFile(id: string): Promise<RedactedAudioFile>;
|
|
86
99
|
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
const { WritableStream } = typeof window !== "undefined"
|
|
2
|
+
? window
|
|
3
|
+
: typeof global !== "undefined"
|
|
4
|
+
? global
|
|
5
|
+
: globalThis;
|
|
6
|
+
|
|
7
|
+
const PolyfillWebSocket = WebSocket ?? global?.WebSocket ?? window?.WebSocket ?? self?.WebSocket;
|
|
8
|
+
const factory = (url, params) => {
|
|
9
|
+
if (params) {
|
|
10
|
+
return new PolyfillWebSocket(url, params);
|
|
11
|
+
}
|
|
12
|
+
return new PolyfillWebSocket(url);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
var RealtimeErrorType;
|
|
16
|
+
(function (RealtimeErrorType) {
|
|
17
|
+
RealtimeErrorType[RealtimeErrorType["BadSampleRate"] = 4000] = "BadSampleRate";
|
|
18
|
+
RealtimeErrorType[RealtimeErrorType["AuthFailed"] = 4001] = "AuthFailed";
|
|
19
|
+
// Both InsufficientFunds and FreeAccount error use 4002
|
|
20
|
+
RealtimeErrorType[RealtimeErrorType["InsufficientFundsOrFreeAccount"] = 4002] = "InsufficientFundsOrFreeAccount";
|
|
21
|
+
RealtimeErrorType[RealtimeErrorType["NonexistentSessionId"] = 4004] = "NonexistentSessionId";
|
|
22
|
+
RealtimeErrorType[RealtimeErrorType["SessionExpired"] = 4008] = "SessionExpired";
|
|
23
|
+
RealtimeErrorType[RealtimeErrorType["ClosedSession"] = 4010] = "ClosedSession";
|
|
24
|
+
RealtimeErrorType[RealtimeErrorType["RateLimited"] = 4029] = "RateLimited";
|
|
25
|
+
RealtimeErrorType[RealtimeErrorType["UniqueSessionViolation"] = 4030] = "UniqueSessionViolation";
|
|
26
|
+
RealtimeErrorType[RealtimeErrorType["SessionTimeout"] = 4031] = "SessionTimeout";
|
|
27
|
+
RealtimeErrorType[RealtimeErrorType["AudioTooShort"] = 4032] = "AudioTooShort";
|
|
28
|
+
RealtimeErrorType[RealtimeErrorType["AudioTooLong"] = 4033] = "AudioTooLong";
|
|
29
|
+
RealtimeErrorType[RealtimeErrorType["BadJson"] = 4100] = "BadJson";
|
|
30
|
+
RealtimeErrorType[RealtimeErrorType["BadSchema"] = 4101] = "BadSchema";
|
|
31
|
+
RealtimeErrorType[RealtimeErrorType["TooManyStreams"] = 4102] = "TooManyStreams";
|
|
32
|
+
RealtimeErrorType[RealtimeErrorType["Reconnected"] = 4103] = "Reconnected";
|
|
33
|
+
RealtimeErrorType[RealtimeErrorType["ReconnectAttemptsExhausted"] = 1013] = "ReconnectAttemptsExhausted";
|
|
34
|
+
})(RealtimeErrorType || (RealtimeErrorType = {}));
|
|
35
|
+
const RealtimeErrorMessages = {
|
|
36
|
+
[RealtimeErrorType.BadSampleRate]: "Sample rate must be a positive integer",
|
|
37
|
+
[RealtimeErrorType.AuthFailed]: "Not Authorized",
|
|
38
|
+
[RealtimeErrorType.InsufficientFundsOrFreeAccount]: "Insufficient funds or you are using a free account. This feature is paid-only and requires you to add a credit card. Please visit https://assemblyai.com/dashboard/ to add a credit card to your account.",
|
|
39
|
+
[RealtimeErrorType.NonexistentSessionId]: "Session ID does not exist",
|
|
40
|
+
[RealtimeErrorType.SessionExpired]: "Session has expired",
|
|
41
|
+
[RealtimeErrorType.ClosedSession]: "Session is closed",
|
|
42
|
+
[RealtimeErrorType.RateLimited]: "Rate limited",
|
|
43
|
+
[RealtimeErrorType.UniqueSessionViolation]: "Unique session violation",
|
|
44
|
+
[RealtimeErrorType.SessionTimeout]: "Session Timeout",
|
|
45
|
+
[RealtimeErrorType.AudioTooShort]: "Audio too short",
|
|
46
|
+
[RealtimeErrorType.AudioTooLong]: "Audio too long",
|
|
47
|
+
[RealtimeErrorType.BadJson]: "Bad JSON",
|
|
48
|
+
[RealtimeErrorType.BadSchema]: "Bad schema",
|
|
49
|
+
[RealtimeErrorType.TooManyStreams]: "Too many streams",
|
|
50
|
+
[RealtimeErrorType.Reconnected]: "Reconnected",
|
|
51
|
+
[RealtimeErrorType.ReconnectAttemptsExhausted]: "Reconnect attempts exhausted",
|
|
52
|
+
};
|
|
53
|
+
class RealtimeError extends Error {
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const defaultRealtimeUrl = "wss://api.assemblyai.com/v2/realtime/ws";
|
|
57
|
+
const forceEndOfUtteranceMessage = `{"force_end_utterance":true}`;
|
|
58
|
+
const terminateSessionMessage = `{"terminate_session":true}`;
|
|
59
|
+
class RealtimeTranscriber {
|
|
60
|
+
constructor(params) {
|
|
61
|
+
this.listeners = {};
|
|
62
|
+
this.realtimeUrl = params.realtimeUrl ?? defaultRealtimeUrl;
|
|
63
|
+
this.sampleRate = params.sampleRate ?? 16_000;
|
|
64
|
+
this.wordBoost = params.wordBoost;
|
|
65
|
+
this.encoding = params.encoding;
|
|
66
|
+
this.endUtteranceSilenceThreshold = params.endUtteranceSilenceThreshold;
|
|
67
|
+
this.disablePartialTranscripts = params.disablePartialTranscripts;
|
|
68
|
+
if ("token" in params && params.token)
|
|
69
|
+
this.token = params.token;
|
|
70
|
+
if ("apiKey" in params && params.apiKey)
|
|
71
|
+
this.apiKey = params.apiKey;
|
|
72
|
+
if (!(this.token || this.apiKey)) {
|
|
73
|
+
throw new Error("API key or temporary token is required.");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
connectionUrl() {
|
|
77
|
+
const url = new URL(this.realtimeUrl);
|
|
78
|
+
if (url.protocol !== "wss:") {
|
|
79
|
+
throw new Error("Invalid protocol, must be wss");
|
|
80
|
+
}
|
|
81
|
+
const searchParams = new URLSearchParams();
|
|
82
|
+
if (this.token) {
|
|
83
|
+
searchParams.set("token", this.token);
|
|
84
|
+
}
|
|
85
|
+
searchParams.set("sample_rate", this.sampleRate.toString());
|
|
86
|
+
if (this.wordBoost && this.wordBoost.length > 0) {
|
|
87
|
+
searchParams.set("word_boost", JSON.stringify(this.wordBoost));
|
|
88
|
+
}
|
|
89
|
+
if (this.encoding) {
|
|
90
|
+
searchParams.set("encoding", this.encoding);
|
|
91
|
+
}
|
|
92
|
+
searchParams.set("enable_extra_session_information", "true");
|
|
93
|
+
if (this.disablePartialTranscripts) {
|
|
94
|
+
searchParams.set("disable_partial_transcripts", this.disablePartialTranscripts.toString());
|
|
95
|
+
}
|
|
96
|
+
url.search = searchParams.toString();
|
|
97
|
+
return url;
|
|
98
|
+
}
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
100
|
+
on(event, listener) {
|
|
101
|
+
this.listeners[event] = listener;
|
|
102
|
+
}
|
|
103
|
+
connect() {
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
if (this.socket) {
|
|
106
|
+
throw new Error("Already connected");
|
|
107
|
+
}
|
|
108
|
+
const url = this.connectionUrl();
|
|
109
|
+
if (this.token) {
|
|
110
|
+
this.socket = factory(url.toString());
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.socket = factory(url.toString(), {
|
|
114
|
+
headers: { Authorization: this.apiKey },
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
this.socket.binaryType = "arraybuffer";
|
|
118
|
+
this.socket.onopen = () => {
|
|
119
|
+
if (this.endUtteranceSilenceThreshold === undefined ||
|
|
120
|
+
this.endUtteranceSilenceThreshold === null) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this.configureEndUtteranceSilenceThreshold(this.endUtteranceSilenceThreshold);
|
|
124
|
+
};
|
|
125
|
+
this.socket.onclose = ({ code, reason }) => {
|
|
126
|
+
if (!reason) {
|
|
127
|
+
if (code in RealtimeErrorType) {
|
|
128
|
+
reason = RealtimeErrorMessages[code];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
this.listeners.close?.(code, reason);
|
|
132
|
+
};
|
|
133
|
+
this.socket.onerror = (event) => {
|
|
134
|
+
if (event.error)
|
|
135
|
+
this.listeners.error?.(event.error);
|
|
136
|
+
else
|
|
137
|
+
this.listeners.error?.(new Error(event.message));
|
|
138
|
+
};
|
|
139
|
+
this.socket.onmessage = ({ data }) => {
|
|
140
|
+
const message = JSON.parse(data.toString());
|
|
141
|
+
if ("error" in message) {
|
|
142
|
+
this.listeners.error?.(new RealtimeError(message.error));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
switch (message.message_type) {
|
|
146
|
+
case "SessionBegins": {
|
|
147
|
+
const openObject = {
|
|
148
|
+
sessionId: message.session_id,
|
|
149
|
+
expiresAt: new Date(message.expires_at),
|
|
150
|
+
};
|
|
151
|
+
resolve(openObject);
|
|
152
|
+
this.listeners.open?.(openObject);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case "PartialTranscript": {
|
|
156
|
+
// message.created is actually a string when coming from the socket
|
|
157
|
+
message.created = new Date(message.created);
|
|
158
|
+
this.listeners.transcript?.(message);
|
|
159
|
+
this.listeners["transcript.partial"]?.(message);
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case "FinalTranscript": {
|
|
163
|
+
// message.created is actually a string when coming from the socket
|
|
164
|
+
message.created = new Date(message.created);
|
|
165
|
+
this.listeners.transcript?.(message);
|
|
166
|
+
this.listeners["transcript.final"]?.(message);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case "SessionInformation": {
|
|
170
|
+
this.listeners.session_information?.(message);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case "SessionTerminated": {
|
|
174
|
+
this.sessionTerminatedResolve?.();
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
sendAudio(audio) {
|
|
182
|
+
this.send(audio);
|
|
183
|
+
}
|
|
184
|
+
stream() {
|
|
185
|
+
return new WritableStream({
|
|
186
|
+
write: (chunk) => {
|
|
187
|
+
this.sendAudio(chunk);
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Manually end an utterance
|
|
193
|
+
*/
|
|
194
|
+
forceEndUtterance() {
|
|
195
|
+
this.send(forceEndOfUtteranceMessage);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Configure the threshold for how long to wait before ending an utterance. Default is 700ms.
|
|
199
|
+
* @param threshold - The duration of the end utterance silence threshold in milliseconds.
|
|
200
|
+
* This value must be an integer between 0 and 20_000.
|
|
201
|
+
*/
|
|
202
|
+
configureEndUtteranceSilenceThreshold(threshold) {
|
|
203
|
+
this.send(`{"end_utterance_silence_threshold":${threshold}}`);
|
|
204
|
+
}
|
|
205
|
+
send(data) {
|
|
206
|
+
if (!this.socket || this.socket.readyState !== this.socket.OPEN) {
|
|
207
|
+
throw new Error("Socket is not open for communication");
|
|
208
|
+
}
|
|
209
|
+
this.socket.send(data);
|
|
210
|
+
}
|
|
211
|
+
async close(waitForSessionTermination = true) {
|
|
212
|
+
if (this.socket) {
|
|
213
|
+
if (this.socket.readyState === this.socket.OPEN) {
|
|
214
|
+
if (waitForSessionTermination) {
|
|
215
|
+
const sessionTerminatedPromise = new Promise((resolve) => {
|
|
216
|
+
this.sessionTerminatedResolve = resolve;
|
|
217
|
+
});
|
|
218
|
+
this.socket.send(terminateSessionMessage);
|
|
219
|
+
await sessionTerminatedPromise;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
this.socket.send(terminateSessionMessage);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (this.socket?.removeAllListeners)
|
|
226
|
+
this.socket.removeAllListeners();
|
|
227
|
+
this.socket.close();
|
|
228
|
+
}
|
|
229
|
+
this.listeners = {};
|
|
230
|
+
this.socket = undefined;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* @deprecated Use RealtimeTranscriber instead
|
|
235
|
+
*/
|
|
236
|
+
class RealtimeService extends RealtimeTranscriber {
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export { RealtimeService, RealtimeTranscriber };
|