@happyvertical/speech 0.101.3 → 0.102.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.
Files changed (63) hide show
  1. package/README.md +36 -0
  2. package/dist/adapters/local/audio.d.ts +11 -1
  3. package/dist/adapters/local/env.d.ts +6 -2
  4. package/dist/adapters/local/runtime.d.ts +8 -1
  5. package/dist/adapters/local/transcriber.d.ts +3 -3
  6. package/dist/adapters/local/types.d.ts +7 -1
  7. package/dist/adapters/local/worker.d.ts +13 -3
  8. package/dist/adapters/openai-compatible-transcriber.d.ts +1 -1
  9. package/dist/adapters/openai-compatible.d.ts +1 -1
  10. package/dist/adapters/openai-realtime.d.ts +13 -2
  11. package/dist/adapters/qwen3.d.ts +1 -1
  12. package/dist/adapters/studio-server.d.ts +1 -1
  13. package/dist/adapters/voxtral-realtime.d.ts +36 -2
  14. package/dist/browser.d.ts +8 -0
  15. package/dist/browser.d.ts.map +1 -0
  16. package/dist/browser.js +159 -0
  17. package/dist/browser.js.map +1 -0
  18. package/dist/chunks/{errors-CQBX3OPS.js → errors-Cs0vX-mV.js} +25 -2
  19. package/dist/chunks/errors-Cs0vX-mV.js.map +1 -0
  20. package/dist/chunks/{registry-C_5BakmM.js → registry-DFSF3WoM.js} +2 -2
  21. package/dist/chunks/{registry-C_5BakmM.js.map → registry-DFSF3WoM.js.map} +1 -1
  22. package/dist/chunks/wav-B0Djpj2r.js +232 -0
  23. package/dist/chunks/wav-B0Djpj2r.js.map +1 -0
  24. package/dist/chunks/{websocket-CR9uXAuS.js → websocket-YbAfY6YI.js} +2 -2
  25. package/dist/chunks/{websocket-CR9uXAuS.js.map → websocket-YbAfY6YI.js.map} +1 -1
  26. package/dist/conversation/openai-webrtc.d.ts +1 -1
  27. package/dist/conversation-server.d.ts +3 -2
  28. package/dist/conversation-server.js +2 -2
  29. package/dist/conversation.js +1 -1
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +35 -4
  33. package/dist/index.js.map +1 -1
  34. package/dist/local.d.ts +20 -2
  35. package/dist/local.js +2 -2
  36. package/dist/pcm.d.ts +8 -0
  37. package/dist/pcm.d.ts.map +1 -0
  38. package/dist/pcm.js +3 -0
  39. package/dist/shared/audio.d.ts +7 -1
  40. package/dist/shared/env.d.ts +8 -1
  41. package/dist/shared/errors.d.ts +18 -0
  42. package/dist/shared/errors.d.ts.map +1 -1
  43. package/dist/shared/factory.d.ts +2 -2
  44. package/dist/shared/http.d.ts +2 -2
  45. package/dist/shared/input-format.d.ts +20 -0
  46. package/dist/shared/input-format.d.ts.map +1 -0
  47. package/dist/shared/pcm-capture.d.ts +38 -0
  48. package/dist/shared/pcm-capture.d.ts.map +1 -0
  49. package/dist/shared/pcm.d.ts +6 -1
  50. package/dist/shared/realtime-auth.d.ts +10 -2
  51. package/dist/shared/realtime-session.d.ts +14 -5
  52. package/dist/shared/registry.d.ts +10 -2
  53. package/dist/shared/resample.d.ts +15 -0
  54. package/dist/shared/resample.d.ts.map +1 -0
  55. package/dist/shared/streaming-client-secret.d.ts +21 -3
  56. package/dist/shared/streaming-factory.d.ts +7 -3
  57. package/dist/shared/streaming-types.d.ts +10 -3
  58. package/dist/shared/turn-limit.d.ts +13 -1
  59. package/dist/shared/types.d.ts +4 -4
  60. package/dist/shared/wav.d.ts +52 -0
  61. package/dist/shared/wav.d.ts.map +1 -0
  62. package/package.json +9 -1
  63. package/dist/chunks/errors-CQBX3OPS.js.map +0 -1
package/dist/local.d.ts CHANGED
@@ -1,6 +1,24 @@
1
+ /**
2
+ * @happyvertical/speech/local
3
+ *
4
+ * On-device speech-to-text with transformers.js (`@huggingface/transformers`,
5
+ * an optional peer dependency loaded on first use). Importing this entry
6
+ * registers `type: 'local'` with `getTranscriber()` and `getSpeech()`.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { getTranscriber } from '@happyvertical/speech';
11
+ * import '@happyvertical/speech/local';
12
+ *
13
+ * const transcriber = await getTranscriber({ type: 'local' });
14
+ * const { text } = await transcriber.transcribe({ audio: wavBytes, mimeType: 'audio/wav' });
15
+ * ```
16
+ *
17
+ * @packageDocumentation
18
+ */
1
19
  import { LocalTranscriber } from './adapters/local/transcriber.js';
