@vocalstack/js-sdk 1.1.0 → 2.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +112 -65
- package/SKILL.md +249 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.js +56 -0
- package/dist/file.d.ts +2 -0
- package/dist/file.js +38 -0
- package/dist/http.d.ts +43 -0
- package/dist/http.js +124 -0
- package/dist/index.d.ts +42 -2
- package/dist/index.js +51 -17
- package/dist/mic.d.ts +27 -0
- package/dist/mic.js +236 -0
- package/dist/realtime.d.ts +77 -0
- package/dist/realtime.js +490 -0
- package/dist/resources.d.ts +374 -0
- package/dist/resources.js +712 -0
- package/dist/subscription.d.ts +38 -0
- package/dist/subscription.js +153 -0
- package/dist/ws.d.ts +9 -0
- package/dist/ws.js +12 -0
- package/package.json +32 -36
- package/skills/vocalstack/SKILL.md +249 -0
- package/dist/ApiVersion.d.ts +0 -2
- package/dist/ApiVersion.js +0 -32
- package/dist/RestApi.d.ts +0 -21
- package/dist/RestApi.js +0 -95
- package/dist/VocalStackApi.d.ts +0 -19
- package/dist/VocalStackApi.js +0 -60
- package/dist/WebsocketApi.d.ts +0 -38
- package/dist/WebsocketApi.js +0 -150
- package/dist/apis/Billing.d.ts +0 -59
- package/dist/apis/Billing.js +0 -21
- package/dist/apis/LiveTranscription.d.ts +0 -58
- package/dist/apis/LiveTranscription.js +0 -55
- package/dist/apis/Polyglot.d.ts +0 -113
- package/dist/apis/Polyglot.js +0 -68
- package/dist/apis/Security.d.ts +0 -25
- package/dist/apis/Security.js +0 -35
- package/dist/apis/Transcriptions.d.ts +0 -60
- package/dist/apis/Transcriptions.js +0 -38
- package/dist/apis/UrlTranscription.d.ts +0 -59
- package/dist/apis/UrlTranscription.js +0 -53
- package/dist/apis/index.d.ts +0 -6
- package/dist/apis/index.js +0 -22
- package/dist/esm/ApiVersion.d.ts +0 -2
- package/dist/esm/ApiVersion.js +0 -28
- package/dist/esm/RestApi.d.ts +0 -21
- package/dist/esm/RestApi.js +0 -91
- package/dist/esm/VocalStackApi.d.ts +0 -19
- package/dist/esm/VocalStackApi.js +0 -56
- package/dist/esm/WebsocketApi.d.ts +0 -38
- package/dist/esm/WebsocketApi.js +0 -146
- package/dist/esm/apis/Billing.d.ts +0 -59
- package/dist/esm/apis/Billing.js +0 -17
- package/dist/esm/apis/LiveTranscription.d.ts +0 -58
- package/dist/esm/apis/LiveTranscription.js +0 -51
- package/dist/esm/apis/Polyglot.d.ts +0 -113
- package/dist/esm/apis/Polyglot.js +0 -64
- package/dist/esm/apis/Security.d.ts +0 -25
- package/dist/esm/apis/Security.js +0 -31
- package/dist/esm/apis/Transcriptions.d.ts +0 -60
- package/dist/esm/apis/Transcriptions.js +0 -34
- package/dist/esm/apis/UrlTranscription.d.ts +0 -59
- package/dist/esm/apis/UrlTranscription.js +0 -49
- package/dist/esm/apis/index.d.ts +0 -6
- package/dist/esm/apis/index.js +0 -6
- package/dist/esm/generics.zod.d.ts +0 -83
- package/dist/esm/generics.zod.js +0 -47
- package/dist/esm/index.d.ts +0 -2
- package/dist/esm/index.js +0 -2
- package/dist/generics.zod.d.ts +0 -83
- package/dist/generics.zod.js +0 -50
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Copyright (
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VocalStack
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,89 +1,136 @@
|
|
|
1
|
-
|
|
1
|
+
# @vocalstack/js-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The VocalStack speech API for Node (20 or newer) and browsers: one client,
|
|
4
|
+
`VocalStack`, with one group per resource, typed from the API's one OpenAPI
|
|
5
|
+
document (`/v1/openapi.json`, generated into `@vocalstack/contract`).
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
to interact with the [VocalStack API](https://www.vocalstack.com/api).
|
|
7
|
-
Most notably, it helps implement asynchronous bidirectional communication
|
|
8
|
-
such as live stream transcriptions in just a few lines of code.
|
|
9
|
-
This SDK will work in both Node.js and the browser.
|
|
7
|
+
Read more about the [VocalStack JavaScript SDK](https://vocalstack.com/#developers) and our other SDKs on the [VocalStack website](https://vocalstack.com/).
|
|
10
8
|
|
|
9
|
+
```ts
|
|
10
|
+
import { VocalStack } from '@vocalstack/js-sdk';
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
const vs = new VocalStack({ apiKey: process.env.VOCALSTACK_API_KEY }); // baseUrl defaults to https://api.vocalstack.com
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// A stored transcription of a link (or `file:` a path, Blob or bytes).
|
|
15
|
+
const tx = await vs.transcriptions.create({ url: 'https://example.com/talk.mp3' });
|
|
16
|
+
const job = await vs.jobs.wait(tx.job!.recordingId); // done | failed | cropping
|
|
17
|
+
const { sentences } = await vs.recordings.transcript(job.recordingId);
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
npm install @vocalstack/js-sdk
|
|
19
|
+
const srt = await (await vs.recordings.export(job.recordingId, 'srt')).text();
|
|
18
20
|
```
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
at https://www.vocalstack.com/dashboard/api-keys.
|
|
22
|
-
There is a free tier available, so you can start experimenting right away.
|
|
23
|
-
|
|
22
|
+
<!-- sdk-docs: generated from the VocalStack API guides; edits here are overwritten -->
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
## Install
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
```sh
|
|
27
|
+
npm install @vocalstack/js-sdk
|
|
28
|
+
```
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
## Guides
|
|
31
|
+
|
|
32
|
+
- [Quickstart](https://github.com/vocalstack/js-sdk/blob/main/docs/quickstart.md): An API key, the SDK, and your first transcript in a dozen lines.
|
|
33
|
+
- [Transcribe a file](https://github.com/vocalstack/js-sdk/blob/main/docs/transcribe-a-file.md): Upload a file, wait for its job, read the transcript and export it. Asynchronous; nothing to keep open.
|
|
34
|
+
- [Transcribe a link](https://github.com/vocalstack/js-sdk/blob/main/docs/transcribe-a-link.md): The server fetches a public URL or a YouTube video, then transcribes it like a file. Asynchronous, with a fetch phase first.
|
|
35
|
+
- [Transcribe a batch of files](https://github.com/vocalstack/js-sdk/blob/main/docs/transcribe-a-batch.md): Send many files or links as one batch, start it once, and read every transcript when it is done. Cheaper than one transcription per file.
|
|
36
|
+
- [Stream a microphone](https://github.com/vocalstack/js-sdk/blob/main/docs/stream-a-microphone.md): Live audio in, transcript events out, over one WebSocket. Drafts replace the live tail; commits append.
|
|
37
|
+
- [Stream a file at full speed](https://github.com/vocalstack/js-sdk/blob/main/docs/stream-a-file.md): Send a file's bytes through a realtime session and read the transcript as fast as the server makes it. Nothing is stored.
|
|
38
|
+
- [Go live](https://github.com/vocalstack/js-sdk/blob/main/docs/go-live.md): Publish live audio to a stored transcription, and put it on a broadcast link listeners open. Control is HTTP; audio is one WebSocket.
|
|
39
|
+
- [Listen](https://github.com/vocalstack/js-sdk/blob/main/docs/listen.md): Follow a recording as it happens, in any language: subscribe, catch up from a snapshot, then apply live frames in order.
|
|
40
|
+
- [Live translation and spoken translations](https://github.com/vocalstack/js-sdk/blob/main/docs/translation-and-voices.md): Translate a stream as it is spoken, and have each translated sentence spoken back in a voice. Text frames and audio clips, over the same socket.
|
|
41
|
+
- [Connect an app with one click](https://github.com/vocalstack/js-sdk/blob/main/docs/connect-an-app.md): Let the people who use your app connect their VocalStack account with one Authenticate click — OAuth 2.0 authorization code with PKCE, where the access token is an API key — instead of pasting a key.
|
|
42
|
+
- [Errors, retries and limits](https://github.com/vocalstack/js-sdk/blob/main/docs/errors-and-limits.md): One error envelope everywhere; what to retry (429, 503 unavailable) and how to make a retry safe (Idempotency-Key); credit, plan limits and the 402s.
|
|
43
|
+
|
|
44
|
+
<!-- /sdk-docs -->
|
|
45
|
+
|
|
46
|
+
## What is what
|
|
47
|
+
|
|
48
|
+
| Name | What it is | Group |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| realtime session | a signed token plus one socket; nothing stored | `vs.realtime` |
|
|
51
|
+
| broadcast | the link listeners open | `vs.broadcasts` |
|
|
52
|
+
| transcription | a file, link, microphone or stream, transcribed and stored (`live: true` for a microphone or stream) | `vs.transcriptions` |
|
|
53
|
+
| recording | one take inside a broadcast or a transcription | `vs.recordings` |
|
|
54
|
+
| job | a file or link recording's transcription work | `vs.jobs` |
|
|
55
|
+
| batch | many files transcribed together, for less than one transcription each | `vs.batches` |
|
|
56
|
+
|
|
57
|
+
The other groups: `vs.corrections`, `vs.shareLinks`, `vs.voices`,
|
|
58
|
+
`vs.search`, `vs.me`, `vs.languages`, `vs.rtmp`, `vs.events`, `vs.meta`.
|
|
59
|
+
Method names follow the document's `operationId`s (`broadcasts.startRecording`
|
|
60
|
+
is `POST /v1/broadcasts/{id}/recordings`); lists return arrays.
|
|
61
|
+
|
|
62
|
+
## Options
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
new VocalStack({
|
|
66
|
+
baseUrl, // default https://api.vocalstack.com
|
|
67
|
+
apiKey, // an API key (vs_key_…), for a server
|
|
68
|
+
token, // or, in a browser: an access token / browser token, or a getter (read per request)
|
|
69
|
+
actingPrincipal, // act for an organization (X-Acting-Principal)
|
|
70
|
+
deviceId, // correlation only (X-Device-Id)
|
|
71
|
+
fetch, // any fetch
|
|
72
|
+
maxRetries: 2, // 429s, and 503 unavailable on safe calls, retried after Retry-After (at most 60 s each)
|
|
73
|
+
readyDeadlineMs, // how long a realtime socket may take to report ready
|
|
34
74
|
});
|
|
35
|
-
|
|
36
|
-
transcription.start();
|
|
37
|
-
|
|
38
|
-
// This will print the transcription data as it comes in
|
|
39
|
-
transcription.onData(console.log);
|
|
40
|
-
|
|
41
|
-
/*
|
|
42
|
-
{
|
|
43
|
-
status: 'processing',
|
|
44
|
-
data: {
|
|
45
|
-
id: 'd1e7b3b0-7b3b-4b3b-8b3b-0b3b7b3b3b3b',
|
|
46
|
-
operation: 'transcription-prerecorded',
|
|
47
|
-
progress: 0.1,
|
|
48
|
-
timeline: [
|
|
49
|
-
{
|
|
50
|
-
start: 0,
|
|
51
|
-
end: 7.52,
|
|
52
|
-
text: 'Meaningless, meaningless, says the teacher, utterly meaningless, everything is meaningless.',
|
|
53
|
-
language: 'en',
|
|
54
|
-
translations: { ... },
|
|
55
|
-
},
|
|
56
|
-
...
|
|
57
|
-
]
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
*/
|
|
61
75
|
```
|
|
62
76
|
|
|
77
|
+
Every call that creates something takes `{ idempotencyKey }` last.
|
|
63
78
|
|
|
64
|
-
|
|
79
|
+
## Realtime sessions
|
|
65
80
|
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
```ts
|
|
82
|
+
const session = await vs.realtime.connect({ spokenLanguage: 'en' });
|
|
83
|
+
session.sendAudio(pcmChunk); // raw PCM, f32le, 16 kHz, mono, as binary frames
|
|
84
|
+
for await (const event of session) {
|
|
85
|
+
if (event.type === 'transcript.committed') render(event.segment);
|
|
86
|
+
}
|
|
87
|
+
session.eof(); // at end of source: final commits, then session.done
|
|
68
88
|
|
|
69
|
-
|
|
70
|
-
|
|
89
|
+
// A whole file through one socket, nothing stored:
|
|
90
|
+
const { text, segments } = await vs.realtime.transcribeFile('meeting.mp3', { spokenLanguage: 'en' });
|
|
91
|
+
```
|
|
71
92
|
|
|
72
|
-
-
|
|
73
|
-
|
|
93
|
+
- `transcript.draft` frames **fully replace** the live tail; never append them.
|
|
94
|
+
- `transcript.committed` segments are immutable, gapless by `segment.seq`,
|
|
95
|
+
and carry word timings.
|
|
96
|
+
- A dropped connection ends the session (no resume): start a new one.
|
|
97
|
+
|
|
98
|
+
## Live recordings and listeners
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const b = await vs.broadcasts.create({ name: 'Release standup' }); // b.code is the link
|
|
102
|
+
const grant = await vs.broadcasts.startRecording(b.id); // 409 conflict + details.takeover
|
|
103
|
+
// when another device records
|
|
104
|
+
const stream = await vs.recordings.publish(grant); // the publisher socket
|
|
105
|
+
await vs.recordings.pause(grant.recording.id);
|
|
106
|
+
await vs.recordings.resume(grant.recording.id);
|
|
107
|
+
await vs.recordings.stop(grant.recording.id); // only from paused
|
|
108
|
+
|
|
109
|
+
// A listener (no account), holding a share link's code:
|
|
110
|
+
const link = await vs.broadcasts.createShareLink(b.id, { label: 'Audience' });
|
|
111
|
+
const anon = new VocalStack({ baseUrl, deviceId });
|
|
112
|
+
const sub = await anon.recordings.subscribe(grant.recording.id, { lang: 'es', shareCode: link.code, asOwner: false });
|
|
113
|
+
for await (const ev of sub) {
|
|
114
|
+
if (ev.type === 'snapshot') render(ev.snapshot); // backfill, initial or after a gap
|
|
115
|
+
if (ev.type === 'frame') apply(ev.frame); // transcript.*, recording.state, ...
|
|
116
|
+
}
|
|
117
|
+
```
|
|
74
118
|
|
|
75
|
-
|
|
76
|
-
|
|
119
|
+
Browser microphone capture ships as `@vocalstack/js-sdk/mic`
|
|
120
|
+
(`streamMicrophone(session, { onStall, onRecover })`).
|
|
77
121
|
|
|
78
|
-
|
|
79
|
-
Transcribe speech from pre-recorded audio in a URL to plain text. Major file formats are supported, including MP3, WAV, FLAC, and OGG.
|
|
122
|
+
## Errors
|
|
80
123
|
|
|
81
|
-
|
|
82
|
-
|
|
124
|
+
`VocalStackError` carries `code` (the contract's closed enum), `message`,
|
|
125
|
+
`status`, `retryable`, `details` (quota numbers, code suggestions,
|
|
126
|
+
`{ recordingId, takeover }`), `headers` and `retryAfter`.
|
|
83
127
|
|
|
84
|
-
|
|
85
|
-
Common request options and responses for all transcription operations. Use options to configure the transcription settings.
|
|
128
|
+
## Binary answers
|
|
86
129
|
|
|
87
|
-
|
|
88
|
-
|
|
130
|
+
`recordings.export`, `recordings.clip`, `recordings.voiceClip`, `jobs.preview`,
|
|
131
|
+
`voices.preview`, `voices.clonePreview` and `voices.sample` return the
|
|
132
|
+
`Response` itself: `.text()`, `.arrayBuffer()` or stream it.
|
|
133
|
+
`recordings.audioUrl` returns the signed media URL.
|
|
89
134
|
|
|
135
|
+
Verified against a language-agnostic conformance suite shared by every
|
|
136
|
+
VocalStack SDK and against a running API. For agents: `SKILL.md`.
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vocalstack
|
|
3
|
+
description: Use when writing Node or TypeScript code that calls the VocalStack speech API with @vocalstack/js-sdk, for example to transcribe an audio or video URL or a local file, stream live audio for realtime transcription, fetch or export a transcript (JSON, SRT, XLSX, PDF), or search transcripts. Covers installing, API keys, base URLs, five copy-paste programs and error handling (401, 402, 403, 429).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# VocalStack TypeScript SDK
|
|
7
|
+
|
|
8
|
+
The VocalStack API transcribes speech (files, links, live audio), with speaker
|
|
9
|
+
labels and translation. `@vocalstack/js-sdk` has one client, `VocalStack`, with
|
|
10
|
+
one group per resource: `vs.transcriptions`, `vs.jobs`, `vs.recordings`,
|
|
11
|
+
`vs.broadcasts`, `vs.search`, `vs.realtime` and a few more (listed at the end).
|
|
12
|
+
|
|
13
|
+
## What is what
|
|
14
|
+
|
|
15
|
+
- A **transcription** is a file, a link, a microphone or a stream, transcribed
|
|
16
|
+
and stored. Create one with `vs.transcriptions.create(...)`.
|
|
17
|
+
- A **recording** is one take inside it. Its id (`recordingId`) is what you
|
|
18
|
+
read the transcript and exports with: `vs.recordings.transcript(recordingId)`.
|
|
19
|
+
- A **job** is a file or link recording's transcription work:
|
|
20
|
+
`vs.jobs.wait(recordingId)` polls it until it is `done` or `failed`
|
|
21
|
+
(`{ onProgress: (job) => … }` sees each read: `job.state`, `job.progress.pct`).
|
|
22
|
+
- A **batch** is many files transcribed together for less than one
|
|
23
|
+
transcription each: `vs.batches.create()`, `addFile`/`addUrl` per file,
|
|
24
|
+
`start`, then `wait` (section 6).
|
|
25
|
+
- A **broadcast** is a link listeners open to follow a live recording.
|
|
26
|
+
- A **realtime session** (`vs.realtime`) is a socket you stream audio into and
|
|
27
|
+
read the transcript from. Nothing is stored. "Session" means only this.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
npm init -y && npm pkg set type=module # in a new folder: the programs below are ESM
|
|
33
|
+
npm i @vocalstack/js-sdk # Node 20 or newer
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
To run code newer than the npm release, install it from a clone of
|
|
37
|
+
https://github.com/vocalstack/js-sdk. Build it
|
|
38
|
+
first (`npm run build` also builds the bundled `contract/` package):
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
git clone https://github.com/vocalstack/js-sdk && (cd js-sdk && npm install && npm run build)
|
|
42
|
+
npm i ./js-sdk
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Use ESM (`"type": "module"` in package.json, or `.mjs` files).
|
|
46
|
+
|
|
47
|
+
Speaker numbers start at 0. A speaker of `-1` means speaker labels were off
|
|
48
|
+
for that transcription, so print the text without a speaker.
|
|
49
|
+
|
|
50
|
+
## Auth and base URL
|
|
51
|
+
|
|
52
|
+
- Read the key from `VOCALSTACK_API_KEY` and pass it as `apiKey`. It goes out
|
|
53
|
+
as `Authorization: Bearer <key>`, also on sockets.
|
|
54
|
+
- An API key starts with `vs_key_`. There is one kind of key: every call is
|
|
55
|
+
real usage on the account's tier and quotas.
|
|
56
|
+
- Keys are for servers. Never put a key in browser code or in a URL. The API
|
|
57
|
+
refuses a key sent from a web page (`Origin` header) or in a query string.
|
|
58
|
+
- Scopes: `read` (read transcripts, search, export), `write` and
|
|
59
|
+
`recordings:publish` (create transcriptions, stream live audio), or `*`.
|
|
60
|
+
- Base URL: `https://api.vocalstack.com` (the default). Override it with
|
|
61
|
+
`VOCALSTACK_BASE_URL` (for example, a local or staging server).
|
|
62
|
+
|
|
63
|
+
Every program below starts like this:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
import { VocalStack } from '@vocalstack/js-sdk';
|
|
67
|
+
const vs = new VocalStack({
|
|
68
|
+
baseUrl: process.env.VOCALSTACK_BASE_URL, // absent: https://api.vocalstack.com
|
|
69
|
+
apiKey: process.env.VOCALSTACK_API_KEY,
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 1. Transcribe a URL and wait for the result
|
|
74
|
+
|
|
75
|
+
The server fetches the link itself, so it must be a public http(s) address.
|
|
76
|
+
Private, loopback and cloud-metadata addresses are refused.
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
import { VocalStack } from '@vocalstack/js-sdk';
|
|
80
|
+
const vs = new VocalStack({ baseUrl: process.env.VOCALSTACK_BASE_URL, apiKey: process.env.VOCALSTACK_API_KEY });
|
|
81
|
+
|
|
82
|
+
const tx = await vs.transcriptions.create({
|
|
83
|
+
url: 'https://raw.githubusercontent.com/openai/whisper/main/tests/jfk.flac',
|
|
84
|
+
name: 'JFK sample',
|
|
85
|
+
});
|
|
86
|
+
const recordingId = tx.job.recordingId;
|
|
87
|
+
|
|
88
|
+
// queued -> fetching -> transcribing -> done | failed
|
|
89
|
+
const job = await vs.jobs.wait(recordingId);
|
|
90
|
+
if (job.state !== 'done') throw new Error(`transcription ${job.state}: ${job.error}`);
|
|
91
|
+
|
|
92
|
+
const { sentences } = await vs.recordings.transcript(recordingId);
|
|
93
|
+
for (const s of sentences) console.log(s.speaker >= 0 ? `[speaker ${s.speaker}] ${s.text}` : s.text);
|
|
94
|
+
console.log('recordingId', recordingId);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 2. Transcribe a local file
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
import { VocalStack } from '@vocalstack/js-sdk';
|
|
101
|
+
const vs = new VocalStack({ baseUrl: process.env.VOCALSTACK_BASE_URL, apiKey: process.env.VOCALSTACK_API_KEY });
|
|
102
|
+
|
|
103
|
+
const tx = await vs.transcriptions.create({
|
|
104
|
+
file: process.argv[2], // a path (mp3, wav, flac, ogg, m4a, mp4 ...), a Blob or bytes
|
|
105
|
+
settings: { spokenLanguage: 'en', speakerLabels: 'on' }, // both optional; speakerLabels: 'auto' (default) | 'on' | 'off'
|
|
106
|
+
// Off unless asked for: settings.summary, settings.title, settings.summaryTranslations,
|
|
107
|
+
// settings.voice (spoken translations), and shareLink: true (a public share link).
|
|
108
|
+
});
|
|
109
|
+
const job = await vs.jobs.wait(tx.job.recordingId);
|
|
110
|
+
if (job.state !== 'done') throw new Error(`transcription ${job.state}: ${job.error}`);
|
|
111
|
+
|
|
112
|
+
const { sentences } = await vs.recordings.transcript(job.recordingId);
|
|
113
|
+
console.log(sentences.map((s) => s.text).join(' '));
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 3. Fetch a transcript or an export file
|
|
117
|
+
|
|
118
|
+
`recordings.transcript` returns segments (with word timings), sentences and
|
|
119
|
+
speakers. `recordings.export` returns the file as a `Response`: formats `json`,
|
|
120
|
+
`srt`, `xlsx`, `pdf`; add `{ lang: '<code>' }` for a translated file.
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
import { writeFile } from 'node:fs/promises';
|
|
124
|
+
import { VocalStack } from '@vocalstack/js-sdk';
|
|
125
|
+
const vs = new VocalStack({ baseUrl: process.env.VOCALSTACK_BASE_URL, apiKey: process.env.VOCALSTACK_API_KEY });
|
|
126
|
+
const recordingId = process.argv[2];
|
|
127
|
+
|
|
128
|
+
const snap = await vs.recordings.transcript(recordingId);
|
|
129
|
+
console.log(`${snap.segments.length} segments, ${snap.sentences.length} sentences, state ${snap.state}`);
|
|
130
|
+
|
|
131
|
+
const res = await vs.recordings.export(recordingId, 'srt');
|
|
132
|
+
await writeFile('transcript.srt', Buffer.from(await res.arrayBuffer()));
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## 4. Live transcription from a microphone or a file
|
|
136
|
+
|
|
137
|
+
A live transcription takes raw PCM: 32-bit float little-endian, 16 kHz, mono.
|
|
138
|
+
Create it, start a recording, and open the publisher socket the grant names.
|
|
139
|
+
Here ffmpeg decodes a file at realtime speed (`-re`). For a microphone, swap
|
|
140
|
+
the input: `-f avfoundation -i :0` on macOS, `-f pulse -i default` on Linux.
|
|
141
|
+
(`@vocalstack/js-sdk/mic` is for browsers, and browsers must not hold API keys.)
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
import { spawn } from 'node:child_process';
|
|
145
|
+
import { VocalStack } from '@vocalstack/js-sdk';
|
|
146
|
+
const vs = new VocalStack({ baseUrl: process.env.VOCALSTACK_BASE_URL, apiKey: process.env.VOCALSTACK_API_KEY });
|
|
147
|
+
|
|
148
|
+
const tx = await vs.transcriptions.createLive({ name: 'Live demo', settings: { spokenLanguage: 'en' } });
|
|
149
|
+
const grant = await vs.transcriptions.startRecording(tx.id);
|
|
150
|
+
const stream = await vs.recordings.publish(grant, { readyDeadlineMs: 180_000 }); // a cold engine can take a while
|
|
151
|
+
|
|
152
|
+
const ff = spawn('ffmpeg', ['-loglevel', 'error', '-re', '-i', process.argv[2],
|
|
153
|
+
'-f', 'f32le', '-ac', '1', '-ar', '16000', '-']);
|
|
154
|
+
ff.stdout.on('data', (chunk) => stream.sendAudio(new Uint8Array(chunk)));
|
|
155
|
+
ff.on('close', () => stream.eof()); // eof() ends the recording
|
|
156
|
+
|
|
157
|
+
for await (const ev of stream) {
|
|
158
|
+
if (ev.type === 'transcript.committed') console.log(`[speaker ${ev.segment.speaker}] ${ev.segment.text}`);
|
|
159
|
+
// also: 'transcript.draft' (replaces the live tail), 'transcript.sentence', 'transcript.translated'
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// To end a recording without eof(): vs.recordings.pause(id), then vs.recordings.stop(id).
|
|
163
|
+
const { sentences } = await vs.recordings.transcript(grant.recording.id);
|
|
164
|
+
console.log('final:', sentences.map((s) => s.text).join(' '));
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
To transcribe without storing anything, use a realtime session instead:
|
|
168
|
+
`await vs.realtime.transcribeFile('talk.mp3')` returns `{ text, segments }`.
|
|
169
|
+
|
|
170
|
+
## 5. Search transcripts
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
import { VocalStack } from '@vocalstack/js-sdk';
|
|
174
|
+
const vs = new VocalStack({ baseUrl: process.env.VOCALSTACK_BASE_URL, apiKey: process.env.VOCALSTACK_API_KEY });
|
|
175
|
+
|
|
176
|
+
const { hits } = await vs.search.query(process.argv[2] ?? 'country', { limit: 5 }); // q: 2+ characters
|
|
177
|
+
for (const h of hits) {
|
|
178
|
+
console.log(`${h.linkName} @ ${h.mediaStart.toFixed(1)}s [speaker ${h.speaker}]: ${h.text} (recording ${h.recordingId})`);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## 6. A batch of files
|
|
183
|
+
|
|
184
|
+
Many files at once cost less as a batch: one create, one minimum charge for
|
|
185
|
+
the whole batch. Its files get transcripts, exports and speaker labels (when
|
|
186
|
+
`settings.speakerLabels` asks), never a summary, title, share link or voices.
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
import { VocalStack } from '@vocalstack/js-sdk';
|
|
190
|
+
const vs = new VocalStack({ baseUrl: process.env.VOCALSTACK_BASE_URL, apiKey: process.env.VOCALSTACK_API_KEY });
|
|
191
|
+
|
|
192
|
+
const batch = await vs.batches.create({ name: 'Interviews', settings: { spokenLanguage: 'en' } });
|
|
193
|
+
for (const path of process.argv.slice(2)) await vs.batches.addFile(batch.id, { file: path }); // or addUrl(batch.id, url)
|
|
194
|
+
await vs.batches.start(batch.id);
|
|
195
|
+
const done = await vs.batches.wait(batch.id); // done (or stopped); check done.counts.failed
|
|
196
|
+
for (const tx of done.files ?? []) {
|
|
197
|
+
if (tx.job?.state !== 'done') continue;
|
|
198
|
+
const { sentences } = await vs.recordings.transcript(tx.job.recordingId);
|
|
199
|
+
console.log(tx.name, sentences.map((s) => s.text).join(' '));
|
|
200
|
+
}
|
|
201
|
+
// vs.batches.export(batch.id, 'srt') is every finished transcript in one zip.
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Errors
|
|
205
|
+
|
|
206
|
+
A failed call throws `VocalStackError` with `code`, `message`, `status`,
|
|
207
|
+
`retryable`, `details` (the body's `data.details`), `headers` and
|
|
208
|
+
`retryAfter` (seconds, from `Retry-After`). A `429`, and a `503 unavailable`
|
|
209
|
+
on a call that is safe to repeat, is retried for you after its `Retry-After`,
|
|
210
|
+
twice by default (`maxRetries` on the client).
|
|
211
|
+
|
|
212
|
+
| Status | `code` | What to do |
|
|
213
|
+
|---|---|---|
|
|
214
|
+
| 401 | `invalid_token`, `key_expired`, `key_revoked` | Check `VOCALSTACK_API_KEY`. Use the replacement key, or create a new one. |
|
|
215
|
+
| 403 | `forbidden` | Usually a missing scope: the message names it, and `err.headers.get('www-authenticate')` lists the scopes the route needs. A suspended key or account gets `suspended`. |
|
|
216
|
+
| 403 | `terms_acceptance_required` | The account has not accepted the current Terms of Service and Privacy Policy: someone signs in to the Dashboard and accepts them (an organisation's keys work once an owner or admin has). Not retryable until then. |
|
|
217
|
+
| 402 | `insufficient_credit` | The account's credit is out, or does not cover the transcription: every transcription is billed at least the plan's minimum (`rates.minimumSeconds`, 5 minutes). `details`: `balance`, `plan`, `topUpUrl`, and `required` when the cost was known up front. Add credit; `vs.billing.get()` shows the balance. |
|
|
218
|
+
| 409 | `payment_method_required` | Auto top-up (`vs.billing.setAutoTopUp`) and the portal (`vs.billing.portal()`) need a payment first (`vs.billing.createCheckout({ kind: 'topup', amount, acceptTerms: true })` opens one; a signed-in person's token, never an API key). |
|
|
219
|
+
| 402 | `quota_exceeded` | The plan's transcriptions-at-once limit is reached. `details`: `metric` (`concurrent_transcriptions`), `limit`, `used`, `plan`, `topUpUrl`. |
|
|
220
|
+
| 429 | `rate_limited` | Retried automatically; if it still fails, wait `err.retryAfter` seconds. Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. |
|
|
221
|
+
| 503 | `unavailable` | The service is waking (its database was asleep). Retried automatically after `Retry-After` (2 s) on calls that are safe to repeat: GET, HEAD, DELETE, or any call with `{ idempotencyKey }`. A create without a key is not retried: retry it yourself, with an `idempotencyKey` so it cannot run twice. A realtime socket refused this way throws `unavailable`; a bare 503 there is `capacity_exceeded`. |
|
|
222
|
+
|
|
223
|
+
```js
|
|
224
|
+
import { VocalStack, VocalStackError } from '@vocalstack/js-sdk';
|
|
225
|
+
const vs = new VocalStack({ baseUrl: process.env.VOCALSTACK_BASE_URL, apiKey: process.env.VOCALSTACK_API_KEY });
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
await vs.search.query('country');
|
|
229
|
+
} catch (err) {
|
|
230
|
+
if (!(err instanceof VocalStackError)) throw err;
|
|
231
|
+
if (err.code === 'insufficient_credit' || err.code === 'quota_exceeded') console.error(err.message, err.details);
|
|
232
|
+
else console.error(err.status, err.code, err.message);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## More
|
|
237
|
+
|
|
238
|
+
- The other groups: `vs.transcriptions` (`list`, `get`, `update`, `archive`,
|
|
239
|
+
`restore`, `purge`, `listShareLinks`, `createShareLink`), `vs.recordings`
|
|
240
|
+
(`get`, `summarize`, `summary`, `translation`, `audioUrl`, `subscribe` for a
|
|
241
|
+
live transcript feed), `vs.batches` (`create`, `addFile`, `addUrl`, `start`,
|
|
242
|
+
`wait`, `get`, `list`, `export`, `stop`, `delete`), `vs.corrections`, `vs.shareLinks`, `vs.voices`,
|
|
243
|
+
`vs.broadcasts`, `vs.me` (`get`, `usage`), `vs.languages`,
|
|
244
|
+
`vs.rtmp`, `vs.events`, `vs.meta`. Each method is named after its operation
|
|
245
|
+
in the OpenAPI document (`transcriptions.create` is `POST /v1/transcriptions`).
|
|
246
|
+
- `<base>/llms.txt` lists every operation in a form written for agents.
|
|
247
|
+
`<base>/llms-full.txt` adds parameters, auth mode, tier and scopes.
|
|
248
|
+
- `<base>/docs` is the interactive reference. `<base>/v1/openapi.json` is
|
|
249
|
+
the OpenAPI document; the SDK's types are generated from it.
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ErrorCode } from '@vocalstack/contract';
|
|
2
|
+
export type ConnectTokenErrorCode = 'invalid_request' | 'invalid_grant' | 'unsupported_grant_type';
|
|
3
|
+
export interface VocalStackErrorOptions {
|
|
4
|
+
retryable?: boolean;
|
|
5
|
+
status?: number;
|
|
6
|
+
details?: unknown;
|
|
7
|
+
headers?: Headers;
|
|
8
|
+
requestId?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare class VocalStackError extends Error {
|
|
11
|
+
readonly code: ErrorCode | ConnectTokenErrorCode;
|
|
12
|
+
readonly retryable: boolean;
|
|
13
|
+
readonly status?: number;
|
|
14
|
+
readonly details?: unknown;
|
|
15
|
+
readonly headers?: Headers;
|
|
16
|
+
readonly requestId?: string;
|
|
17
|
+
constructor(code: ErrorCode | ConnectTokenErrorCode, message: string, opts?: VocalStackErrorOptions);
|
|
18
|
+
get retryAfter(): number | undefined;
|
|
19
|
+
}
|
|
20
|
+
export declare function capacityExceeded(message: string): VocalStackError;
|
|
21
|
+
export declare function fromEnvelope(body: unknown, status?: number, headers?: Headers): VocalStackError;
|
|
22
|
+
export declare function fromOAuth(body: unknown, status?: number, headers?: Headers): VocalStackError;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export class VocalStackError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
retryable;
|
|
4
|
+
status;
|
|
5
|
+
details;
|
|
6
|
+
headers;
|
|
7
|
+
requestId;
|
|
8
|
+
constructor(code, message, opts = {}) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = 'VocalStackError';
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.retryable = opts.retryable ?? false;
|
|
13
|
+
this.status = opts.status;
|
|
14
|
+
this.details = opts.details;
|
|
15
|
+
this.headers = opts.headers;
|
|
16
|
+
this.requestId = opts.requestId;
|
|
17
|
+
}
|
|
18
|
+
get retryAfter() {
|
|
19
|
+
const v = this.headers?.get('retry-after');
|
|
20
|
+
if (v == null || v === '')
|
|
21
|
+
return undefined;
|
|
22
|
+
const n = Number(v);
|
|
23
|
+
if (Number.isFinite(n))
|
|
24
|
+
return n;
|
|
25
|
+
const at = Date.parse(v);
|
|
26
|
+
return Number.isNaN(at) ? undefined : Math.max(0, (at - Date.now()) / 1000);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function capacityExceeded(message) {
|
|
30
|
+
return new VocalStackError('capacity_exceeded', message, { retryable: true });
|
|
31
|
+
}
|
|
32
|
+
export function fromEnvelope(body, status, headers) {
|
|
33
|
+
const b = body;
|
|
34
|
+
const requestId = (b && typeof b.requestId === 'string' ? b.requestId : undefined) ?? headers?.get('x-request-id') ?? undefined;
|
|
35
|
+
if (b && typeof b.code === 'string') {
|
|
36
|
+
return new VocalStackError(b.code, b.message ?? b.code, {
|
|
37
|
+
retryable: b.data?.retryable ?? false,
|
|
38
|
+
status,
|
|
39
|
+
details: b.data?.details,
|
|
40
|
+
headers,
|
|
41
|
+
requestId,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return new VocalStackError('internal', `unexpected error (status ${status})`, { status, headers, requestId });
|
|
45
|
+
}
|
|
46
|
+
export function fromOAuth(body, status, headers) {
|
|
47
|
+
const b = body;
|
|
48
|
+
if (status === 400 && b && typeof b.error === 'string') {
|
|
49
|
+
return new VocalStackError(b.error, b.error_description ?? b.error, {
|
|
50
|
+
status,
|
|
51
|
+
headers,
|
|
52
|
+
requestId: headers?.get('x-request-id') ?? undefined,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return fromEnvelope(body, status, headers);
|
|
56
|
+
}
|
package/dist/file.d.ts
ADDED
package/dist/file.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export async function* fileChunks(input) {
|
|
2
|
+
if (typeof input === 'string') {
|
|
3
|
+
const fsSpecifier = 'node:' + 'fs';
|
|
4
|
+
const { createReadStream } = (await import(/* @vite-ignore */ fsSpecifier));
|
|
5
|
+
for await (const chunk of createReadStream(input)) {
|
|
6
|
+
yield chunk;
|
|
7
|
+
}
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (input instanceof Uint8Array) {
|
|
11
|
+
yield input;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (input instanceof ArrayBuffer) {
|
|
15
|
+
yield new Uint8Array(input);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (typeof Blob !== 'undefined' && input instanceof Blob) {
|
|
19
|
+
const reader = input.stream().getReader();
|
|
20
|
+
try {
|
|
21
|
+
for (;;) {
|
|
22
|
+
const { done, value } = await reader.read();
|
|
23
|
+
if (done)
|
|
24
|
+
return;
|
|
25
|
+
if (value)
|
|
26
|
+
yield value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
reader.releaseLock();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (input && typeof input === 'object' && Symbol.asyncIterator in input) {
|
|
34
|
+
yield* input;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
throw new TypeError('transcribeFile: expected a path, Blob, byte array, or async iterable');
|
|
38
|
+
}
|