@moxxy/plugin-stt-whisper 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Moxxy (moxxy.ai)
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.
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Audio helpers shared across STT plugins. Whisper-family backends — the
3
+ * generic OpenAI Whisper endpoint, the Codex OAuth `/transcribe` proxy,
4
+ * any future Whisper-compatible vendor — all want the same preprocessing
5
+ * (filename inference from MIME, raw PCM16 → WAV wrapping). Centralizing
6
+ * them here keeps the wrapper plugins thin.
7
+ */
8
+ import { MOXXY_PCM16_24KHZ_MIME } from '@moxxy/sdk';
9
+ /** A custom MIME tag used by the TUI voice recorder to flag raw PCM16
10
+ * mono @ 24kHz bytes (ffmpeg's `-f s16le` output) so the transcriber
11
+ * knows to wrap them in a WAV header before upload. Public so other
12
+ * recorders / channels can mark their captures identically. Sourced from
13
+ * the SDK's zero-dep cross-package source of truth; re-exported here so the
14
+ * existing `@moxxy/plugin-stt-whisper` surface stays stable. */
15
+ export { MOXXY_PCM16_24KHZ_MIME };
16
+ /** Default filename per MIME type, used to set the upload `filename` so
17
+ * the vendor's content sniffer routes to the right decoder. Defaults to
18
+ * `audio.bin` for anything unmapped.
19
+ *
20
+ * The MIME string is caller-supplied and can be fully untrusted (the
21
+ * Telegram voice path forwards `media.mime_type` verbatim). A plain object
22
+ * literal would let a crafted MIME equal to an inherited member name
23
+ * ('constructor', 'toString', '__proto__', …) resolve to a function/object
24
+ * instead of `undefined`, defeating the `?? 'audio.bin'` fallback. Using a
25
+ * null-prototype map closes that hole; lookups can only hit own keys. */
26
+ export declare const WHISPER_FILENAME_BY_MIME: Readonly<Record<string, string>>;
27
+ export interface NormalizedAudioUpload {
28
+ readonly bytes: Uint8Array;
29
+ readonly mimeType: string;
30
+ readonly filename: string;
31
+ }
32
+ /**
33
+ * Normalize a raw audio buffer for upload to a Whisper-family endpoint.
34
+ *
35
+ * - If the input is `MOXXY_PCM16_24KHZ_MIME`, wraps the raw samples in a
36
+ * WAV header (samples are mono, 24kHz, 16-bit little-endian) and
37
+ * reports the upload as `audio/wav`.
38
+ * - Otherwise passes the bytes through and picks a filename from the
39
+ * MIME table (or `audio.bin` for unknown types).
40
+ *
41
+ * The optional `filenamePrefix` lets callers brand the upload filename
42
+ * (e.g. Codex uses `moxxy.wav`); when omitted the default `audio.<ext>`
43
+ * shape is used.
44
+ */
45
+ export declare function normalizeWhisperUpload(audio: Uint8Array | ArrayBuffer, mimeType: string | undefined, filenamePrefix?: string): NormalizedAudioUpload;
46
+ /**
47
+ * Wrap raw PCM16 mono samples in a WAV container. Used by recorders that
48
+ * stream `s16le` (ffmpeg's default raw output) so the bytes can be sent
49
+ * to endpoints that only accept container formats.
50
+ */
51
+ export declare function pcm16MonoToWav(pcm: Uint8Array | ArrayBuffer, sampleRate?: number): Uint8Array;
52
+ //# sourceMappingURL=audio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio.d.ts","sourceRoot":"","sources":["../src/audio.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEpD;;;;;iEAKiE;AACjE,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAElC;;;;;;;;;0EAS0E;AAC1E,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAYlE,CAAC;AAEL,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,UAAU,GAAG,WAAW,EAC/B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,cAAc,CAAC,EAAE,MAAM,GACtB,qBAAqB,CA8BvB;AAWD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,WAAW,EAAE,UAAU,SAAS,GAAG,UAAU,CAgC7F"}
package/dist/audio.js ADDED
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Audio helpers shared across STT plugins. Whisper-family backends — the
3
+ * generic OpenAI Whisper endpoint, the Codex OAuth `/transcribe` proxy,
4
+ * any future Whisper-compatible vendor — all want the same preprocessing
5
+ * (filename inference from MIME, raw PCM16 → WAV wrapping). Centralizing
6
+ * them here keeps the wrapper plugins thin.
7
+ */
8
+ import { MOXXY_PCM16_24KHZ_MIME } from '@moxxy/sdk';
9
+ /** A custom MIME tag used by the TUI voice recorder to flag raw PCM16
10
+ * mono @ 24kHz bytes (ffmpeg's `-f s16le` output) so the transcriber
11
+ * knows to wrap them in a WAV header before upload. Public so other
12
+ * recorders / channels can mark their captures identically. Sourced from
13
+ * the SDK's zero-dep cross-package source of truth; re-exported here so the
14
+ * existing `@moxxy/plugin-stt-whisper` surface stays stable. */
15
+ export { MOXXY_PCM16_24KHZ_MIME };
16
+ /** Default filename per MIME type, used to set the upload `filename` so
17
+ * the vendor's content sniffer routes to the right decoder. Defaults to
18
+ * `audio.bin` for anything unmapped.
19
+ *
20
+ * The MIME string is caller-supplied and can be fully untrusted (the
21
+ * Telegram voice path forwards `media.mime_type` verbatim). A plain object
22
+ * literal would let a crafted MIME equal to an inherited member name
23
+ * ('constructor', 'toString', '__proto__', …) resolve to a function/object
24
+ * instead of `undefined`, defeating the `?? 'audio.bin'` fallback. Using a
25
+ * null-prototype map closes that hole; lookups can only hit own keys. */
26
+ export const WHISPER_FILENAME_BY_MIME =
27
+ /* null-prototype: see comment above */ Object.assign(Object.create(null), {
28
+ 'audio/ogg': 'audio.ogg',
29
+ 'audio/opus': 'audio.opus',
30
+ 'audio/mpeg': 'audio.mp3',
31
+ 'audio/mp3': 'audio.mp3',
32
+ 'audio/wav': 'audio.wav',
33
+ 'audio/x-wav': 'audio.wav',
34
+ 'audio/webm': 'audio.webm',
35
+ 'audio/m4a': 'audio.m4a',
36
+ 'audio/mp4': 'audio.mp4',
37
+ 'audio/flac': 'audio.flac',
38
+ });
39
+ /**
40
+ * Normalize a raw audio buffer for upload to a Whisper-family endpoint.
41
+ *
42
+ * - If the input is `MOXXY_PCM16_24KHZ_MIME`, wraps the raw samples in a
43
+ * WAV header (samples are mono, 24kHz, 16-bit little-endian) and
44
+ * reports the upload as `audio/wav`.
45
+ * - Otherwise passes the bytes through and picks a filename from the
46
+ * MIME table (or `audio.bin` for unknown types).
47
+ *
48
+ * The optional `filenamePrefix` lets callers brand the upload filename
49
+ * (e.g. Codex uses `moxxy.wav`); when omitted the default `audio.<ext>`
50
+ * shape is used.
51
+ */
52
+ export function normalizeWhisperUpload(audio, mimeType, filenamePrefix) {
53
+ const bytes = audio instanceof Uint8Array ? audio : new Uint8Array(audio);
54
+ // MIME types are case-insensitive (RFC 2045) and may carry a `; codecs=…`
55
+ // parameter; some callers (Telegram) forward the header verbatim without
56
+ // normalizing. Canonicalize once so casing/params don't miss the table.
57
+ //
58
+ // `mimeType` is *typed* `string | undefined`, but the value crosses a trust
59
+ // boundary unvalidated: the Telegram path forwards `media.mime_type` from raw
60
+ // update JSON, which a crafted message could make a number/object/array.
61
+ // Guard the runtime type so `.toLowerCase()` can't throw on hostile input —
62
+ // anything non-string degrades to the default rather than crashing the path.
63
+ const rawMime = typeof mimeType === 'string' ? mimeType : '';
64
+ const mt = (rawMime || 'audio/wav').toLowerCase().split(';')[0].trim();
65
+ if (mt === MOXXY_PCM16_24KHZ_MIME) {
66
+ return {
67
+ bytes: pcm16MonoToWav(bytes, 24_000),
68
+ mimeType: 'audio/wav',
69
+ filename: rename(filenamePrefix, 'wav') ?? 'audio.wav',
70
+ };
71
+ }
72
+ // Null-prototype map + string guard: an untrusted MIME can't surface an
73
+ // inherited member (function/object), so the upload filename is always a
74
+ // real string and `extOf` never receives a non-string.
75
+ const mapped = WHISPER_FILENAME_BY_MIME[mt];
76
+ const defaultName = typeof mapped === 'string' ? mapped : 'audio.bin';
77
+ return {
78
+ bytes,
79
+ mimeType: mt,
80
+ filename: rename(filenamePrefix, extOf(defaultName)) ?? defaultName,
81
+ };
82
+ }
83
+ function rename(prefix, ext) {
84
+ return prefix ? `${prefix}.${ext}` : undefined;
85
+ }
86
+ function extOf(filename) {
87
+ const dot = filename.lastIndexOf('.');
88
+ return dot >= 0 ? filename.slice(dot + 1) : 'bin';
89
+ }
90
+ /**
91
+ * Wrap raw PCM16 mono samples in a WAV container. Used by recorders that
92
+ * stream `s16le` (ffmpeg's default raw output) so the bytes can be sent
93
+ * to endpoints that only accept container formats.
94
+ */
95
+ export function pcm16MonoToWav(pcm, sampleRate = 24_000) {
96
+ const raw = pcm instanceof Uint8Array ? pcm : new Uint8Array(pcm);
97
+ // 16-bit samples are 2 bytes; a trailing odd byte is half a sample and
98
+ // breaks RIFF even-alignment, so drop it. `subarray` is a view, no copy.
99
+ const data = raw.byteLength & 1 ? raw.subarray(0, raw.byteLength & ~1) : raw;
100
+ // The RIFF/data chunk sizes are unsigned 32-bit; >~4GiB would silently
101
+ // wrap to a tiny size and emit a structurally corrupt WAV. Reject loudly.
102
+ if (data.byteLength > 0xffffffff - 36) {
103
+ throw new RangeError(`PCM payload too large for a WAV container (${data.byteLength} bytes; max ${0xffffffff - 36}).`);
104
+ }
105
+ const headerBytes = 44;
106
+ const wav = new Uint8Array(headerBytes + data.byteLength);
107
+ const view = new DataView(wav.buffer);
108
+ writeAscii(wav, 0, 'RIFF');
109
+ view.setUint32(4, 36 + data.byteLength, true);
110
+ writeAscii(wav, 8, 'WAVE');
111
+ writeAscii(wav, 12, 'fmt ');
112
+ view.setUint32(16, 16, true);
113
+ view.setUint16(20, 1, true);
114
+ view.setUint16(22, 1, true);
115
+ view.setUint32(24, sampleRate, true);
116
+ view.setUint32(28, sampleRate * 2, true);
117
+ view.setUint16(32, 2, true);
118
+ view.setUint16(34, 16, true);
119
+ writeAscii(wav, 36, 'data');
120
+ view.setUint32(40, data.byteLength, true);
121
+ wav.set(data, headerBytes);
122
+ return wav;
123
+ }
124
+ function writeAscii(target, offset, value) {
125
+ for (let i = 0; i < value.length; i += 1) {
126
+ target[offset + i] = value.charCodeAt(i);
127
+ }
128
+ }
129
+ //# sourceMappingURL=audio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio.js","sourceRoot":"","sources":["../src/audio.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { type Plugin } from '@moxxy/sdk';
2
+ import { type WhisperModel, type WhisperTranscriberOptions } from './whisper.js';
3
+ export { WhisperTranscriber, createWhisperTranscriber, type WhisperModel, type WhisperTranscriberOptions, } from './whisper.js';
4
+ export { MOXXY_PCM16_24KHZ_MIME, WHISPER_FILENAME_BY_MIME, normalizeWhisperUpload, pcm16MonoToWav, type NormalizedAudioUpload, } from './audio.js';
5
+ export interface BuildWhisperPluginOptions {
6
+ /**
7
+ * Default model used when the host calls `session.transcribers.setActive(name)`
8
+ * without an explicit config. Defaults to `whisper-1`.
9
+ */
10
+ readonly model?: WhisperModel;
11
+ /**
12
+ * Optional default config baked into the transcriber def. Callers can
13
+ * still override per-`setActive(name, config)`.
14
+ */
15
+ readonly defaults?: Omit<WhisperTranscriberOptions, 'client'>;
16
+ }
17
+ /**
18
+ * Build the @moxxy/plugin-stt-whisper plugin. Each instance registers
19
+ * exactly one transcriber, named `openai-<model>`.
20
+ *
21
+ * To run several Whisper-family models at once, build the plugin once per
22
+ * model — the transcriber names won't collide (`openai-whisper-1` vs
23
+ * `openai-gpt-4o-transcribe`), but each registration carries the SAME
24
+ * plugin name, so the host must register them under DISTINCT plugin names
25
+ * (the name-keyed plugin registry would otherwise clobber the first).
26
+ *
27
+ * Activation:
28
+ * session.transcribers.setActive('openai-whisper-1', { apiKey: ... });
29
+ *
30
+ * The plugin is intentionally side-effect free — registering it does
31
+ * NOT activate the transcriber. The host (CLI / config loader) chooses
32
+ * activation explicitly so users on local providers aren't surprised by
33
+ * outbound calls to OpenAI.
34
+ */
35
+ export declare function buildWhisperPlugin(opts?: BuildWhisperPluginOptions): Plugin;
36
+ declare const _default: Plugin;
37
+ export default _default;
38
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAsB,KAAK,YAAY,EAAE,KAAK,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAErG,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,KAAK,YAAY,EACjB,KAAK,yBAAyB,GAC/B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,EACtB,cAAc,EACd,KAAK,qBAAqB,GAC3B,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,yBAAyB,EAAE,QAAQ,CAAC,CAAC;CAC/D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,yBAA8B,GAAG,MAAM,CAqB/E;;AAGD,wBAAoC"}
package/dist/index.js ADDED
@@ -0,0 +1,47 @@
1
+ import { definePlugin, defineTranscriber } from '@moxxy/sdk';
2
+ import { WhisperTranscriber } from './whisper.js';
3
+ export { WhisperTranscriber, createWhisperTranscriber, } from './whisper.js';
4
+ export { MOXXY_PCM16_24KHZ_MIME, WHISPER_FILENAME_BY_MIME, normalizeWhisperUpload, pcm16MonoToWav, } from './audio.js';
5
+ /**
6
+ * Build the @moxxy/plugin-stt-whisper plugin. Each instance registers
7
+ * exactly one transcriber, named `openai-<model>`.
8
+ *
9
+ * To run several Whisper-family models at once, build the plugin once per
10
+ * model — the transcriber names won't collide (`openai-whisper-1` vs
11
+ * `openai-gpt-4o-transcribe`), but each registration carries the SAME
12
+ * plugin name, so the host must register them under DISTINCT plugin names
13
+ * (the name-keyed plugin registry would otherwise clobber the first).
14
+ *
15
+ * Activation:
16
+ * session.transcribers.setActive('openai-whisper-1', { apiKey: ... });
17
+ *
18
+ * The plugin is intentionally side-effect free — registering it does
19
+ * NOT activate the transcriber. The host (CLI / config loader) chooses
20
+ * activation explicitly so users on local providers aren't surprised by
21
+ * outbound calls to OpenAI.
22
+ */
23
+ export function buildWhisperPlugin(opts = {}) {
24
+ const model = opts.model ?? 'whisper-1';
25
+ const name = `openai-${model}`;
26
+ return definePlugin({
27
+ name: '@moxxy/plugin-stt-whisper',
28
+ version: '0.0.0',
29
+ transcribers: [
30
+ defineTranscriber({
31
+ name,
32
+ displayName: `OpenAI ${model}`,
33
+ createClient: (config) => {
34
+ const merged = {
35
+ ...(opts.defaults ?? {}),
36
+ ...config,
37
+ model,
38
+ };
39
+ return new WhisperTranscriber(merged);
40
+ },
41
+ }),
42
+ ],
43
+ });
44
+ }
45
+ // Discovery entry: `createPluginLoader` requires a default Plugin export.
46
+ export default buildWhisperPlugin();
47
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAe,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAqD,MAAM,cAAc,CAAC;AAErG,OAAO,EACL,kBAAkB,EAClB,wBAAwB,GAGzB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,EACtB,cAAc,GAEf,MAAM,YAAY,CAAC;AAepB;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAkC,EAAE;IACrE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,WAAW,CAAC;IACxC,MAAM,IAAI,GAAG,UAAU,KAAK,EAAE,CAAC;IAC/B,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,2BAA2B;QACjC,OAAO,EAAE,OAAO;QAChB,YAAY,EAAE;YACZ,iBAAiB,CAAC;gBAChB,IAAI;gBACJ,WAAW,EAAE,UAAU,KAAK,EAAE;gBAC9B,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE;oBACvB,MAAM,MAAM,GAA8B;wBACxC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;wBACxB,GAAI,MAAoC;wBACxC,KAAK;qBACN,CAAC;oBACF,OAAO,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAC;gBACxC,CAAC;aACF,CAAC;SACH;KACF,CAAC,CAAC;AACL,CAAC;AAED,0EAA0E;AAC1E,eAAe,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,40 @@
1
+ import OpenAI from 'openai';
2
+ import { type Transcriber, type TranscribeOptions, type TranscriptionResult } from '@moxxy/sdk';
3
+ export type WhisperModel = 'whisper-1' | 'gpt-4o-transcribe' | 'gpt-4o-mini-transcribe';
4
+ export interface WhisperTranscriberOptions {
5
+ readonly apiKey?: string;
6
+ readonly baseURL?: string;
7
+ /** Defaults to `whisper-1`. */
8
+ readonly model?: WhisperModel;
9
+ /**
10
+ * Default language hint (BCP-47). Overridden per-call by
11
+ * `TranscribeOptions.language`. Omit to let Whisper auto-detect.
12
+ */
13
+ readonly language?: string;
14
+ /** Inject a pre-built OpenAI client (tests pass a stub here). */
15
+ readonly client?: OpenAI;
16
+ }
17
+ /**
18
+ * `Transcriber` backed by OpenAI's audio.transcriptions endpoint
19
+ * (Whisper-1 by default). Requests `verbose_json` so we can return
20
+ * `language`, `durationSec`, and per-segment text without an extra call.
21
+ *
22
+ * Audio bytes come in as `Uint8Array | ArrayBuffer`; we wrap them in a
23
+ * Node `File` for upload (Node 20.10+ provides File / Blob globals).
24
+ */
25
+ export declare class WhisperTranscriber implements Transcriber {
26
+ readonly name: string;
27
+ private readonly client;
28
+ private readonly model;
29
+ private readonly defaultLanguage;
30
+ constructor(opts?: WhisperTranscriberOptions);
31
+ transcribe(audio: Uint8Array | ArrayBuffer, opts?: TranscribeOptions): Promise<TranscriptionResult>;
32
+ /**
33
+ * Run an SDK transcription call, translating failures into structured
34
+ * `MoxxyError`s (network vs. HTTP status) to match the codex sibling.
35
+ * User aborts re-throw unchanged so cancellation isn't masked as an error.
36
+ */
37
+ private run;
38
+ }
39
+ export declare function createWhisperTranscriber(opts?: WhisperTranscriberOptions): WhisperTranscriber;
40
+ //# sourceMappingURL=whisper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whisper.d.ts","sourceRoot":"","sources":["../src/whisper.ts"],"names":[],"mappings":"AAAA,OAAO,MAA2D,MAAM,QAAQ,CAAC;AACjF,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACzB,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,mBAAmB,GAAG,wBAAwB,CAAC;AAKxF,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,+BAA+B;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,qBAAa,kBAAmB,YAAW,WAAW;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;gBAEzC,IAAI,GAAE,yBAA8B;IAY1C,UAAU,CACd,KAAK,EAAE,UAAU,GAAG,WAAW,EAC/B,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAAC,mBAAmB,CAAC;IAkE/B;;;;OAIG;YACW,GAAG;CA0BlB;AAqDD,wBAAgB,wBAAwB,CAAC,IAAI,GAAE,yBAA8B,GAAG,kBAAkB,CAEjG"}
@@ -0,0 +1,161 @@
1
+ import OpenAI, { APIError, APIConnectionError, APIUserAbortError } from 'openai';
2
+ import { classifyHttpStatus, classifyNetworkError, MoxxyError, } from '@moxxy/sdk';
3
+ import { normalizeWhisperUpload } from './audio.js';
4
+ /** Provider tag attached to classified errors for logs/debug context. */
5
+ const WHISPER_PROVIDER_ID = 'openai';
6
+ /**
7
+ * `Transcriber` backed by OpenAI's audio.transcriptions endpoint
8
+ * (Whisper-1 by default). Requests `verbose_json` so we can return
9
+ * `language`, `durationSec`, and per-segment text without an extra call.
10
+ *
11
+ * Audio bytes come in as `Uint8Array | ArrayBuffer`; we wrap them in a
12
+ * Node `File` for upload (Node 20.10+ provides File / Blob globals).
13
+ */
14
+ export class WhisperTranscriber {
15
+ name;
16
+ client;
17
+ model;
18
+ defaultLanguage;
19
+ constructor(opts = {}) {
20
+ this.model = opts.model ?? 'whisper-1';
21
+ this.name = `openai-${this.model}`;
22
+ this.defaultLanguage = opts.language;
23
+ this.client =
24
+ opts.client ??
25
+ new OpenAI({
26
+ apiKey: opts.apiKey ?? process.env.OPENAI_API_KEY,
27
+ ...(opts.baseURL ? { baseURL: opts.baseURL } : {}),
28
+ });
29
+ }
30
+ async transcribe(audio, opts = {}) {
31
+ // Route through the shared Whisper-family preprocessor so the project's
32
+ // raw-PCM16 contract (MOXXY_PCM16_24KHZ_MIME) gets WAV-wrapped and filename
33
+ // inference stays in lockstep with the Codex sibling. Default the MIME to
34
+ // `audio/ogg` to preserve prior behavior for callers that omit it.
35
+ const upload = normalizeWhisperUpload(audio, opts.mimeType ?? 'audio/ogg');
36
+ // Node 20.10+ exposes File globally; the OpenAI SDK accepts it as
37
+ // an `Uploadable`.
38
+ const file = new File([upload.bytes], upload.filename, { type: upload.mimeType });
39
+ const language = opts.language ?? this.defaultLanguage;
40
+ // verbose_json is only supported by whisper-1; the gpt-4o family
41
+ // returns plain JSON. Branch so callers get rich segments when
42
+ // available, and a graceful text-only result when not.
43
+ if (this.model === 'whisper-1') {
44
+ const response = await this.run(() => this.client.audio.transcriptions.create({
45
+ model: this.model,
46
+ file,
47
+ response_format: 'verbose_json',
48
+ ...(language ? { language } : {}),
49
+ ...(opts.prompt ? { prompt: opts.prompt } : {}),
50
+ }, { signal: opts.signal }));
51
+ // OpenAI verbose-json response: { text, language, duration, segments[] }
52
+ const r = requireTextResponse(response);
53
+ const result = { text: r.text };
54
+ if (typeof r.language === 'string')
55
+ result.language = r.language;
56
+ if (typeof r.duration === 'number')
57
+ result.durationSec = r.duration;
58
+ if (Array.isArray(r.segments)) {
59
+ // The vendor response is untrusted: a malformed/partial `segments`
60
+ // entry (`null`, a non-object, or one missing the numeric start/end /
61
+ // string text) would either crash the blind `.start` read or leak a
62
+ // value that violates the `TranscriptionSegment` type contract.
63
+ // Validate each element and drop the ones that don't conform rather
64
+ // than crashing or emitting `{ text: 123 }` downstream.
65
+ result.segments = sanitizeSegments(r.segments);
66
+ }
67
+ return result;
68
+ }
69
+ const response = await this.run(() => this.client.audio.transcriptions.create({
70
+ model: this.model,
71
+ file,
72
+ ...(language ? { language } : {}),
73
+ ...(opts.prompt ? { prompt: opts.prompt } : {}),
74
+ }, { signal: opts.signal }));
75
+ return { text: requireTextResponse(response).text };
76
+ }
77
+ /**
78
+ * Run an SDK transcription call, translating failures into structured
79
+ * `MoxxyError`s (network vs. HTTP status) to match the codex sibling.
80
+ * User aborts re-throw unchanged so cancellation isn't masked as an error.
81
+ */
82
+ async run(call) {
83
+ try {
84
+ return await call();
85
+ }
86
+ catch (err) {
87
+ // Intentional cancellation: propagate as-is so callers see the abort.
88
+ if (err instanceof APIUserAbortError)
89
+ throw err;
90
+ const ctx = { provider: WHISPER_PROVIDER_ID, url: this.client.baseURL };
91
+ if (err instanceof APIConnectionError) {
92
+ const network = classifyNetworkError(err.cause ?? err, ctx);
93
+ if (network)
94
+ throw network;
95
+ }
96
+ if (err instanceof APIError && typeof err.status === 'number') {
97
+ const classified = classifyHttpStatus(err.status, { ...ctx, body: err.message });
98
+ if (classified)
99
+ throw classified;
100
+ throw new MoxxyError({
101
+ code: 'PROVIDER_BAD_REQUEST',
102
+ message: `OpenAI transcription returned HTTP ${err.status}.`,
103
+ context: { ...ctx, status: err.status },
104
+ cause: err,
105
+ });
106
+ }
107
+ const network = classifyNetworkError(err, ctx);
108
+ if (network)
109
+ throw network;
110
+ throw err;
111
+ }
112
+ }
113
+ }
114
+ /**
115
+ * Validate that an OpenAI transcription response carries a string `text`
116
+ * field before we trust it. The SDK's union return type is wider than what
117
+ * we consume, so we narrow it here; a response missing `text` (or whose
118
+ * `text` isn't a string) is a real contract violation worth surfacing as a
119
+ * structured error rather than silently returning `{ text: undefined }`.
120
+ */
121
+ function requireTextResponse(response) {
122
+ if (!response ||
123
+ typeof response !== 'object' ||
124
+ typeof response.text !== 'string') {
125
+ throw new MoxxyError({
126
+ code: 'PROVIDER_UNKNOWN_RESPONSE',
127
+ message: 'OpenAI transcription response was missing a text field.',
128
+ context: { provider: WHISPER_PROVIDER_ID },
129
+ });
130
+ }
131
+ return response;
132
+ }
133
+ /**
134
+ * Defensively narrow an untrusted `segments` array from a vendor response into
135
+ * the well-typed `TranscriptionSegment[]` contract. Each element is validated
136
+ * to be an object carrying finite numeric `start`/`end` and a string `text`;
137
+ * non-conforming entries (`null`, primitives, NaN/Infinity bounds, missing
138
+ * fields) are dropped so a hostile/partial response degrades to fewer (or zero)
139
+ * segments instead of crashing the caller or leaking off-contract values.
140
+ */
141
+ function sanitizeSegments(raw) {
142
+ const out = [];
143
+ for (const seg of raw) {
144
+ if (!seg || typeof seg !== 'object')
145
+ continue;
146
+ const s = seg;
147
+ if (typeof s.start !== 'number' ||
148
+ typeof s.end !== 'number' ||
149
+ typeof s.text !== 'string' ||
150
+ !Number.isFinite(s.start) ||
151
+ !Number.isFinite(s.end)) {
152
+ continue;
153
+ }
154
+ out.push({ start: s.start, end: s.end, text: s.text });
155
+ }
156
+ return out;
157
+ }
158
+ export function createWhisperTranscriber(opts = {}) {
159
+ return new WhisperTranscriber(opts);
160
+ }
161
+ //# sourceMappingURL=whisper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whisper.js","sourceRoot":"","sources":["../src/whisper.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@moxxy/plugin-stt-whisper",
3
+ "version": "0.27.0",
4
+ "description": "OpenAI Whisper Transcriber for moxxy. Audio bytes → text + segments. Plugs into the session's TranscriberRegistry; channels with audio input (Telegram voice, HTTP multipart) consume it transparently.",
5
+ "keywords": [
6
+ "moxxy",
7
+ "agent",
8
+ "stt",
9
+ "whisper",
10
+ "transcriber"
11
+ ],
12
+ "homepage": "https://moxxy.ai",
13
+ "bugs": {
14
+ "url": "https://github.com/moxxy-ai/moxxy/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/moxxy-ai/moxxy.git",
19
+ "directory": "packages/plugin-stt-whisper"
20
+ },
21
+ "author": "Michal Makowski <michal.makowski97@gmail.com>",
22
+ "license": "MIT",
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "type": "module",
27
+ "main": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "src"
38
+ ],
39
+ "moxxy": {
40
+ "plugin": {
41
+ "entry": "./dist/index.js",
42
+ "kind": "transcriber"
43
+ }
44
+ },
45
+ "dependencies": {
46
+ "openai": "^4.80.0",
47
+ "@moxxy/sdk": "0.27.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^22.10.0",
51
+ "typescript": "^5.7.3",
52
+ "vitest": "^2.1.8",
53
+ "@moxxy/tsconfig": "0.0.0",
54
+ "@moxxy/vitest-preset": "0.0.0"
55
+ },
56
+ "scripts": {
57
+ "build": "tsc -p tsconfig.json",
58
+ "typecheck": "tsc -p tsconfig.json --noEmit",
59
+ "test": "vitest run",
60
+ "clean": "rm -rf dist .turbo"
61
+ }
62
+ }