2
- import { LocalTranscriberOptions } from './adapters/local/types.js';
3
- import { SpeechFactoryContext } from './shared/factory.js';
20
+ import type { LocalTranscriberOptions } from './adapters/local/types.js';
21
+ import type { SpeechFactoryContext } from './shared/factory.js';
4
22
  export { decodeToPcm16k, LOCAL_SAMPLE_RATE, } from './adapters/local/audio.js';
5
23
  export { LOCAL_TRANSCRIBER_ENV_KEYS } from './adapters/local/env.js';
6
24
  export { TRANSFORMERS_INSTALL_HINT, TRANSFORMERS_PACKAGE, } from './adapters/local/runtime.js';
package/dist/local.js CHANGED
@@ -1,5 +1,5 @@
1
- import { i as SpeechProviderError, n as SpeechConfigurationError, r as SpeechError } from "./chunks/errors-CQBX3OPS.js";
2
- import { f as readEnv, h as reportSpeechUsage, o as defaultEnv, p as resolveTranscriberConfig, r as registerOptionalTranscriber, v as normalizeAudioInput, y as normalizeMimeType } from "./chunks/registry-C_5BakmM.js";
1
+ import { a as SpeechProviderError, i as SpeechError, r as SpeechConfigurationError } from "./chunks/errors-Cs0vX-mV.js";
2
+ import { f as readEnv, h as reportSpeechUsage, o as defaultEnv, p as resolveTranscriberConfig, r as registerOptionalTranscriber, v as normalizeAudioInput, y as normalizeMimeType } from "./chunks/registry-DFSF3WoM.js";
3
3
  //#region src/adapters/local/env.ts
4
4
  /**
5
5
  * Environment configuration for the `local` transcriber. Explicit options win,
package/dist/pcm.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `@happyvertical/speech/pcm`: pure PCM and WAV utilities for the realtime
3
+ * transcribers. No Node APIs, no DOM; safe in browsers, workers and Node.
4
+ */
5
+ export { WavFormatError, type WavFormatErrorReason } from './shared/errors.js';
6
+ export { MAX_RESAMPLE_OUTPUT_SAMPLES, resampleMono, } from './shared/resample.js';
7
+ export { encodeWavPcm16, float32ToPcm16, type ParsedWavPcm16, parseWavPcm16, pcm16ToFloat32, type WavExpectation, } from './shared/wav.js';
8
+ //# sourceMappingURL=pcm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pcm.d.ts","sourceRoot":"","sources":["../src/pcm.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EACL,2BAA2B,EAC3B,YAAY,GACb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,cAAc,EACd,cAAc,EACd,KAAK,cAAc,EACnB,aAAa,EACb,cAAc,EACd,KAAK,cAAc,GACpB,MAAM,iBAAiB,CAAC"}
package/dist/pcm.js ADDED
@@ -0,0 +1,3 @@
1
+ import { o as WavFormatError } from "./chunks/errors-Cs0vX-mV.js";
2
+ import { a as MAX_RESAMPLE_OUTPUT_SAMPLES, i as pcm16ToFloat32, n as float32ToPcm16, o as resampleMono, r as parseWavPcm16, t as encodeWavPcm16 } from "./chunks/wav-B0Djpj2r.js";
3
+ export { MAX_RESAMPLE_OUTPUT_SAMPLES, WavFormatError, encodeWavPcm16, float32ToPcm16, parseWavPcm16, pcm16ToFloat32, resampleMono };
@@ -1,4 +1,10 @@
1
- import { AudioInput, AudioSource } from './types.js';
1
+ /**
2
+ * Audio input normalisation shared by speech adapters: accepts the input
3
+ * shapes callers actually have (Blob, Buffer, Uint8Array, ArrayBuffer,
4
+ * ReadableStream), enforces byte limits while buffering, and derives a
5
+ * multipart filename whose extension matches the MIME type.
6
+ */
7
+ import type { AudioInput, AudioSource } from './types.js';
2
8
  /** OpenAI caps `/audio/transcriptions` uploads at 25 MB. */
3
9
  export declare const DEFAULT_MAX_AUDIO_BYTES: number;
4
10
  /**
@@ -1,4 +1,11 @@
1
- import { SpeechFetch } from './types.js';
1
+ /**
2
+ * Environment configuration for speech adapters.
3
+ *
4
+ * Transcriber settings resolve in this order (first wins):
5
+ * explicit options → factory context → `HAVE_SPEECH_TRANSCRIBER_*` →
6
+ * legacy `HAVE_SPEECH_STT_*` → gateway `STT_*` aliases.
7
+ */
8
+ import type { SpeechFetch } from './types.js';
2
9
  export interface SpeechEnv {
3
10
  [key: string]: string | undefined;
4
11
  }
