@vocalstack/js-sdk 1.1.0 → 2.0.0-alpha.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.
Files changed (73) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +112 -69
  3. package/SKILL.md +252 -0
  4. package/dist/errors.d.ts +40 -0
  5. package/dist/errors.js +74 -0
  6. package/dist/file.d.ts +2 -0
  7. package/dist/file.js +38 -0
  8. package/dist/http.d.ts +75 -0
  9. package/dist/http.js +122 -0
  10. package/dist/index.d.ts +56 -2
  11. package/dist/index.js +65 -17
  12. package/dist/mic.d.ts +75 -0
  13. package/dist/mic.js +258 -0
  14. package/dist/realtime.d.ts +132 -0
  15. package/dist/realtime.js +450 -0
  16. package/dist/resources.d.ts +521 -0
  17. package/dist/resources.js +787 -0
  18. package/dist/subscription.d.ts +55 -0
  19. package/dist/subscription.js +164 -0
  20. package/dist/ws.d.ts +12 -0
  21. package/dist/ws.js +15 -0
  22. package/package.json +32 -36
  23. package/skills/vocalstack/SKILL.md +252 -0
  24. package/dist/ApiVersion.d.ts +0 -2
  25. package/dist/ApiVersion.js +0 -32
  26. package/dist/RestApi.d.ts +0 -21
  27. package/dist/RestApi.js +0 -95
  28. package/dist/VocalStackApi.d.ts +0 -19
  29. package/dist/VocalStackApi.js +0 -60
  30. package/dist/WebsocketApi.d.ts +0 -38
  31. package/dist/WebsocketApi.js +0 -150
  32. package/dist/apis/Billing.d.ts +0 -59
  33. package/dist/apis/Billing.js +0 -21
  34. package/dist/apis/LiveTranscription.d.ts +0 -58
  35. package/dist/apis/LiveTranscription.js +0 -55
  36. package/dist/apis/Polyglot.d.ts +0 -113
  37. package/dist/apis/Polyglot.js +0 -68
  38. package/dist/apis/Security.d.ts +0 -25
  39. package/dist/apis/Security.js +0 -35
  40. package/dist/apis/Transcriptions.d.ts +0 -60
  41. package/dist/apis/Transcriptions.js +0 -38
  42. package/dist/apis/UrlTranscription.d.ts +0 -59
  43. package/dist/apis/UrlTranscription.js +0 -53
  44. package/dist/apis/index.d.ts +0 -6
  45. package/dist/apis/index.js +0 -22
  46. package/dist/esm/ApiVersion.d.ts +0 -2
  47. package/dist/esm/ApiVersion.js +0 -28
  48. package/dist/esm/RestApi.d.ts +0 -21
  49. package/dist/esm/RestApi.js +0 -91
  50. package/dist/esm/VocalStackApi.d.ts +0 -19
  51. package/dist/esm/VocalStackApi.js +0 -56
  52. package/dist/esm/WebsocketApi.d.ts +0 -38
  53. package/dist/esm/WebsocketApi.js +0 -146
  54. package/dist/esm/apis/Billing.d.ts +0 -59
  55. package/dist/esm/apis/Billing.js +0 -17
  56. package/dist/esm/apis/LiveTranscription.d.ts +0 -58
  57. package/dist/esm/apis/LiveTranscription.js +0 -51
  58. package/dist/esm/apis/Polyglot.d.ts +0 -113
  59. package/dist/esm/apis/Polyglot.js +0 -64
  60. package/dist/esm/apis/Security.d.ts +0 -25
  61. package/dist/esm/apis/Security.js +0 -31
  62. package/dist/esm/apis/Transcriptions.d.ts +0 -60
  63. package/dist/esm/apis/Transcriptions.js +0 -34
  64. package/dist/esm/apis/UrlTranscription.d.ts +0 -59
  65. package/dist/esm/apis/UrlTranscription.js +0 -49
  66. package/dist/esm/apis/index.d.ts +0 -6
  67. package/dist/esm/apis/index.js +0 -6
  68. package/dist/esm/generics.zod.d.ts +0 -83
  69. package/dist/esm/generics.zod.js +0 -47
  70. package/dist/esm/index.d.ts +0 -2
  71. package/dist/esm/index.js +0 -2
  72. package/dist/generics.zod.d.ts +0 -83
  73. package/dist/generics.zod.js +0 -50
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
+ }
package/dist/http.d.ts ADDED
@@ -0,0 +1,75 @@
1
+ export type Query = Record<string, string | number | boolean | undefined | null>;
2
+ export interface ClientOptions {
3
+ /** API origin; `https://api.vocalstack.com` when absent. */
4
+ baseUrl?: string;
5
+ /** An API key (`vs_key_…`), on a server: keys are refused from web
6
+ * pages. Absent (and no `token`) = anonymous (a listener). */
7
+ apiKey?: string;
8
+ /** In a browser instead: a signed-in person's access token or a
9
+ * short-lived browser token (`vs_bt_…`), or a getter so a refreshed one
10
+ * is picked up per request. Not with `apiKey`. */
11
+ token?: string | (() => string | undefined | null | Promise<string | undefined | null>);
12
+ /** Act for an organization the user belongs to (`X-Acting-Principal`). */
13
+ actingPrincipal?: string;
14
+ /** A stable per-device id (correlation only, `X-Device-Id`). */
15
+ deviceId?: string;
16
+ fetch?: typeof fetch;
17
+ /** How often a `429 rate_limited`, or a `503 unavailable` on a call that
18
+ * is safe to repeat (GET, HEAD, DELETE, or one with an `Idempotency-Key`),
19
+ * is retried after its `Retry-After` (at most 60 s each). Default 2; 0
20
+ * turns it off. */
21
+ maxRetries?: number;
22
+ /** Default deadline for a realtime socket to report ready, in ms. */
23
+ readyDeadlineMs?: number;
24
+ }
25
+ /** Per-call options every method takes last. */
26
+ export interface CallOptions {
27
+ /** Sent as `Idempotency-Key` on the creates that honour it. */
28
+ idempotencyKey?: string;
29
+ signal?: AbortSignal;
30
+ }
31
+ /** How a listener reads something that is not theirs: a private
32
+ * broadcast's listener token, or a share link's code. */
33
+ export interface ReadAs {
34
+ listenerToken?: string;
35
+ shareCode?: string;
36
+ }
37
+ /** A listener-side read's query: the wire spells both as the options do
38
+ * (`?listenerToken=`, `?shareCode=`), so this only picks them out. */
39
+ export declare function listenerQuery(as: ReadAs | undefined): Query;
40
+ export interface RequestInit2 extends CallOptions {
41
+ query?: Query;
42
+ body?: unknown;
43
+ headers?: Record<string, string>;
44
+ /** Send no credential even when the client has one. */
45
+ anonymous?: boolean;
46
+ /** Never retry, whatever the answer (a single-use code). */
47
+ noRetry?: boolean;
48
+ /** Read a 400 as OAuth's `{ error, error_description }`. */
49
+ oauthErrors?: boolean;
50
+ }
51
+ export declare const DEFAULT_BASE_URL = "https://api.vocalstack.com";
52
+ export declare function qs(q: Query | undefined): string;
53
+ export declare const seg: (s: string | number) => string;
54
+ /** Whether a failed call is retried after its `Retry-After` (up to
55
+ * `maxRetries` times): `429 rate_limited` always (nothing ran), and
56
+ * `503 unavailable` (the database is waking) only when repeating the call
57
+ * cannot do the work twice: GET, HEAD and DELETE, or a call carrying an
58
+ * `Idempotency-Key`. */
59
+ export declare function retries(status: number, code: string, method: string, idempotencyKey?: string): boolean;
60
+ /** The one HTTP path every resource method goes through. */
61
+ export declare class Transport {
62
+ #private;
63
+ readonly baseUrl: string;
64
+ readonly opts: ClientOptions;
65
+ constructor(opts: ClientOptions);
66
+ /** What goes in `Authorization: Bearer`: the API key, else the token. */
67
+ bearer(): Promise<string | undefined>;
68
+ url(path: string, query?: Query): string;
69
+ wsUrl(path: string, query?: Query): string;
70
+ /** The raw response, after error mapping. Redirects are not followed
71
+ * (`redirect: 'manual'`), so a 302 comes back as itself. */
72
+ raw(method: string, path: string, init?: RequestInit2): Promise<Response>;
73
+ /** A JSON call; an empty body resolves to `undefined`. */
74
+ json<T>(method: string, path: string, init?: RequestInit2): Promise<T>;
75
+ }
package/dist/http.js ADDED
@@ -0,0 +1,122 @@
1
+ import { fromEnvelope, fromOAuth } from './errors.js';
2
+ /** A listener-side read's query: the wire spells both as the options do
3
+ * (`?listenerToken=`, `?shareCode=`), so this only picks them out. */
4
+ export function listenerQuery(as) {
5
+ return { listenerToken: as?.listenerToken, shareCode: as?.shareCode };
6
+ }
7
+ export const DEFAULT_BASE_URL = 'https://api.vocalstack.com';
8
+ export function qs(q) {
9
+ if (!q)
10
+ return '';
11
+ const parts = [];
12
+ for (const [k, v] of Object.entries(q)) {
13
+ if (v === undefined || v === null || v === '')
14
+ continue;
15
+ parts.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);
16
+ }
17
+ return parts.length ? `?${parts.join('&')}` : '';
18
+ }
19
+ export const seg = (s) => encodeURIComponent(String(s));
20
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
21
+ /** The wait before retrying `503 unavailable` when it names none. */
22
+ const UNAVAILABLE_WAIT_S = 2;
23
+ /** Whether a failed call is retried after its `Retry-After` (up to
24
+ * `maxRetries` times): `429 rate_limited` always (nothing ran), and
25
+ * `503 unavailable` (the database is waking) only when repeating the call
26
+ * cannot do the work twice: GET, HEAD and DELETE, or a call carrying an
27
+ * `Idempotency-Key`. */
28
+ export function retries(status, code, method, idempotencyKey) {
29
+ if (status === 429)
30
+ return true;
31
+ if (status !== 503 || code !== 'unavailable')
32
+ return false;
33
+ const m = method.toUpperCase();
34
+ return m === 'GET' || m === 'HEAD' || m === 'DELETE' || !!idempotencyKey;
35
+ }
36
+ /** The one HTTP path every resource method goes through. */
37
+ export class Transport {
38
+ baseUrl;
39
+ opts;
40
+ #fetch;
41
+ constructor(opts) {
42
+ this.baseUrl = (opts.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, '');
43
+ if (opts.apiKey && opts.token)
44
+ throw new TypeError('VocalStack: pass apiKey (a server) or token (a browser), not both');
45
+ this.opts = opts;
46
+ this.#fetch = opts.fetch ?? globalThis.fetch.bind(globalThis);
47
+ }
48
+ /** What goes in `Authorization: Bearer`: the API key, else the token. */
49
+ async bearer() {
50
+ if (this.opts.apiKey)
51
+ return this.opts.apiKey;
52
+ const t = this.opts.token;
53
+ const v = typeof t === 'function' ? await t() : t;
54
+ return v ?? undefined;
55
+ }
56
+ url(path, query) {
57
+ return `${this.baseUrl}${path}${qs(query)}`;
58
+ }
59
+ wsUrl(path, query) {
60
+ return `${this.baseUrl.replace(/^http/, 'ws')}${path}${qs(query)}`;
61
+ }
62
+ /** The raw response, after error mapping. Redirects are not followed
63
+ * (`redirect: 'manual'`), so a 302 comes back as itself. */
64
+ async raw(method, path, init = {}) {
65
+ const headers = { accept: 'application/json', ...init.headers };
66
+ const form = typeof FormData !== 'undefined' && init.body instanceof FormData;
67
+ const urlencoded = init.body instanceof URLSearchParams;
68
+ if (urlencoded)
69
+ headers['content-type'] = 'application/x-www-form-urlencoded';
70
+ else if (init.body !== undefined && !form)
71
+ headers['content-type'] = 'application/json';
72
+ const token = init.anonymous ? undefined : await this.bearer();
73
+ if (token)
74
+ headers.authorization = `Bearer ${token}`;
75
+ if (this.opts.actingPrincipal)
76
+ headers['x-acting-principal'] = this.opts.actingPrincipal;
77
+ if (this.opts.deviceId)
78
+ headers['x-device-id'] = this.opts.deviceId;
79
+ if (init.idempotencyKey)
80
+ headers['idempotency-key'] = init.idempotencyKey;
81
+ const body = init.body === undefined ? undefined : form ? init.body : urlencoded ? String(init.body) : JSON.stringify(init.body);
82
+ const maxRetries = init.noRetry ? 0 : (this.opts.maxRetries ?? 2);
83
+ for (let attempt = 0;; attempt++) {
84
+ const res = await this.#fetch(this.url(path, init.query), {
85
+ method,
86
+ headers,
87
+ body,
88
+ redirect: 'manual',
89
+ signal: init.signal,
90
+ });
91
+ if (res.ok || (res.status >= 300 && res.status < 400))
92
+ return res;
93
+ const text = await res.text().catch(() => '');
94
+ let json = null;
95
+ try {
96
+ json = text ? JSON.parse(text) : null;
97
+ }
98
+ catch {
99
+ json = null;
100
+ }
101
+ const err = (init.oauthErrors ? fromOAuth : fromEnvelope)(json, res.status, res.headers);
102
+ if (retries(res.status, err.code, method, init.idempotencyKey) && attempt < maxRetries) {
103
+ // `unavailable` (a waking database) asks for Retry-After: 2; a
104
+ // missing header means the same.
105
+ const wait = err.retryAfter ?? (err.code === 'unavailable' ? UNAVAILABLE_WAIT_S : undefined);
106
+ if (wait !== undefined && wait <= 60) {
107
+ await sleep(wait * 1000);
108
+ continue;
109
+ }
110
+ }
111
+ throw err;
112
+ }
113
+ }
114
+ /** A JSON call; an empty body resolves to `undefined`. */
115
+ async json(method, path, init = {}) {
116
+ const res = await this.raw(method, path, init);
117
+ if (res.status >= 300 && res.status < 400)
118
+ return { location: res.headers.get('location') };
119
+ const text = await res.text();
120
+ return (text ? JSON.parse(text) : undefined);
121
+ }
122
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,56 @@
1
- export * from './apis/index';
2
- export * from './generics.zod';
1
+ /**
2
+ * The VocalStack SDK: one client, `VocalStack`, grouped by resource.
3
+ *
4
+ * import { VocalStack } from '@vocalstack/js-sdk';
5
+ * const vs = new VocalStack({ apiKey: process.env.VOCALSTACK_API_KEY });
6
+ *
7
+ * const tx = await vs.transcriptions.createFromUrl('https://example.com/talk.mp3');
8
+ * const job = await vs.jobs.wait(tx.job!.recordingId);
9
+ * const { sentences } = await vs.recordings.transcript(job.recordingId);
10
+ *
11
+ * What is what (the API's glossary): a realtime session is a signed token
12
+ * plus one socket, nothing stored (`vs.realtime`); a broadcast is the link
13
+ * listeners open; a transcription is a file, link, microphone or stream
14
+ * transcribed and stored; a recording is one take inside either; a job is
15
+ * a file or link recording's transcription work.
16
+ */
17
+ import { type ClientOptions } from './http.js';
18
+ import { Realtime } from './realtime.js';
19
+ import { Batches, Billing, Broadcasts, Connect, Corrections, Events, Jobs, Languages, MeApi, Meta, Recordings, Rtmp, Search, ShareLinks, Transcriptions, Voices } from './resources.js';
20
+ export type { ClientOptions, CallOptions, ReadAs, Query } from './http.js';
21
+ export { DEFAULT_BASE_URL } from './http.js';
22
+ export { VocalStackError } from './errors.js';
23
+ export type { ConnectTokenErrorCode, VocalStackErrorOptions } from './errors.js';
24
+ export { RealtimeSession } from './realtime.js';
25
+ export type { ConnectParams, SessionEvent } from './realtime.js';
26
+ export type { FileInput } from './file.js';
27
+ export { Realtime, applySentence } from './realtime.js';
28
+ export type { ConnectOptions, TranscribeOptions, RealtimeTranscript } from './realtime.js';
29
+ export { Subscription } from './subscription.js';
30
+ export type { SubscribeOptions, SubscriptionEvent } from './subscription.js';
31
+ export type { WsLike } from './ws.js';
32
+ export { Batches, Broadcasts, Connect, Corrections, Events, Jobs, Languages, MeApi, Meta, Recordings, Rtmp, Search, ShareLinks, Transcriptions, Voices, } from './resources.js';
33
+ export type { AddBatchFileInput, ConnectToken, ConnectTokenInput, CreateTranscriptionInput, LifecycleFilter, Me, PageOptions, RecordingPageOptions, SearchOptions, Upload, UsageChargesOptions, UsageOptions } from './resources.js';
34
+ export type * from '@vocalstack/contract';
35
+ export type VocalStackOptions = ClientOptions;
36
+ export declare class VocalStack {
37
+ readonly baseUrl: string;
38
+ readonly realtime: Realtime;
39
+ readonly broadcasts: Broadcasts;
40
+ readonly transcriptions: Transcriptions;
41
+ readonly batches: Batches;
42
+ readonly recordings: Recordings;
43
+ readonly jobs: Jobs;
44
+ readonly corrections: Corrections;
45
+ readonly shareLinks: ShareLinks;
46
+ readonly voices: Voices;
47
+ readonly search: Search;
48
+ readonly me: MeApi;
49
+ readonly connect: Connect;
50
+ readonly billing: Billing;
51
+ readonly languages: Languages;
52
+ readonly rtmp: Rtmp;
53
+ readonly events: Events;
54
+ readonly meta: Meta;
55
+ constructor(opts?: ClientOptions);
56
+ }
package/dist/index.js CHANGED
@@ -1,18 +1,66 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
1
+ /**
2
+ * The VocalStack SDK: one client, `VocalStack`, grouped by resource.
3
+ *
4
+ * import { VocalStack } from '@vocalstack/js-sdk';
5
+ * const vs = new VocalStack({ apiKey: process.env.VOCALSTACK_API_KEY });
6
+ *
7
+ * const tx = await vs.transcriptions.createFromUrl('https://example.com/talk.mp3');
8
+ * const job = await vs.jobs.wait(tx.job!.recordingId);
9
+ * const { sentences } = await vs.recordings.transcript(job.recordingId);
10
+ *
11
+ * What is what (the API's glossary): a realtime session is a signed token
12
+ * plus one socket, nothing stored (`vs.realtime`); a broadcast is the link
13
+ * listeners open; a transcription is a file, link, microphone or stream
14
+ * transcribed and stored; a recording is one take inside either; a job is
15
+ * a file or link recording's transcription work.
16
+ */
17
+ import { Transport } from './http.js';
18
+ import { Realtime } from './realtime.js';
19
+ import { Batches, Billing, Broadcasts, Connect, Corrections, Events, Jobs, Languages, MeApi, Meta, Recordings, Rtmp, Search, ShareLinks, Transcriptions, Voices, } from './resources.js';
20
+ export { DEFAULT_BASE_URL } from './http.js';
21
+ export { VocalStackError } from './errors.js';
22
+ export { RealtimeSession } from './realtime.js';
23
+ export { Realtime, applySentence } from './realtime.js';
24
+ export { Subscription } from './subscription.js';
25
+ export { Batches, Broadcasts, Connect, Corrections, Events, Jobs, Languages, MeApi, Meta, Recordings, Rtmp, Search, ShareLinks, Transcriptions, Voices, } from './resources.js';
26
+ export class VocalStack {
27
+ baseUrl;
28
+ realtime;
29
+ broadcasts;
30
+ transcriptions;
31
+ batches;
32
+ recordings;
33
+ jobs;
34
+ corrections;
35
+ shareLinks;
36
+ voices;
37
+ search;
38
+ me;
39
+ connect;
40
+ billing;
41
+ languages;
42
+ rtmp;
43
+ events;
44
+ meta;
45
+ constructor(opts = {}) {
46
+ const t = new Transport(opts);
47
+ this.baseUrl = t.baseUrl;
48
+ this.realtime = new Realtime(t);
49
+ this.broadcasts = new Broadcasts(t);
50
+ this.transcriptions = new Transcriptions(t);
51
+ this.batches = new Batches(t);
52
+ this.recordings = new Recordings(t);
53
+ this.jobs = new Jobs(t);
54
+ this.corrections = new Corrections(t);
55
+ this.shareLinks = new ShareLinks(t);
56
+ this.voices = new Voices(t);
57
+ this.search = new Search(t);
58
+ this.me = new MeApi(t);
59
+ this.connect = new Connect(t);
60
+ this.billing = new Billing(t);
61
+ this.languages = new Languages(t);
62
+ this.rtmp = new Rtmp(t);
63
+ this.events = new Events(t);
64
+ this.meta = new Meta(t);
7
65
  }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./apis/index"), exports);
18
- __exportStar(require("./generics.zod"), exports);
66
+ }
package/dist/mic.d.ts ADDED
@@ -0,0 +1,75 @@
1
+ import type { RealtimeSession } from './realtime.js';
2
+ /** Why capture stopped producing audio (see `MicCaptureOptions.onStall`). */
3
+ export type MicStallReason =
4
+ /** The input track ended: device unplugged, headset switched, permission revoked. */
5
+ 'track_ended'
6
+ /** The track is muted by the OS/hardware (no samples until it unmutes). */
7
+ | 'track_muted'
8
+ /** The AudioContext was suspended (phone call, screen lock, route change). */
9
+ | 'context_suspended'
10
+ /** The AudioContext was closed underneath us. */
11
+ | 'context_closed'
12
+ /** Seconds of exact-zero samples: a silently denied or hardware-muted mic. */
13
+ | 'silent';
14
+ export interface MicCaptureOptions {
15
+ chunkSeconds?: number;
16
+ sampleRate?: number;
17
+ audio?: MediaTrackConstraints;
18
+ /**
19
+ * Called every ~0.5s with (pcm, wall): cumulative output samples
20
+ * produced and seconds since capture start. Wire it to
21
+ * `session.sendClock` so the server can detect capture loss (a
22
+ * suspended context or dead track produces no samples while wall
23
+ * time advances) and mark the transcript with honest gaps. Driven
24
+ * by a timer, not by sample arrival, so it keeps reporting while
25
+ * capture is stalled — that is the case it exists for.
26
+ */
27
+ onClock?: (pcm: number, wall: number) => void;
28
+ /**
29
+ * Capture stopped producing audio. Recovery is attempted
30
+ * automatically (context resume, track re-acquire); `onRecover`
31
+ * fires when samples flow again. Use it to show a "microphone
32
+ * lost" state instead of "Listening".
33
+ */
34
+ onStall?: (reason: MicStallReason) => void;
35
+ /** Audio is flowing again after a stall. */
36
+ onRecover?: () => void;
37
+ /**
38
+ * Exact-zero run length (seconds) that flags a silently denied or
39
+ * hardware-muted mic (real mics always carry a noise floor). Default
40
+ * 3; 0 disables.
41
+ */
42
+ silentSeconds?: number;
43
+ }
44
+ /** The input device the capture is reading, as the browser reports it. */
45
+ export interface MicDevice {
46
+ /** The device's name, e.g. "MacBook Pro Microphone" ("" before permission is granted). */
47
+ label: string;
48
+ /** Its native rate (the capture resamples to `sampleRate`). */
49
+ sampleRate?: number;
50
+ channelCount?: number;
51
+ noiseSuppression?: boolean;
52
+ echoCancellation?: boolean;
53
+ }
54
+ export interface MicCapture {
55
+ stop(): Promise<void>;
56
+ readonly sampleRate: number;
57
+ /** The microphone being read now (a re-acquired track may be another one). */
58
+ readonly device: MicDevice;
59
+ /** Hold the capture open but stop sending what the microphone hears.
60
+ *
61
+ * A side conversation should not have to end the session, and pausing
62
+ * is not the same thing: pausing closes the stream and the listeners
63
+ * lose it. Muting keeps every listener connected and the recording
64
+ * running, and simply says nothing.
65
+ *
66
+ * Muted chunks are emitted as SILENCE rather than dropped. The server
67
+ * measures media time by the audio it receives, so dropping would
68
+ * compress the timeline and every later timestamp would be wrong;
69
+ * silence keeps the clock honest and transcribes to nothing, which is
70
+ * exactly what happened. */
71
+ setMuted(muted: boolean): void;
72
+ readonly muted: boolean;
73
+ }
74
+ export declare function captureMicrophone(onChunk: (pcm: Float32Array) => void, opts?: MicCaptureOptions): Promise<MicCapture>;
75
+ export declare function streamMicrophone(session: RealtimeSession, opts?: MicCaptureOptions): Promise<MicCapture>;