@@ -21,4 +21,22 @@ export declare class SpeechProviderError extends SpeechError {
21
21
  cause?: unknown;
22
22
  });
23
23
  }
24
+ /** Why a WAV buffer was rejected by `parseWavPcm16`, or an encode input refused. */
25
+ export type WavFormatErrorReason = 'not_riff' | 'truncated' | 'malformed_chunk' | 'missing_fmt' | 'duplicate_fmt' | 'data_before_fmt' | 'missing_data' | 'empty_data' | 'unsupported_format' | 'unsupported_bits' | 'inconsistent_header' | 'misaligned_data' | 'rate_mismatch' | 'channel_mismatch' | 'invalid_argument';
26
+ /**
27
+ * Thrown by the PCM/WAV utilities (`@happyvertical/speech/pcm`) for input that
28
+ * is not exactly the 16-bit PCM WAV that was asked for, and for invalid
29
+ * arguments (sample rate, sizes). `reason` is stable and machine-readable.
30
+ */
31
+ export declare class WavFormatError extends SpeechError {
32
+ readonly reason: WavFormatErrorReason;
33
+ constructor(reason: WavFormatErrorReason, message: string);
34
+ }
35
+ /** Why a browser PCM capture could not start or produce audio. */
36
+ export type PcmCaptureErrorReason = 'unsupported' | 'setup_failed' | 'cancelled';
37
+ /** Thrown by `createPcmCapture` (`@happyvertical/speech/browser`). */
38
+ export declare class PcmCaptureError extends SpeechError {
39
+ readonly reason: PcmCaptureErrorReason;
40
+ constructor(reason: PcmCaptureErrorReason, message: string, cause?: unknown);
41
+ }
24
42
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/shared/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,WAAY,SAAQ,KAAK;IAG3B,IAAI,EAAE,MAAM;IACZ,OAAO,CAAC,EAAE,MAAM;gBAFvB,OAAO,EAAE,MAAM,EACR,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,YAAA;CAK1B;AAED,qBAAa,wBAAyB,SAAQ,WAAW;gBAC3C,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;CAI9C;AAED,qBAAa,yBAA0B,SAAQ,WAAW;gBAC5C,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAIvC;AAED,qBAAa,mBAAoB,SAAQ,WAAW;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;gBAG7B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QACP,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,KAAK,CAAC,EAAE,OAAO,CAAC;KACZ;CAWT"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/shared/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,WAAY,SAAQ,KAAK;IAG3B,IAAI,EAAE,MAAM;IACZ,OAAO,CAAC,EAAE,MAAM;gBAFvB,OAAO,EAAE,MAAM,EACR,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,YAAA;CAK1B;AAED,qBAAa,wBAAyB,SAAQ,WAAW;gBAC3C,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;CAI9C;AAED,qBAAa,yBAA0B,SAAQ,WAAW;gBAC5C,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAIvC;AAED,qBAAa,mBAAoB,SAAQ,WAAW;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;gBAG7B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QACP,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,KAAK,CAAC,EAAE,OAAO,CAAC;KACZ;CAWT;AAED,oFAAoF;AACpF,MAAM,MAAM,oBAAoB,GAC5B,UAAU,GACV,WAAW,GACX,iBAAiB,GACjB,aAAa,GACb,eAAe,GACf,iBAAiB,GACjB,cAAc,GACd,YAAY,GACZ,oBAAoB,GACpB,kBAAkB,GAClB,qBAAqB,GACrB,iBAAiB,GACjB,eAAe,GACf,kBAAkB,GAClB,kBAAkB,CAAC;AAEvB;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,WAAW;IAC7C,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;gBAE1B,MAAM,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM;CAK1D;AAED,kEAAkE;AAClE,MAAM,MAAM,qBAAqB,GAC7B,aAAa,GACb,cAAc,GACd,WAAW,CAAC;AAEhB,sEAAsE;AACtE,qBAAa,eAAgB,SAAQ,WAAW;IAC9C,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;gBAE3B,MAAM,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAQ5E"}
@@ -1,5 +1,5 @@
1
- import { SpeechEnv } from './env.js';
2
- import { GetSpeechOptions, GetSpeechSynthesizerOptions, GetTranscriberOptions, Speech, SpeechAdapterAvailability, SpeechFetch, SpeechSynthesizer, Transcriber } from './types.js';
1
+ import { type SpeechEnv } from './env.js';
2
+ import type { GetSpeechOptions, GetSpeechSynthesizerOptions, GetTranscriberOptions, Speech, SpeechAdapterAvailability, SpeechFetch, SpeechSynthesizer, Transcriber } from './types.js';
3
3
  export interface SpeechFactoryContext {
4
4
  env?: SpeechEnv;
5
5
  fetch?: SpeechFetch;
@@ -1,5 +1,5 @@
1
- import { SpeechRetryOptions } from './retry.js';
2
- import { HttpSpeechOptions, SpeechFetch, SynthesisRequest, SynthesizedSpeech, TranscriptResult, TranscriptSegment, WordTiming } from './types.js';
1
+ import { type SpeechRetryOptions } from './retry.js';
2
+ import type { HttpSpeechOptions, SpeechFetch, SynthesisRequest, SynthesizedSpeech, TranscriptResult, TranscriptSegment, WordTiming } from './types.js';
3
3
  /**
4
4
  * Returns `baseUrl` with a trailing `/` on its path so relative paths resolve
5
5
  * beneath it. The query string is preserved and any fragment is dropped.
@@ -0,0 +1,20 @@
1
+ import type { TranscriberType } from './types.js';
2
+ /** What audio a transcriber type accepts from a caller. */
3
+ export type TranscriberInputFormat = {
4
+ kind: 'compressed';
5
+ } | {
6
+ kind: 'pcm16';
7
+ sampleRate: number;
8
+ channels: 1;
9
+ };
10
+ /**
11
+ * Says what to record for a transcriber type. `pcm16` types reject compressed
12
+ * containers (webm, ogg, mp3) and need 16-bit little-endian mono PCM, raw or in
13
+ * a WAV, at exactly `sampleRate`; use `@happyvertical/speech/browser` or
14
+ * `/pcm` to produce it. `compressed` types decode containers themselves
15
+ * (HTTP APIs, and the on-device transcriber, which decodes with the platform).
16
+ * For `openai-realtime` this is the default format; G.711 is an explicit
17
+ * adapter option and not a capture format.
18
+ */
19
+ export declare function transcriberInputFormat(type: TranscriberType): TranscriberInputFormat;
20
+ //# sourceMappingURL=input-format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-format.d.ts","sourceRoot":"","sources":["../../src/shared/input-format.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,2DAA2D;AAC3D,MAAM,MAAM,sBAAsB,GAC9B;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,CAAC,CAAA;CAAE,CAAC;AAKvD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,eAAe,GACpB,sBAAsB,CAuBxB"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Browser PCM capture: `MediaStream` -> mono Float32 at a requested rate and a
3
+ * ready 16-bit PCM WAV, via `AudioContext` + `AudioWorklet`. The worklet code
4
+ * is inlined and loaded from a Blob URL, so no bundler configuration is
5
+ * needed. Nothing here touches Node APIs; the Web Audio globals are read when
6
+ * `createPcmCapture` is called, so importing this module is SSR-safe.
7
+ */
8
+ export interface PcmCaptureOptions {
9
+ /** Rate of the returned samples and WAV, in Hz (e.g. 16000 for Voxtral). */
10
+ sampleRate: number;
11
+ /** Audio beyond this duration is dropped, not stored. */
12
+ maxDurationMs: number;
13
+ /** Called once, when the cap is reached; the capture keeps running until stopped. */
14
+ onLimit?: () => void;
15
+ }
16
+ export interface PcmCaptureResult {
17
+ /** Mono samples at `sampleRate`, -1..1. */
18
+ samples: Float32Array;
19
+ /** The same audio as a 16-bit PCM mono WAV. */
20
+ wav: Uint8Array;
21
+ /** True when audio past `maxDurationMs` was dropped. */
22
+ truncated: boolean;
23
+ }
24
+ export interface PcmCapture {
25
+ /** Ends capture, tears everything down, and resolves with the audio. Idempotent. */
26
+ stop(): Promise<PcmCaptureResult>;
27
+ /** Discards the audio and tears everything down. Idempotent; a pending `stop()` rejects. */
28
+ cancel(): void;
29
+ }
30
+ /** Whether this runtime has the Web Audio pieces `createPcmCapture` needs. */
31
+ export declare function pcmCaptureSupported(): boolean;
32
+ /**
33
+ * Starts capturing `stream`. Throws `PcmCaptureError` (`unsupported`) when Web
34
+ * Audio is unavailable. The caller keeps ownership of the stream and its
35
+ * tracks: stop them yourself after `stop()` or `cancel()`.
36
+ */
37
+ export declare function createPcmCapture(stream: MediaStream, options: PcmCaptureOptions): PcmCapture;
38
+ //# sourceMappingURL=pcm-capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pcm-capture.d.ts","sourceRoot":"","sources":["../../src/shared/pcm-capture.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgCH,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,2CAA2C;IAC3C,OAAO,EAAE,YAAY,CAAC;IACtB,+CAA+C;IAC/C,GAAG,EAAE,UAAU,CAAC;IAChB,wDAAwD;IACxD,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,oFAAoF;IACpF,IAAI,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAClC,4FAA4F;IAC5F,MAAM,IAAI,IAAI,CAAC;CAChB;AAED,8EAA8E;AAC9E,wBAAgB,mBAAmB,IAAI,OAAO,CAQ7C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,iBAAiB,GACzB,UAAU,CA6IZ"}
@@ -1,4 +1,9 @@
1
- import { StreamingAudioChunk, StreamingAudioFormat } from './streaming-types.js';
1
+ /**
2
+ * Raw-audio helpers for streaming adapters: byte views, base64 encoding,
3
+ * duration from byte counts, and WAV/`audio/L16` unwrapping for the
4
+ * record-then-send wrapper. Nothing here decodes compressed audio or resamples.
5
+ */
6
+ import type { StreamingAudioChunk, StreamingAudioFormat } from './streaming-types.js';
2
7
  /** Returns a byte view over a chunk without copying. */
3
8
  export declare function chunkToBytes(chunk: StreamingAudioChunk, adapter?: string): Uint8Array;
4
9
  export declare function encodeBase64(bytes: Uint8Array): string;
@@ -1,5 +1,13 @@
1
- import { RealtimeConnection } from './realtime-session.js';
2
- import { StreamingClientSecret } from './streaming-types.js';
1
+ /**
2
+ * Credential handling shared by realtime (WebSocket) adapters.
3
+ *
4
+ * Node callers authenticate with a long-lived `apiKey` in the
5
+ * `Authorization` header. Browsers cannot set WebSocket headers and must never
6
+ * hold a long-lived key, so they pass a short-lived `clientSecret`, which is
7
+ * sent as a WebSocket subprotocol.
8
+ */
9
+ import type { RealtimeConnection } from './realtime-session.js';
10
+ import type { StreamingClientSecret } from './streaming-types.js';
3
11
  /** Subprotocol prefix that carries a client secret (OpenAI's convention). */
4
12
  export declare const CLIENT_SECRET_SUBPROTOCOL_PREFIX = "openai-insecure-api-key.";
5
13
  /** Throws when an adapter is given a long-lived `apiKey` in a browser. */
@@ -1,8 +1,17 @@
1
- import { StreamingAudioChunk, StreamingAudioFormat, StreamingSession, StreamingSessionEventName, StreamingSessionListener, StreamingSessionState, StreamingTurnDetection } from './streaming-types.js';
2
- import { TurnLimit } from './turn-limit.js';
3
- import { TranscriptResult } from './types.js';
4
- import { SpeechUsageCallback } from './usage.js';
5
- import { SpeechWebSocketFactory } from './websocket.js';
1
+ /**
2
+ * Protocol-agnostic realtime transcription session over a WebSocket.
3
+ *
4
+ * The session owns the socket lifecycle, write queue and backpressure,
5
+ * timeouts, turn bookkeeping, events, and the final `TranscriptResult`. A
6
+ * {@link RealtimeProtocol} supplies the wire format, so a sibling adapter
7
+ * (for example a vLLM-served Voxtral realtime endpoint) only has to map its
8
+ * own messages.
9
+ */
10
+ import type { StreamingAudioChunk, StreamingAudioFormat, StreamingSession, StreamingSessionEventName, StreamingSessionListener, StreamingSessionState, StreamingTurnDetection } from './streaming-types.js';
11
+ import { type TurnLimit } from './turn-limit.js';
12
+ import type { TranscriptResult } from './types.js';
13
+ import { type SpeechUsageCallback } from './usage.js';
14
+ import { type SpeechWebSocketFactory } from './websocket.js';
6
15
  export declare const DEFAULT_STREAMING_TIMEOUT_MS = 30000;
7
16
  export declare const DEFAULT_STREAMING_CONNECT_TIMEOUT_MS = 10000;
8
17
  export declare const DEFAULT_STREAMING_HIGH_WATER_MARK: number;
@@ -1,5 +1,13 @@
1
- import { SpeechFactoryContext } from './factory.js';
2
- import { GetTranscriberOptions, Transcriber, TranscriberType } from './types.js';
1
+ /**
2
+ * Registry for opt-in transcriber adapters that live behind subpath exports
3
+ * (e.g. `@happyvertical/speech/local`).
4
+ *
5
+ * The core entry never imports those adapters, so their heavy optional peer
6
+ * dependencies are never resolved by bundlers or loaded at runtime unless the
7
+ * caller imports the subpath, which registers its factory here.
8
+ */
9
+ import type { SpeechFactoryContext } from './factory.js';
10
+ import type { GetTranscriberOptions, Transcriber, TranscriberType } from './types.js';
3
11
  export type OptionalTranscriberFactory = (options: GetTranscriberOptions, context: SpeechFactoryContext) => Transcriber | Promise<Transcriber>;
4
12
  export declare function registerOptionalTranscriber(type: TranscriberType, factory: OptionalTranscriberFactory): void;
5
13
  export declare function getOptionalTranscriberFactory(type: string): OptionalTranscriberFactory | undefined;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Mono resampler: windowed-sinc (Hann, 16 zero crossings per side)
3
+ * interpolation. When downsampling, the kernel's cutoff is scaled to 95% of
4
+ * the target Nyquist frequency so content above it is attenuated before it can
5
+ * alias; when upsampling it is a plain band-limited interpolator. Edges treat
6
+ * missing samples as absent and renormalise the kernel, so DC gain stays 1.
7
+ * Quality is suited to speech recognition (passband flat to roughly 90% of
8
+ * Nyquist, stopband well below -40 dB), not mastering. Cost is
9
+ * O(output length x taps), taps ~ 32 x max(1, fromRate / toRate).
10
+ */
11
+ /** Largest output a single call may produce (samples). */
12
+ export declare const MAX_RESAMPLE_OUTPUT_SAMPLES: number;
13
+ /** Resamples mono float samples from `fromRate` to `toRate` (Hz). */
14
+ export declare function resampleMono(samples: Float32Array, fromRate: number, toRate: number): Float32Array;
15
+ //# sourceMappingURL=resample.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resample.d.ts","sourceRoot":"","sources":["../../src/shared/resample.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,0DAA0D;AAC1D,eAAO,MAAM,2BAA2B,QAAU,CAAC;AAWnD,qEAAqE;AACrE,wBAAgB,YAAY,CAC1B,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,YAAY,CA2Cd"}
@@ -1,6 +1,24 @@
1
- import { SpeechEnv } from './env.js';
2
- import { OpenAIRealtimeTranscriberOptions, StreamingAudioFormat, StreamingTranscriberType, StreamingTurnDetection } from './streaming-types.js';
3
- import { SpeechFetch } from './types.js';
1
+ /**
2
+ * Server-side minting of short-lived browser credentials for realtime
3
+ * transcription.
4
+ *
5
+ * A browser must never hold the long-lived provider key. Your server calls
6
+ * {@link createStreamingClientSecret} once per tenant session (after its own
7
+ * auth check), records the returned tenant/session attribution, and hands the
8
+ * browser only `value` and `expiresAt`. The browser passes `value` as the
9
+ * streaming adapter's `clientSecret`.
10
+ *
11
+ * - `openai-realtime`: `POST /v1/realtime/client_secrets` with
12
+ * `expires_after: { anchor: 'created_at', seconds }` and a
13
+ * `session.type: "transcription"` config, returning an `ek_…` secret
14
+ * (https://developers.openai.com/api/reference/resources/realtime/subresources/client_secrets/methods/create).
15
+ * - `voxtral-realtime`: vLLM has no ephemeral-token mechanism, so this throws
16
+ * `SpeechConfigurationError`. Browser Voxtral needs your own proxy or token
17
+ * gateway in front of vLLM.
18
+ */
19
+ import { type SpeechEnv } from './env.js';
20
+ import type { OpenAIRealtimeTranscriberOptions, StreamingAudioFormat, StreamingTranscriberType, StreamingTurnDetection } from './streaming-types.js';
21
+ import type { SpeechFetch } from './types.js';
4
22
  /** Default client-secret lifetime: long enough to connect, short if leaked. */
5
23
  export declare const DEFAULT_CLIENT_SECRET_TTL_SECONDS = 60;
6
24
  /** OpenAI's accepted `expires_after.seconds` range. */
@@ -1,6 +1,10 @@
1
- import { SpeechEnv } from './env.js';
2
- import { GetStreamingTranscriberOptions, StreamingTranscriber, StreamingTranscriberType, StreamingTurnDetection } from './streaming-types.js';
3
- import { Transcriber } from './types.js';
1
+ /**
2
+ * Streaming transcriber factory, environment configuration, and the
3
+ * record-then-send `Transcriber` wrapper.
4
+ */
5
+ import { type SpeechEnv } from './env.js';
6
+ import type { GetStreamingTranscriberOptions, StreamingTranscriber, StreamingTranscriberType, StreamingTurnDetection } from './streaming-types.js';
7
+ import type { Transcriber } from './types.js';
4
8
  /** Streaming adapters `getStreamingTranscriber()` can build. */
5
9
  export declare const STREAMING_TRANSCRIBER_TYPES: readonly StreamingTranscriberType[];
6
10
  /** Environment variable names read for each streaming setting, in priority order. */
@@ -1,6 +1,13 @@
1
- import { TranscriptResult } from './types.js';
2
- import { SpeechUsageCallback } from './usage.js';
3
- import { SpeechWebSocketConstructor, SpeechWebSocketFactory } from './websocket.js';
1
+ /**
2
+ * Streaming (realtime) speech-to-text contract.
3
+ *
4
+ * A {@link StreamingTranscriber} opens one {@link StreamingSession} per
5
+ * utterance or conversation. Callers push raw audio with `write()`, listen for
6
+ * `partial` and `final` events, and call `end()` for the whole transcript.
7
+ */
8
+ import type { TranscriptResult } from './types.js';
9
+ import type { SpeechUsageCallback } from './usage.js';
10
+ import type { SpeechWebSocketConstructor, SpeechWebSocketFactory } from './websocket.js';
4
11
  /** Streaming adapter identifiers. */
5
12
  export type StreamingTranscriberType = 'openai-realtime' | 'voxtral-realtime';
6
13
  /**
@@ -1,4 +1,16 @@
1
- import { StreamingAudioFormat, StreamingSessionSettings, StreamingTurnLimit } from './streaming-types.js';
1
+ /**
2
+ * Per-turn audio limits for realtime sessions.
3
+ *
4
+ * Some providers cap the audio of one turn rather than of a session: vLLM's
5
+ * realtime endpoint keeps a turn's audio and generated tokens in one model
6
+ * context (`max_model_len`) and clears it after each final commit. A
7
+ * {@link TurnSplitter} measures the audio of the current turn from byte counts
8
+ * and the negotiated format, and picks where to roll over to a new turn: at
9
+ * the first quiet stretch inside a window before the limit, or at the limit
10
+ * itself when no quiet stretch turns up. The silence check is a plain RMS
11
+ * energy test, not a voice-activity model.
12
+ */
13
+ import type { StreamingAudioFormat, StreamingSessionSettings, StreamingTurnLimit } from './streaming-types.js';
2
14
  /** Default per-turn audio cap for providers that need one (`voxtral-realtime`). */
3
15
  export declare const DEFAULT_MAX_TURN_SECONDS = 270;
4
16
  /** How long before the cap the rollover starts looking for silence. */
@@ -1,7 +1,7 @@
1
- import { LocalTranscriberSettings } from '../adapters/local/types.js';
2
- import { SpeechRetryOptions } from './retry.js';
3
- import { GetStreamingTranscriberOptions, StreamingTranscriberType } from './streaming-types.js';
4
- import { SpeechUsage, SpeechUsageCallback } from './usage.js';
1
+ import type { LocalTranscriberSettings } from '../adapters/local/types.js';
2
+ import type { SpeechRetryOptions } from './retry.js';
3
+ import type { GetStreamingTranscriberOptions, StreamingTranscriberType } from './streaming-types.js';
4
+ import type { SpeechUsage, SpeechUsageCallback } from './usage.js';
5
5
  export type { SpeechRetryOptions } from './retry.js';
6
6
  export type { SpeechOperation, SpeechUsage, SpeechUsageCallback, } from './usage.js';
7
7
  export type SpeechAdapterType = 'studio-server' | 'qwen3-tts' | 'openai-compatible' | 'local' | StreamingTranscriberType;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Pure, dependency-free 16-bit PCM WAV helpers. No Node APIs and no DOM: they
3
+ * run in the browser, in Node and in workers. Exposed as
4
+ * `@happyvertical/speech/pcm`.
5
+ *
6
+ * `parseWavPcm16` is strict because it reads untrusted bytes: every declared
7
+ * size is checked against the buffer before it is trusted, nothing is
8
+ * allocated from a declared (rather than present) size, and work is linear in
9
+ * the input length.
10
+ */
11
+ /**
12
+ * Converts float samples (-1..1) to 16-bit PCM (scale 32768, the inverse of
13
+ * `pcm16ToFloat32`, so Int16 round-trips exactly). Clamps; NaN becomes 0.
14
+ */
15
+ export declare function float32ToPcm16(samples: Float32Array): Int16Array;
16
+ /** Converts 16-bit PCM to float samples in -1..1 (divides by 32768). */
17
+ export declare function pcm16ToFloat32(samples: Int16Array): Float32Array;
18
+ /**
19
+ * Encodes mono samples as a canonical 44-byte-header 16-bit PCM WAV. Float
20
+ * input is clamped and converted; `Int16Array` is written as is.
21
+ */
22
+ export declare function encodeWavPcm16(samples: Float32Array | Int16Array, sampleRate: number): Uint8Array;
23
+ /** Constraints `parseWavPcm16` enforces on the declared format. */
24
+ export interface WavExpectation {
25
+ /** Required sample rate in Hz. */
26
+ sampleRate?: number;
27
+ /** Required channel count. */
28
+ channels?: number;
29
+ }
30
+ /** A validated 16-bit PCM WAV. */
31
+ export interface ParsedWavPcm16 {
32
+ sampleRate: number;
33
+ channels: number;
34
+ /** Frames (one sample per channel). */
35
+ frames: number;
36
+ durationMs: number;
37
+ /** Interleaved samples, decoded to native endianness (a copy). */
38
+ samples: Int16Array;
39
+ /** The little-endian PCM bytes, a view into the input. */
40
+ data: Uint8Array;
41
+ }
42
+ /**
43
+ * Strictly parses a 16-bit PCM WAV. Rejects (with `WavFormatError`) anything
44
+ * that is not a well-formed RIFF/WAVE with a `fmt ` chunk before its `data`
45
+ * chunk, format tag 1 (or WAVE_FORMAT_EXTENSIBLE with the PCM subtype), 16
46
+ * bits, a consistent header, and a declared data length that lies within the
47
+ * buffer, is non-zero and is a whole number of frames. Nothing is clamped or
48
+ * guessed. Every chunk inside the declared RIFF is bounds-checked, including
49
+ * those after `data`; bytes beyond the RIFF are ignored.
50
+ */
51
+ export declare function parseWavPcm16(bytes: Uint8Array, expect?: WavExpectation): ParsedWavPcm16;
52
+ //# sourceMappingURL=wav.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wav.d.ts","sourceRoot":"","sources":["../../src/shared/wav.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAcH;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,UAAU,CAShE;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,OAAO,EAAE,UAAU,GAAG,YAAY,CAMhE;AAsBD;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,YAAY,GAAG,UAAU,EAClC,UAAU,EAAE,MAAM,GACjB,UAAU,CA6BZ;AAED,mEAAmE;AACnE,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,kCAAkC;AAClC,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,OAAO,EAAE,UAAU,CAAC;IACpB,0DAA0D;IAC1D,IAAI,EAAE,UAAU,CAAC;CAClB;AAqGD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,UAAU,EACjB,MAAM,GAAE,cAAmB,GAC1B,cAAc,CAsGhB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/speech",
3
- "version": "0.101.3",
3
+ "version": "0.102.1",
4
4
  "description": "Speech provider abstraction for STT and TTS backends",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,6 +14,14 @@
14
14
  "import": "./dist/local.js",
15
15
  "types": "./dist/local.d.ts"
16
16
  },
17
+ "./pcm": {
18
+ "import": "./dist/pcm.js",
19
+ "types": "./dist/pcm.d.ts"
20
+ },
21
+ "./browser": {
22
+ "import": "./dist/browser.js",
23
+ "types": "./dist/browser.d.ts"
24
+ },
17
25
  "./conversation": {
18
26
  "import": "./dist/conversation.js",
19
27
  "types": "./dist/conversation.d.ts"
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors-CQBX3OPS.js","names":[],"sources":["../../src/shared/errors.ts"],"sourcesContent":["export class SpeechError extends Error {\n constructor(\n message: string,\n public code: string,\n public adapter?: string,\n ) {\n super(message);\n this.name = 'SpeechError';\n }\n}\n\nexport class SpeechConfigurationError extends SpeechError {\n constructor(message: string, adapter?: string) {\n super(message, 'SPEECH_CONFIGURATION_ERROR', adapter);\n this.name = 'SpeechConfigurationError';\n }\n}\n\nexport class InvalidSpeechAdapterError extends SpeechError {\n constructor(type: string, kind: string) {\n super(`Invalid ${kind} speech adapter type: ${type}`, 'INVALID_ADAPTER');\n this.name = 'InvalidSpeechAdapterError';\n }\n}\n\nexport class SpeechProviderError extends SpeechError {\n readonly status?: number;\n readonly responseBody?: string;\n /** Parsed `Retry-After` delay in milliseconds, when the provider sent one. */\n readonly retryAfterMs?: number;\n\n constructor(\n adapter: string,\n message: string,\n options: {\n status?: number;\n responseBody?: string;\n retryAfterMs?: number;\n cause?: unknown;\n } = {},\n ) {\n super(message, 'SPEECH_PROVIDER_ERROR', adapter);\n this.name = 'SpeechProviderError';\n this.status = options.status;\n this.responseBody = options.responseBody;\n this.retryAfterMs = options.retryAfterMs;\n if (options.cause !== undefined) {\n this.cause = options.cause;\n }\n }\n}\n"],"mappings":";AAAA,IAAa,cAAb,cAAiC,MAAM;CAG5B;CACA;CAHT,YACE,SACA,MACA,SACA;EACA,MAAM,OAAO;EAHN,KAAA,OAAA;EACA,KAAA,UAAA;EAGP,KAAK,OAAO;CACd;AACF;AAEA,IAAa,2BAAb,cAA8C,YAAY;CACxD,YAAY,SAAiB,SAAkB;EAC7C,MAAM,SAAS,8BAA8B,OAAO;EACpD,KAAK,OAAO;CACd;AACF;AAEA,IAAa,4BAAb,cAA+C,YAAY;CACzD,YAAY,MAAc,MAAc;EACtC,MAAM,WAAW,KAAK,wBAAwB,QAAQ,iBAAiB;EACvE,KAAK,OAAO;CACd;AACF;AAEA,IAAa,sBAAb,cAAyC,YAAY;CACnD;CACA;;CAEA;CAEA,YACE,SACA,SACA,UAKI,CAAC,GACL;EACA,MAAM,SAAS,yBAAyB,OAAO;EAC/C,KAAK,OAAO;EACZ,KAAK,SAAS,QAAQ;EACtB,KAAK,eAAe,QAAQ;EAC5B,KAAK,eAAe,QAAQ;EAC5B,IAAI,QAAQ,UAAU,KAAA,GACpB,KAAK,QAAQ,QAAQ;CAEzB;AACF"}