@soniox/tanstack-ai-adapter
Advanced tools
@@ -5,2 +5,23 @@ import { BaseTranscriptionAdapter, TranscriptionAdapterConfig } from '@tanstack/ai/adapters'; | ||
| /** | ||
| * Base error class for Soniox transcription errors | ||
| */ | ||
| export declare class SonioxTranscriptionError extends Error { | ||
| constructor(message: string); | ||
| } | ||
| /** | ||
| * Error thrown when a transcription job times out | ||
| */ | ||
| export declare class SonioxTranscriptionTimeoutError extends SonioxTranscriptionError { | ||
| constructor(message?: string); | ||
| } | ||
| /** | ||
| * Error thrown when the Soniox API returns an error response | ||
| */ | ||
| export declare class SonioxApiError extends SonioxTranscriptionError { | ||
| readonly status: number; | ||
| readonly statusText: string; | ||
| readonly body?: string | undefined; | ||
| constructor(status: number, statusText: string, body?: string | undefined); | ||
| } | ||
| /** | ||
| * Configuration for Soniox Transcription adapter | ||
@@ -10,2 +31,3 @@ */ | ||
| pollingIntervalMs?: number; | ||
| timeout?: number; | ||
| } | ||
@@ -23,5 +45,11 @@ /** Model type for Soniox Transcription */ | ||
| private readonly pollingIntervalMs; | ||
| private readonly timeout; | ||
| protected config: SonioxTranscriptionConfig; | ||
| constructor(config: SonioxTranscriptionConfig, model: TModel); | ||
| transcribe(options: TranscriptionOptions<SonioxTranscriptionProviderOptions>): Promise<TranscriptionResult>; | ||
| /** | ||
| * Extracts URL string if the audio input is a URL (object or string). | ||
| * Returns undefined for non-URL inputs. | ||
| */ | ||
| private extractAudioUrl; | ||
| private mapProviderOptions; | ||
@@ -37,2 +65,6 @@ private mapContext; | ||
| private buildHeaders; | ||
| /** | ||
| * Prepares audio input as a File for upload. | ||
| * Only called for non-URL inputs (URLs use audio_url parameter directly). | ||
| */ | ||
| private prepareAudioFile; | ||
@@ -39,0 +71,0 @@ private delay; |
| import { BaseTranscriptionAdapter } from "@tanstack/ai/adapters"; | ||
| import { generateId, DEFAULT_SONIOX_BASE_URL, getSonioxApiKeyFromEnv } from "../utils/client.js"; | ||
| class SonioxTranscriptionError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = "SonioxTranscriptionError"; | ||
| } | ||
| } | ||
| class SonioxTranscriptionTimeoutError extends SonioxTranscriptionError { | ||
| constructor(message = "Transcription job polling timed out") { | ||
| super(message); | ||
| this.name = "SonioxTranscriptionTimeoutError"; | ||
| } | ||
| } | ||
| class SonioxApiError extends SonioxTranscriptionError { | ||
| constructor(status, statusText, body) { | ||
| super(`Soniox API error (${status} ${statusText})${body ? `: ${body}` : ""}`); | ||
| this.status = status; | ||
| this.statusText = statusText; | ||
| this.body = body; | ||
| this.name = "SonioxApiError"; | ||
| } | ||
| } | ||
| class SonioxTranscriptionAdapter extends BaseTranscriptionAdapter { | ||
| name = "soniox"; | ||
| pollingIntervalMs = 1e3; | ||
| timeout = 5 * 60 * 1e3; | ||
| config; | ||
@@ -13,18 +35,21 @@ constructor(config, model) { | ||
| const { model, audio, language, modelOptions } = options; | ||
| const file = this.prepareAudioFile(audio); | ||
| const headers = this.buildHeaders(); | ||
| const audioUrl = this.extractAudioUrl(audio); | ||
| let fileId; | ||
| let transcriptionId; | ||
| try { | ||
| const formData = new FormData(); | ||
| formData.append("file", file); | ||
| const uploadResponse = await this.requestJson( | ||
| "/v1/files", | ||
| { | ||
| method: "POST", | ||
| headers, | ||
| body: formData | ||
| } | ||
| ); | ||
| fileId = uploadResponse.id; | ||
| if (!audioUrl) { | ||
| const file = this.prepareAudioFile(audio); | ||
| const formData = new FormData(); | ||
| formData.append("file", file); | ||
| const uploadResponse = await this.requestJson( | ||
| "/v1/files", | ||
| { | ||
| method: "POST", | ||
| headers, | ||
| body: formData | ||
| } | ||
| ); | ||
| fileId = uploadResponse.id; | ||
| } | ||
| const mapped = this.mapProviderOptions(modelOptions, language); | ||
@@ -41,3 +66,4 @@ const createResponse = await this.requestJson( | ||
| model, | ||
| file_id: fileId, | ||
| // Use audio_url if URL provided, otherwise use file_id | ||
| ...audioUrl ? { audio_url: audioUrl } : { file_id: fileId }, | ||
| language_hints: mapped.languageHints, | ||
@@ -95,2 +121,15 @@ language_hints_strict: mapped.languageHintsStrict, | ||
| } | ||
| /** | ||
| * Extracts URL string if the audio input is a URL (object or string). | ||
| * Returns undefined for non-URL inputs. | ||
| */ | ||
| extractAudioUrl(audio) { | ||
| if (audio instanceof URL) { | ||
| return audio.toString(); | ||
| } | ||
| if (typeof audio === "string" && (audio.startsWith("https://") || audio.startsWith("http://"))) { | ||
| return audio; | ||
| } | ||
| return void 0; | ||
| } | ||
| mapProviderOptions(options, language) { | ||
@@ -168,7 +207,7 @@ const languageHints = options?.languageHints ? [...options.languageHints] : []; | ||
| const start = Date.now(); | ||
| const timeoutMs = this.config.timeout ?? 3 * 60 * 1e3; | ||
| const timeoutMs = this.config.timeout ?? this.timeout; | ||
| const interval = this.config.pollingIntervalMs ?? this.pollingIntervalMs; | ||
| for (; ; ) { | ||
| if (Date.now() - start > timeoutMs) { | ||
| throw new Error("Transcription job polling timed out"); | ||
| throw new SonioxTranscriptionTimeoutError(); | ||
| } | ||
@@ -186,3 +225,3 @@ const statusResponse = await this.requestJson( | ||
| if (statusResponse.status === "error") { | ||
| throw new Error( | ||
| throw new SonioxTranscriptionError( | ||
| `Transcription failed: ${statusResponse.error_message ?? "Unknown error"}` | ||
@@ -199,5 +238,3 @@ ); | ||
| const text2 = await this.safeReadText(response); | ||
| throw new Error( | ||
| `Soniox API error (${response.status} ${response.statusText})${text2 ? `: ${text2}` : ""}` | ||
| ); | ||
| throw new SonioxApiError(response.status, response.statusText, text2); | ||
| } | ||
@@ -211,3 +248,3 @@ const text = await this.safeReadText(response); | ||
| } catch (error) { | ||
| throw new Error("Failed to parse Soniox response JSON"); | ||
| throw new SonioxTranscriptionError("Failed to parse Soniox response JSON"); | ||
| } | ||
@@ -242,2 +279,6 @@ } | ||
| } | ||
| /** | ||
| * Prepares audio input as a File for upload. | ||
| * Only called for non-URL inputs (URLs use audio_url parameter directly). | ||
| */ | ||
| prepareAudioFile(audio) { | ||
@@ -294,3 +335,6 @@ if (typeof File !== "undefined" && audio instanceof File) { | ||
| export { | ||
| SonioxApiError, | ||
| SonioxTranscriptionAdapter, | ||
| SonioxTranscriptionError, | ||
| SonioxTranscriptionTimeoutError, | ||
| createSonioxTranscription, | ||
@@ -297,0 +341,0 @@ sonioxTranscription |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"transcription.js","sources":["../../../src/adapters/transcription.ts"],"sourcesContent":["import { BaseTranscriptionAdapter } from '@tanstack/ai/adapters'\nimport {\n DEFAULT_SONIOX_BASE_URL,\n generateId,\n getSonioxApiKeyFromEnv,\n} from '../utils'\nimport type { SonioxTranscriptionProviderOptions } from '../audio/transcription-provider-options'\nimport type {\n TranscriptionOptions,\n TranscriptionResult,\n TranscriptionSegment,\n} from '@tanstack/ai'\nimport type { TranscriptionAdapterConfig } from '@tanstack/ai/adapters'\n\ninterface SonioxTranscriptToken {\n text: string\n start_ms?: number | null\n end_ms?: number | null\n confidence?: number | null\n speaker?: number | string | null\n language?: string | null\n translation_status?: string | null\n}\n\ninterface SonioxUploadResponse {\n id: string\n}\n\ninterface SonioxCreateTranscriptionResponse {\n id: string\n status?: string | null\n}\n\ninterface SonioxTranscriptionStatusResponse {\n id: string\n status: string\n audio_duration_ms?: number | null\n error_message?: string | null\n}\n\ninterface SonioxTranscriptResponse {\n id: string\n text?: string | null\n tokens?: Array<SonioxTranscriptToken> | null\n}\n\n/**\n * Configuration for Soniox Transcription adapter\n */\nexport interface SonioxTranscriptionConfig extends TranscriptionAdapterConfig {\n pollingIntervalMs?: number\n}\n\n/** Model type for Soniox Transcription */\nexport type SonioxTranscriptionModel = string\n\n/**\n * Soniox Transcription (Speech-to-Text) Adapter\n *\n * Tree-shakeable adapter for Soniox async transcription functionality.\n * Supports Soniox async transcription models.\n */\nexport class SonioxTranscriptionAdapter<\n TModel extends SonioxTranscriptionModel,\n> extends BaseTranscriptionAdapter<TModel, SonioxTranscriptionProviderOptions> {\n readonly name = 'soniox' as const\n\n private readonly pollingIntervalMs = 1000\n protected override config: SonioxTranscriptionConfig\n\n constructor(config: SonioxTranscriptionConfig, model: TModel) {\n super(config, model)\n this.config = config\n }\n\n async transcribe(\n options: TranscriptionOptions<SonioxTranscriptionProviderOptions>,\n ): Promise<TranscriptionResult> {\n const { model, audio, language, modelOptions } = options\n\n const file = this.prepareAudioFile(audio)\n const headers = this.buildHeaders()\n\n let fileId: string | undefined\n let transcriptionId: string | undefined\n\n try {\n const formData = new FormData()\n formData.append('file', file)\n\n const uploadResponse = await this.requestJson<SonioxUploadResponse>(\n '/v1/files',\n {\n method: 'POST',\n headers,\n body: formData,\n },\n )\n\n fileId = uploadResponse.id\n\n const mapped = this.mapProviderOptions(modelOptions, language)\n\n const createResponse =\n await this.requestJson<SonioxCreateTranscriptionResponse>(\n '/v1/transcriptions',\n {\n method: 'POST',\n headers: {\n ...headers,\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n model,\n file_id: fileId,\n language_hints: mapped.languageHints,\n language_hints_strict: mapped.languageHintsStrict,\n enable_language_identification:\n mapped.enableLanguageIdentification,\n enable_speaker_diarization: mapped.enableSpeakerDiarization,\n context: this.mapContext(mapped.context),\n client_reference_id: mapped.clientReferenceId,\n webhook_url: mapped.webhookUrl,\n webhook_auth_header_name: mapped.webhookAuthHeaderName,\n webhook_auth_header_value: mapped.webhookAuthHeaderValue,\n translation: this.mapTranslation(mapped.translation),\n }),\n },\n )\n\n transcriptionId = createResponse.id\n\n const statusResponse = await this.pollForCompletion(transcriptionId)\n\n const transcriptResponse =\n await this.requestJson<SonioxTranscriptResponse>(\n `/v1/transcriptions/${transcriptionId}/transcript`,\n {\n method: 'GET',\n headers,\n },\n )\n\n const tokens = transcriptResponse.tokens ?? []\n // Segments always include transcription tokens (exclude translation tokens)\n // Translation tokens are available via providerMetadata\n const segments = this.buildSegments(tokens)\n const detectedLanguage = this.getLanguageFromTokens(tokens)\n\n const result: TranscriptionResult = {\n id: generateId(this.name),\n model,\n text: transcriptResponse.text ?? '',\n language: detectedLanguage ?? language,\n duration:\n typeof statusResponse.audio_duration_ms === 'number'\n ? statusResponse.audio_duration_ms / 1000\n : undefined,\n segments: segments.length > 0 ? segments : undefined,\n }\n\n if (tokens.length > 0) {\n (result as any).providerMetadata = {\n soniox: {\n tokens,\n },\n }\n }\n\n return result\n } finally {\n await this.tryDeleteResource(\n transcriptionId ? `/v1/transcriptions/${transcriptionId}` : undefined,\n headers,\n )\n await this.tryDeleteResource(\n fileId ? `/v1/files/${fileId}` : undefined,\n headers,\n )\n }\n }\n\n private mapProviderOptions(\n options: SonioxTranscriptionProviderOptions | undefined,\n language: string | undefined,\n ): SonioxTranscriptionProviderOptions {\n const languageHints = options?.languageHints\n ? [...options.languageHints]\n : []\n\n if (language && !languageHints.includes(language)) {\n languageHints.push(language)\n }\n\n return {\n ...options,\n languageHints: languageHints.length > 0 ? languageHints : undefined,\n }\n }\n\n private mapContext(context: SonioxTranscriptionProviderOptions['context']) {\n if (!context) return undefined\n\n if (typeof context === 'string') {\n return context\n }\n\n return {\n general: context.general ?? undefined,\n text: context.text ?? undefined,\n terms: context.terms ?? undefined,\n translation_terms: context.translationTerms ?? undefined,\n }\n }\n\n private mapTranslation(\n translation: SonioxTranscriptionProviderOptions['translation'],\n ) {\n if (!translation) return undefined\n\n if (translation.type === 'one_way') {\n return {\n type: 'one_way',\n target_language: translation.targetLanguage,\n }\n }\n\n return {\n type: 'two_way',\n language_a: translation.languageA,\n language_b: translation.languageB,\n }\n }\n\n private buildSegments(\n tokens: Array<SonioxTranscriptToken>,\n ): Array<TranscriptionSegment> {\n return tokens\n .filter((token) => {\n // Must have timestamps\n if (\n typeof token.start_ms !== 'number' ||\n typeof token.end_ms !== 'number'\n ) {\n return false\n }\n // Always exclude translation tokens from segments\n // Translation tokens don't have timestamps and should be accessed via providerMetadata\n return (\n token.translation_status === undefined ||\n token.translation_status === null ||\n token.translation_status === 'original' ||\n token.translation_status === 'none'\n )\n })\n .map((token, index) => ({\n id: index,\n start: token.start_ms! / 1000,\n end: token.end_ms! / 1000,\n text: token.text,\n confidence: token.confidence ?? undefined,\n speaker:\n token.speaker === null || token.speaker === undefined\n ? undefined\n : String(token.speaker),\n }))\n }\n\n private getLanguageFromTokens(\n tokens: Array<SonioxTranscriptToken>,\n ): string | undefined {\n if (tokens.length === 0) return undefined\n\n const counts = new Map<string, number>()\n for (const token of tokens) {\n const lang = token.language ?? undefined\n if (!lang) continue\n counts.set(lang, (counts.get(lang) ?? 0) + 1)\n }\n\n let bestLanguage: string | undefined\n let bestCount = 0\n for (const [language, count] of counts) {\n if (count > bestCount) {\n bestLanguage = language\n bestCount = count\n }\n }\n\n return bestLanguage\n }\n\n private async pollForCompletion(\n transcriptionId: string,\n ): Promise<SonioxTranscriptionStatusResponse> {\n const headers = this.buildHeaders()\n const start = Date.now()\n const timeoutMs = this.config.timeout ?? 3 * 60 * 1000\n const interval = this.config.pollingIntervalMs ?? this.pollingIntervalMs\n\n for (;;) {\n if (Date.now() - start > timeoutMs) {\n throw new Error('Transcription job polling timed out')\n }\n\n const statusResponse =\n await this.requestJson<SonioxTranscriptionStatusResponse>(\n `/v1/transcriptions/${transcriptionId}`,\n {\n method: 'GET',\n headers,\n },\n )\n\n if (statusResponse.status === 'completed') {\n return statusResponse\n }\n\n if (statusResponse.status === 'error') {\n throw new Error(\n `Transcription failed: ${statusResponse.error_message ?? 'Unknown error'}`,\n )\n }\n\n await this.delay(interval)\n }\n }\n\n private async requestJson<T>(path: string, init: RequestInit): Promise<T> {\n const url = new URL(path, this.config.baseUrl ?? DEFAULT_SONIOX_BASE_URL)\n const response = await fetch(url, init)\n\n if (!response.ok) {\n const text = await this.safeReadText(response)\n throw new Error(\n `Soniox API error (${response.status} ${response.statusText})${\n text ? `: ${text}` : ''\n }`,\n )\n }\n\n const text = await this.safeReadText(response)\n if (!text) {\n return {} as T\n }\n\n try {\n return JSON.parse(text) as T\n } catch (error) {\n throw new Error('Failed to parse Soniox response JSON')\n }\n }\n\n private async safeReadText(response: Response): Promise<string> {\n try {\n return await response.text()\n } catch {\n return ''\n }\n }\n\n private async tryDeleteResource(\n path: string | undefined,\n headers: Record<string, string>,\n ): Promise<void> {\n if (!path) return\n\n try {\n await fetch(\n new URL(path, this.config.baseUrl ?? DEFAULT_SONIOX_BASE_URL),\n {\n method: 'DELETE',\n headers,\n },\n )\n } catch {\n // best effort cleanup\n }\n }\n\n private buildHeaders(): Record<string, string> {\n const apiKey = this.config.apiKey ?? getSonioxApiKeyFromEnv()\n return {\n authorization: `Bearer ${apiKey}`,\n ...this.config.headers,\n }\n }\n\n private prepareAudioFile(\n audio: string | File | Blob | ArrayBuffer | Uint8Array,\n ): File {\n if (typeof File !== 'undefined' && audio instanceof File) {\n return audio\n }\n\n if (typeof Blob !== 'undefined' && audio instanceof Blob) {\n const mimeType = audio.type || 'audio/mpeg'\n const extension = mimeType.split('/')[1] || 'mp3'\n return new File([audio], `audio.${extension}`, { type: mimeType })\n }\n\n if (audio instanceof ArrayBuffer) {\n return new File([new Uint8Array(audio)], 'audio.mp3', {\n type: 'audio/mpeg',\n })\n }\n\n if (audio instanceof Uint8Array) {\n return new File([audio], 'audio.mp3', { type: 'audio/mpeg' })\n }\n\n if (typeof audio === 'string') {\n if (audio.startsWith('data:')) {\n const [header, base64Data] = audio.split(',')\n const mimeMatch = header?.match(/data:([^;]+)/)\n const mimeType = mimeMatch?.[1] || 'audio/mpeg'\n const binaryStr = atob(base64Data || '')\n const bytes = new Uint8Array(binaryStr.length)\n for (let i = 0; i < binaryStr.length; i++) {\n bytes[i] = binaryStr.charCodeAt(i)\n }\n const extension = mimeType.split('/')[1] || 'mp3'\n return new File([bytes], `audio.${extension}`, { type: mimeType })\n }\n\n const binaryStr = atob(audio)\n const bytes = new Uint8Array(binaryStr.length)\n for (let i = 0; i < binaryStr.length; i++) {\n bytes[i] = binaryStr.charCodeAt(i)\n }\n return new File([bytes], 'audio.mp3', { type: 'audio/mpeg' })\n }\n\n throw new Error('Invalid audio input type')\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n }\n}\n\n/**\n * Creates a Soniox transcription adapter with explicit API key.\n */\nexport function createSonioxTranscription<\n TModel extends SonioxTranscriptionModel,\n>(\n model: TModel,\n apiKey: string,\n config?: Omit<SonioxTranscriptionConfig, 'apiKey'>,\n): SonioxTranscriptionAdapter<TModel> {\n return new SonioxTranscriptionAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Soniox transcription adapter using environment variables.\n */\nexport function sonioxTranscription<TModel extends SonioxTranscriptionModel>(\n model: TModel,\n config?: Omit<SonioxTranscriptionConfig, 'apiKey'>,\n): SonioxTranscriptionAdapter<TModel> {\n const apiKey = getSonioxApiKeyFromEnv()\n return new SonioxTranscriptionAdapter({ apiKey, ...config }, model)\n}\n"],"names":["text","binaryStr","bytes"],"mappings":";;AA8DO,MAAM,mCAEH,yBAAqE;AAAA,EACpE,OAAO;AAAA,EAEC,oBAAoB;AAAA,EAClB;AAAA,EAEnB,YAAY,QAAmC,OAAe;AAC5D,UAAM,QAAQ,KAAK;AACnB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,WACJ,SAC8B;AAC9B,UAAM,EAAE,OAAO,OAAO,UAAU,iBAAiB;AAEjD,UAAM,OAAO,KAAK,iBAAiB,KAAK;AACxC,UAAM,UAAU,KAAK,aAAA;AAErB,QAAI;AACJ,QAAI;AAEJ,QAAI;AACF,YAAM,WAAW,IAAI,SAAA;AACrB,eAAS,OAAO,QAAQ,IAAI;AAE5B,YAAM,iBAAiB,MAAM,KAAK;AAAA,QAChC;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR;AAAA,UACA,MAAM;AAAA,QAAA;AAAA,MACR;AAGF,eAAS,eAAe;AAExB,YAAM,SAAS,KAAK,mBAAmB,cAAc,QAAQ;AAE7D,YAAM,iBACJ,MAAM,KAAK;AAAA,QACT;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,GAAG;AAAA,YACH,gBAAgB;AAAA,UAAA;AAAA,UAElB,MAAM,KAAK,UAAU;AAAA,YACnB;AAAA,YACA,SAAS;AAAA,YACT,gBAAgB,OAAO;AAAA,YACvB,uBAAuB,OAAO;AAAA,YAC9B,gCACE,OAAO;AAAA,YACT,4BAA4B,OAAO;AAAA,YACnC,SAAS,KAAK,WAAW,OAAO,OAAO;AAAA,YACvC,qBAAqB,OAAO;AAAA,YAC5B,aAAa,OAAO;AAAA,YACpB,0BAA0B,OAAO;AAAA,YACjC,2BAA2B,OAAO;AAAA,YAClC,aAAa,KAAK,eAAe,OAAO,WAAW;AAAA,UAAA,CACpD;AAAA,QAAA;AAAA,MACH;AAGJ,wBAAkB,eAAe;AAEjC,YAAM,iBAAiB,MAAM,KAAK,kBAAkB,eAAe;AAEnE,YAAM,qBACJ,MAAM,KAAK;AAAA,QACT,sBAAsB,eAAe;AAAA,QACrC;AAAA,UACE,QAAQ;AAAA,UACR;AAAA,QAAA;AAAA,MACF;AAGJ,YAAM,SAAS,mBAAmB,UAAU,CAAA;AAG5C,YAAM,WAAW,KAAK,cAAc,MAAM;AAC1C,YAAM,mBAAmB,KAAK,sBAAsB,MAAM;AAE1D,YAAM,SAA8B;AAAA,QAClC,IAAI,WAAW,KAAK,IAAI;AAAA,QACxB;AAAA,QACA,MAAM,mBAAmB,QAAQ;AAAA,QACjC,UAAU,oBAAoB;AAAA,QAC9B,UACE,OAAO,eAAe,sBAAsB,WACxC,eAAe,oBAAoB,MACnC;AAAA,QACN,UAAU,SAAS,SAAS,IAAI,WAAW;AAAA,MAAA;AAG7C,UAAI,OAAO,SAAS,GAAG;AACpB,eAAe,mBAAmB;AAAA,UACjC,QAAQ;AAAA,YACN;AAAA,UAAA;AAAA,QACF;AAAA,MAEJ;AAEA,aAAO;AAAA,IACT,UAAA;AACE,YAAM,KAAK;AAAA,QACT,kBAAkB,sBAAsB,eAAe,KAAK;AAAA,QAC5D;AAAA,MAAA;AAEF,YAAM,KAAK;AAAA,QACT,SAAS,aAAa,MAAM,KAAK;AAAA,QACjC;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA,EAEQ,mBACN,SACA,UACoC;AACpC,UAAM,gBAAgB,SAAS,gBAC3B,CAAC,GAAG,QAAQ,aAAa,IACzB,CAAA;AAEJ,QAAI,YAAY,CAAC,cAAc,SAAS,QAAQ,GAAG;AACjD,oBAAc,KAAK,QAAQ;AAAA,IAC7B;AAEA,WAAO;AAAA,MACL,GAAG;AAAA,MACH,eAAe,cAAc,SAAS,IAAI,gBAAgB;AAAA,IAAA;AAAA,EAE9D;AAAA,EAEQ,WAAW,SAAwD;AACzE,QAAI,CAAC,QAAS,QAAO;AAErB,QAAI,OAAO,YAAY,UAAU;AAC/B,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL,SAAS,QAAQ,WAAW;AAAA,MAC5B,MAAM,QAAQ,QAAQ;AAAA,MACtB,OAAO,QAAQ,SAAS;AAAA,MACxB,mBAAmB,QAAQ,oBAAoB;AAAA,IAAA;AAAA,EAEnD;AAAA,EAEQ,eACN,aACA;AACA,QAAI,CAAC,YAAa,QAAO;AAEzB,QAAI,YAAY,SAAS,WAAW;AAClC,aAAO;AAAA,QACL,MAAM;AAAA,QACN,iBAAiB,YAAY;AAAA,MAAA;AAAA,IAEjC;AAEA,WAAO;AAAA,MACL,MAAM;AAAA,MACN,YAAY,YAAY;AAAA,MACxB,YAAY,YAAY;AAAA,IAAA;AAAA,EAE5B;AAAA,EAEQ,cACN,QAC6B;AAC7B,WAAO,OACJ,OAAO,CAAC,UAAU;AAEjB,UACE,OAAO,MAAM,aAAa,YAC1B,OAAO,MAAM,WAAW,UACxB;AACA,eAAO;AAAA,MACT;AAGA,aACE,MAAM,uBAAuB,UAC7B,MAAM,uBAAuB,QAC7B,MAAM,uBAAuB,cAC7B,MAAM,uBAAuB;AAAA,IAEjC,CAAC,EACA,IAAI,CAAC,OAAO,WAAW;AAAA,MACtB,IAAI;AAAA,MACJ,OAAO,MAAM,WAAY;AAAA,MACzB,KAAK,MAAM,SAAU;AAAA,MACrB,MAAM,MAAM;AAAA,MACZ,YAAY,MAAM,cAAc;AAAA,MAChC,SACE,MAAM,YAAY,QAAQ,MAAM,YAAY,SACxC,SACA,OAAO,MAAM,OAAO;AAAA,IAAA,EAC1B;AAAA,EACN;AAAA,EAEQ,sBACN,QACoB;AACpB,QAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,UAAM,6BAAa,IAAA;AACnB,eAAW,SAAS,QAAQ;AAC1B,YAAM,OAAO,MAAM,YAAY;AAC/B,UAAI,CAAC,KAAM;AACX,aAAO,IAAI,OAAO,OAAO,IAAI,IAAI,KAAK,KAAK,CAAC;AAAA,IAC9C;AAEA,QAAI;AACJ,QAAI,YAAY;AAChB,eAAW,CAAC,UAAU,KAAK,KAAK,QAAQ;AACtC,UAAI,QAAQ,WAAW;AACrB,uBAAe;AACf,oBAAY;AAAA,MACd;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,kBACZ,iBAC4C;AAC5C,UAAM,UAAU,KAAK,aAAA;AACrB,UAAM,QAAQ,KAAK,IAAA;AACnB,UAAM,YAAY,KAAK,OAAO,WAAW,IAAI,KAAK;AAClD,UAAM,WAAW,KAAK,OAAO,qBAAqB,KAAK;AAEvD,eAAS;AACP,UAAI,KAAK,QAAQ,QAAQ,WAAW;AAClC,cAAM,IAAI,MAAM,qCAAqC;AAAA,MACvD;AAEA,YAAM,iBACJ,MAAM,KAAK;AAAA,QACT,sBAAsB,eAAe;AAAA,QACrC;AAAA,UACE,QAAQ;AAAA,UACR;AAAA,QAAA;AAAA,MACF;AAGJ,UAAI,eAAe,WAAW,aAAa;AACzC,eAAO;AAAA,MACT;AAEA,UAAI,eAAe,WAAW,SAAS;AACrC,cAAM,IAAI;AAAA,UACR,yBAAyB,eAAe,iBAAiB,eAAe;AAAA,QAAA;AAAA,MAE5E;AAEA,YAAM,KAAK,MAAM,QAAQ;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,MAAc,YAAe,MAAc,MAA+B;AACxE,UAAM,MAAM,IAAI,IAAI,MAAM,KAAK,OAAO,WAAW,uBAAuB;AACxE,UAAM,WAAW,MAAM,MAAM,KAAK,IAAI;AAEtC,QAAI,CAAC,SAAS,IAAI;AAChB,YAAMA,QAAO,MAAM,KAAK,aAAa,QAAQ;AAC7C,YAAM,IAAI;AAAA,QACR,qBAAqB,SAAS,MAAM,IAAI,SAAS,UAAU,IACzDA,QAAO,KAAKA,KAAI,KAAK,EACvB;AAAA,MAAA;AAAA,IAEJ;AAEA,UAAM,OAAO,MAAM,KAAK,aAAa,QAAQ;AAC7C,QAAI,CAAC,MAAM;AACT,aAAO,CAAA;AAAA,IACT;AAEA,QAAI;AACF,aAAO,KAAK,MAAM,IAAI;AAAA,IACxB,SAAS,OAAO;AACd,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAAA,EACF;AAAA,EAEA,MAAc,aAAa,UAAqC;AAC9D,QAAI;AACF,aAAO,MAAM,SAAS,KAAA;AAAA,IACxB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAc,kBACZ,MACA,SACe;AACf,QAAI,CAAC,KAAM;AAEX,QAAI;AACF,YAAM;AAAA,QACJ,IAAI,IAAI,MAAM,KAAK,OAAO,WAAW,uBAAuB;AAAA,QAC5D;AAAA,UACE,QAAQ;AAAA,UACR;AAAA,QAAA;AAAA,MACF;AAAA,IAEJ,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,eAAuC;AAC7C,UAAM,SAAS,KAAK,OAAO,UAAU,uBAAA;AACrC,WAAO;AAAA,MACL,eAAe,UAAU,MAAM;AAAA,MAC/B,GAAG,KAAK,OAAO;AAAA,IAAA;AAAA,EAEnB;AAAA,EAEQ,iBACN,OACM;AACN,QAAI,OAAO,SAAS,eAAe,iBAAiB,MAAM;AACxD,aAAO;AAAA,IACT;AAEA,QAAI,OAAO,SAAS,eAAe,iBAAiB,MAAM;AACxD,YAAM,WAAW,MAAM,QAAQ;AAC/B,YAAM,YAAY,SAAS,MAAM,GAAG,EAAE,CAAC,KAAK;AAC5C,aAAO,IAAI,KAAK,CAAC,KAAK,GAAG,SAAS,SAAS,IAAI,EAAE,MAAM,UAAU;AAAA,IACnE;AAEA,QAAI,iBAAiB,aAAa;AAChC,aAAO,IAAI,KAAK,CAAC,IAAI,WAAW,KAAK,CAAC,GAAG,aAAa;AAAA,QACpD,MAAM;AAAA,MAAA,CACP;AAAA,IACH;AAEA,QAAI,iBAAiB,YAAY;AAC/B,aAAO,IAAI,KAAK,CAAC,KAAK,GAAG,aAAa,EAAE,MAAM,cAAc;AAAA,IAC9D;AAEA,QAAI,OAAO,UAAU,UAAU;AAC7B,UAAI,MAAM,WAAW,OAAO,GAAG;AAC7B,cAAM,CAAC,QAAQ,UAAU,IAAI,MAAM,MAAM,GAAG;AAC5C,cAAM,YAAY,QAAQ,MAAM,cAAc;AAC9C,cAAM,WAAW,YAAY,CAAC,KAAK;AACnC,cAAMC,aAAY,KAAK,cAAc,EAAE;AACvC,cAAMC,SAAQ,IAAI,WAAWD,WAAU,MAAM;AAC7C,iBAAS,IAAI,GAAG,IAAIA,WAAU,QAAQ,KAAK;AACzCC,iBAAM,CAAC,IAAID,WAAU,WAAW,CAAC;AAAA,QACnC;AACA,cAAM,YAAY,SAAS,MAAM,GAAG,EAAE,CAAC,KAAK;AAC5C,eAAO,IAAI,KAAK,CAACC,MAAK,GAAG,SAAS,SAAS,IAAI,EAAE,MAAM,UAAU;AAAA,MACnE;AAEA,YAAM,YAAY,KAAK,KAAK;AAC5B,YAAM,QAAQ,IAAI,WAAW,UAAU,MAAM;AAC7C,eAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,cAAM,CAAC,IAAI,UAAU,WAAW,CAAC;AAAA,MACnC;AACA,aAAO,IAAI,KAAK,CAAC,KAAK,GAAG,aAAa,EAAE,MAAM,cAAc;AAAA,IAC9D;AAEA,UAAM,IAAI,MAAM,0BAA0B;AAAA,EAC5C;AAAA,EAEQ,MAAM,IAA2B;AACvC,WAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,EACzD;AACF;AAKO,SAAS,0BAGd,OACA,QACA,QACoC;AACpC,SAAO,IAAI,2BAA2B,EAAE,QAAQ,GAAG,OAAA,GAAU,KAAK;AACpE;AAKO,SAAS,oBACd,OACA,QACoC;AACpC,QAAM,SAAS,uBAAA;AACf,SAAO,IAAI,2BAA2B,EAAE,QAAQ,GAAG,OAAA,GAAU,KAAK;AACpE;"} | ||
| {"version":3,"file":"transcription.js","sources":["../../../src/adapters/transcription.ts"],"sourcesContent":["import { BaseTranscriptionAdapter } from '@tanstack/ai/adapters'\nimport {\n DEFAULT_SONIOX_BASE_URL,\n generateId,\n getSonioxApiKeyFromEnv,\n} from '../utils'\nimport type { SonioxTranscriptionProviderOptions } from '../audio/transcription-provider-options'\nimport type {\n TranscriptionOptions,\n TranscriptionResult,\n TranscriptionSegment,\n} from '@tanstack/ai'\nimport type { TranscriptionAdapterConfig } from '@tanstack/ai/adapters'\n\n/**\n * Base error class for Soniox transcription errors\n */\nexport class SonioxTranscriptionError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'SonioxTranscriptionError'\n }\n}\n\n/**\n * Error thrown when a transcription job times out\n */\nexport class SonioxTranscriptionTimeoutError extends SonioxTranscriptionError {\n constructor(message = 'Transcription job polling timed out') {\n super(message)\n this.name = 'SonioxTranscriptionTimeoutError'\n }\n}\n\n/**\n * Error thrown when the Soniox API returns an error response\n */\nexport class SonioxApiError extends SonioxTranscriptionError {\n constructor(\n public readonly status: number,\n public readonly statusText: string,\n public readonly body?: string,\n ) {\n super(`Soniox API error (${status} ${statusText})${body ? `: ${body}` : ''}`)\n this.name = 'SonioxApiError'\n }\n}\n\ninterface SonioxTranscriptToken {\n text: string\n start_ms?: number | null\n end_ms?: number | null\n confidence?: number | null\n speaker?: number | string | null\n language?: string | null\n translation_status?: string | null\n}\n\ninterface SonioxUploadResponse {\n id: string\n}\n\ninterface SonioxCreateTranscriptionResponse {\n id: string\n status?: string | null\n}\n\ninterface SonioxTranscriptionStatusResponse {\n id: string\n status: string\n audio_duration_ms?: number | null\n error_message?: string | null\n}\n\ninterface SonioxTranscriptResponse {\n id: string\n text?: string | null\n tokens?: Array<SonioxTranscriptToken> | null\n}\n\n/**\n * Configuration for Soniox Transcription adapter\n */\nexport interface SonioxTranscriptionConfig extends TranscriptionAdapterConfig {\n pollingIntervalMs?: number\n timeout?: number\n}\n\n/** Model type for Soniox Transcription */\nexport type SonioxTranscriptionModel = string\n\n/**\n * Soniox Transcription (Speech-to-Text) Adapter\n *\n * Tree-shakeable adapter for Soniox async transcription functionality.\n * Supports Soniox async transcription models.\n */\nexport class SonioxTranscriptionAdapter<\n TModel extends SonioxTranscriptionModel,\n> extends BaseTranscriptionAdapter<TModel, SonioxTranscriptionProviderOptions> {\n readonly name = 'soniox' as const\n\n private readonly pollingIntervalMs = 1000\n private readonly timeout = 5 * 60 * 1000\n protected override config: SonioxTranscriptionConfig\n\n constructor(config: SonioxTranscriptionConfig, model: TModel) {\n super(config, model)\n this.config = config\n }\n\n async transcribe(\n options: TranscriptionOptions<SonioxTranscriptionProviderOptions>,\n ): Promise<TranscriptionResult> {\n const { model, audio, language, modelOptions } = options\n\n const headers = this.buildHeaders()\n const audioUrl = this.extractAudioUrl(audio)\n\n let fileId: string | undefined\n let transcriptionId: string | undefined\n\n try {\n // If not a URL, upload the file first\n if (!audioUrl) {\n const file = this.prepareAudioFile(audio)\n const formData = new FormData()\n formData.append('file', file)\n\n const uploadResponse = await this.requestJson<SonioxUploadResponse>(\n '/v1/files',\n {\n method: 'POST',\n headers,\n body: formData,\n },\n )\n\n fileId = uploadResponse.id\n }\n\n const mapped = this.mapProviderOptions(modelOptions, language)\n\n const createResponse =\n await this.requestJson<SonioxCreateTranscriptionResponse>(\n '/v1/transcriptions',\n {\n method: 'POST',\n headers: {\n ...headers,\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n model,\n // Use audio_url if URL provided, otherwise use file_id\n ...(audioUrl ? { audio_url: audioUrl } : { file_id: fileId }),\n language_hints: mapped.languageHints,\n language_hints_strict: mapped.languageHintsStrict,\n enable_language_identification:\n mapped.enableLanguageIdentification,\n enable_speaker_diarization: mapped.enableSpeakerDiarization,\n context: this.mapContext(mapped.context),\n client_reference_id: mapped.clientReferenceId,\n webhook_url: mapped.webhookUrl,\n webhook_auth_header_name: mapped.webhookAuthHeaderName,\n webhook_auth_header_value: mapped.webhookAuthHeaderValue,\n translation: this.mapTranslation(mapped.translation),\n }),\n },\n )\n\n transcriptionId = createResponse.id\n\n const statusResponse = await this.pollForCompletion(transcriptionId)\n\n const transcriptResponse =\n await this.requestJson<SonioxTranscriptResponse>(\n `/v1/transcriptions/${transcriptionId}/transcript`,\n {\n method: 'GET',\n headers,\n },\n )\n\n const tokens = transcriptResponse.tokens ?? []\n // Segments always include transcription tokens (exclude translation tokens)\n // Translation tokens are available via providerMetadata\n const segments = this.buildSegments(tokens)\n const detectedLanguage = this.getLanguageFromTokens(tokens)\n\n const result: TranscriptionResult = {\n id: generateId(this.name),\n model,\n text: transcriptResponse.text ?? '',\n language: detectedLanguage ?? language,\n duration:\n typeof statusResponse.audio_duration_ms === 'number'\n ? statusResponse.audio_duration_ms / 1000\n : undefined,\n segments: segments.length > 0 ? segments : undefined,\n }\n\n if (tokens.length > 0) {\n (result as any).providerMetadata = {\n soniox: {\n tokens,\n },\n }\n }\n\n return result\n } finally {\n // Always clean up transcription\n await this.tryDeleteResource(\n transcriptionId ? `/v1/transcriptions/${transcriptionId}` : undefined,\n headers,\n )\n // Clean up file if we uploaded one\n await this.tryDeleteResource(\n fileId ? `/v1/files/${fileId}` : undefined,\n headers,\n )\n }\n }\n\n /**\n * Extracts URL string if the audio input is a URL (object or string).\n * Returns undefined for non-URL inputs.\n */\n private extractAudioUrl(\n audio: string | URL | File | Blob | ArrayBuffer | Uint8Array,\n ): string | undefined {\n if (audio instanceof URL) {\n return audio.toString()\n }\n\n if (\n typeof audio === 'string' &&\n (audio.startsWith('https://') || audio.startsWith('http://'))\n ) {\n return audio\n }\n\n return undefined\n }\n\n private mapProviderOptions(\n options: SonioxTranscriptionProviderOptions | undefined,\n language: string | undefined,\n ): SonioxTranscriptionProviderOptions {\n const languageHints = options?.languageHints\n ? [...options.languageHints]\n : []\n\n if (language && !languageHints.includes(language)) {\n languageHints.push(language)\n }\n\n return {\n ...options,\n languageHints: languageHints.length > 0 ? languageHints : undefined,\n }\n }\n\n private mapContext(context: SonioxTranscriptionProviderOptions['context']) {\n if (!context) return undefined\n\n if (typeof context === 'string') {\n return context\n }\n\n return {\n general: context.general ?? undefined,\n text: context.text ?? undefined,\n terms: context.terms ?? undefined,\n translation_terms: context.translationTerms ?? undefined,\n }\n }\n\n private mapTranslation(\n translation: SonioxTranscriptionProviderOptions['translation'],\n ) {\n if (!translation) return undefined\n\n if (translation.type === 'one_way') {\n return {\n type: 'one_way',\n target_language: translation.targetLanguage,\n }\n }\n\n return {\n type: 'two_way',\n language_a: translation.languageA,\n language_b: translation.languageB,\n }\n }\n\n private buildSegments(\n tokens: Array<SonioxTranscriptToken>,\n ): Array<TranscriptionSegment> {\n return tokens\n .filter((token) => {\n // Must have timestamps\n if (\n typeof token.start_ms !== 'number' ||\n typeof token.end_ms !== 'number'\n ) {\n return false\n }\n // Always exclude translation tokens from segments\n // Translation tokens don't have timestamps and should be accessed via providerMetadata\n return (\n token.translation_status === undefined ||\n token.translation_status === null ||\n token.translation_status === 'original' ||\n token.translation_status === 'none'\n )\n })\n .map((token, index) => ({\n id: index,\n start: token.start_ms! / 1000,\n end: token.end_ms! / 1000,\n text: token.text,\n confidence: token.confidence ?? undefined,\n speaker:\n token.speaker === null || token.speaker === undefined\n ? undefined\n : String(token.speaker),\n }))\n }\n\n private getLanguageFromTokens(\n tokens: Array<SonioxTranscriptToken>,\n ): string | undefined {\n if (tokens.length === 0) return undefined\n\n const counts = new Map<string, number>()\n for (const token of tokens) {\n const lang = token.language ?? undefined\n if (!lang) continue\n counts.set(lang, (counts.get(lang) ?? 0) + 1)\n }\n\n let bestLanguage: string | undefined\n let bestCount = 0\n for (const [language, count] of counts) {\n if (count > bestCount) {\n bestLanguage = language\n bestCount = count\n }\n }\n\n return bestLanguage\n }\n\n private async pollForCompletion(\n transcriptionId: string,\n ): Promise<SonioxTranscriptionStatusResponse> {\n const headers = this.buildHeaders()\n const start = Date.now()\n const timeoutMs = this.config.timeout ?? this.timeout\n const interval = this.config.pollingIntervalMs ?? this.pollingIntervalMs\n\n for (;;) {\n if (Date.now() - start > timeoutMs) {\n throw new SonioxTranscriptionTimeoutError()\n }\n\n const statusResponse =\n await this.requestJson<SonioxTranscriptionStatusResponse>(\n `/v1/transcriptions/${transcriptionId}`,\n {\n method: 'GET',\n headers,\n },\n )\n\n if (statusResponse.status === 'completed') {\n return statusResponse\n }\n\n if (statusResponse.status === 'error') {\n throw new SonioxTranscriptionError(\n `Transcription failed: ${statusResponse.error_message ?? 'Unknown error'}`,\n )\n }\n\n await this.delay(interval)\n }\n }\n\n private async requestJson<T>(path: string, init: RequestInit): Promise<T> {\n const url = new URL(path, this.config.baseUrl ?? DEFAULT_SONIOX_BASE_URL)\n const response = await fetch(url, init)\n\n if (!response.ok) {\n const text = await this.safeReadText(response)\n throw new SonioxApiError(response.status, response.statusText, text)\n }\n\n const text = await this.safeReadText(response)\n if (!text) {\n return {} as T\n }\n\n try {\n return JSON.parse(text) as T\n } catch (error) {\n throw new SonioxTranscriptionError('Failed to parse Soniox response JSON')\n }\n }\n\n private async safeReadText(response: Response): Promise<string> {\n try {\n return await response.text()\n } catch {\n return ''\n }\n }\n\n private async tryDeleteResource(\n path: string | undefined,\n headers: Record<string, string>,\n ): Promise<void> {\n if (!path) return\n\n try {\n await fetch(\n new URL(path, this.config.baseUrl ?? DEFAULT_SONIOX_BASE_URL),\n {\n method: 'DELETE',\n headers,\n },\n )\n } catch {\n // best effort cleanup\n }\n }\n\n private buildHeaders(): Record<string, string> {\n const apiKey = this.config.apiKey ?? getSonioxApiKeyFromEnv()\n return {\n authorization: `Bearer ${apiKey}`,\n ...this.config.headers,\n }\n }\n\n /**\n * Prepares audio input as a File for upload.\n * Only called for non-URL inputs (URLs use audio_url parameter directly).\n */\n private prepareAudioFile(\n audio: string | URL | File | Blob | ArrayBuffer | Uint8Array,\n ): File {\n if (typeof File !== 'undefined' && audio instanceof File) {\n return audio\n }\n\n if (typeof Blob !== 'undefined' && audio instanceof Blob) {\n const mimeType = audio.type || 'audio/mpeg'\n const extension = mimeType.split('/')[1] || 'mp3'\n return new File([audio], `audio.${extension}`, { type: mimeType })\n }\n\n if (audio instanceof ArrayBuffer) {\n return new File([new Uint8Array(audio)], 'audio.mp3', {\n type: 'audio/mpeg',\n })\n }\n\n if (audio instanceof Uint8Array) {\n return new File([audio], 'audio.mp3', { type: 'audio/mpeg' })\n }\n\n if (typeof audio === 'string') {\n if (audio.startsWith('data:')) {\n const [header, base64Data] = audio.split(',')\n const mimeMatch = header?.match(/data:([^;]+)/)\n const mimeType = mimeMatch?.[1] || 'audio/mpeg'\n const binaryStr = atob(base64Data || '')\n const bytes = new Uint8Array(binaryStr.length)\n for (let i = 0; i < binaryStr.length; i++) {\n bytes[i] = binaryStr.charCodeAt(i)\n }\n const extension = mimeType.split('/')[1] || 'mp3'\n return new File([bytes], `audio.${extension}`, { type: mimeType })\n }\n\n // Assume base64 string\n const binaryStr = atob(audio)\n const bytes = new Uint8Array(binaryStr.length)\n for (let i = 0; i < binaryStr.length; i++) {\n bytes[i] = binaryStr.charCodeAt(i)\n }\n return new File([bytes], 'audio.mp3', { type: 'audio/mpeg' })\n }\n\n throw new Error('Invalid audio input type')\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n }\n}\n\n/**\n * Creates a Soniox transcription adapter with explicit API key.\n */\nexport function createSonioxTranscription<\n TModel extends SonioxTranscriptionModel,\n>(\n model: TModel,\n apiKey: string,\n config?: Omit<SonioxTranscriptionConfig, 'apiKey'>,\n): SonioxTranscriptionAdapter<TModel> {\n return new SonioxTranscriptionAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Soniox transcription adapter using environment variables.\n */\nexport function sonioxTranscription<TModel extends SonioxTranscriptionModel>(\n model: TModel,\n config?: Omit<SonioxTranscriptionConfig, 'apiKey'>,\n): SonioxTranscriptionAdapter<TModel> {\n const apiKey = getSonioxApiKeyFromEnv()\n return new SonioxTranscriptionAdapter({ apiKey, ...config }, model)\n}\n"],"names":["text","binaryStr","bytes"],"mappings":"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"} |
@@ -1,2 +0,2 @@ | ||
| export { SonioxTranscriptionAdapter, createSonioxTranscription, sonioxTranscription, type SonioxTranscriptionConfig, type SonioxTranscriptionModel, } from './adapters/transcription.js'; | ||
| export { SonioxTranscriptionAdapter, createSonioxTranscription, sonioxTranscription, SonioxTranscriptionError, SonioxTranscriptionTimeoutError, SonioxApiError, type SonioxTranscriptionConfig, type SonioxTranscriptionModel, } from './adapters/transcription.js'; | ||
| export type { SonioxTranscriptionProviderOptions } from './audio/transcription-provider-options.js'; |
@@ -1,4 +0,7 @@ | ||
| import { SonioxTranscriptionAdapter, createSonioxTranscription, sonioxTranscription } from "./adapters/transcription.js"; | ||
| import { SonioxApiError, SonioxTranscriptionAdapter, SonioxTranscriptionError, SonioxTranscriptionTimeoutError, createSonioxTranscription, sonioxTranscription } from "./adapters/transcription.js"; | ||
| export { | ||
| SonioxApiError, | ||
| SonioxTranscriptionAdapter, | ||
| SonioxTranscriptionError, | ||
| SonioxTranscriptionTimeoutError, | ||
| createSonioxTranscription, | ||
@@ -5,0 +8,0 @@ sonioxTranscription |
+1
-1
| { | ||
| "name": "@soniox/tanstack-ai-adapter", | ||
| "version": "0.1.0", | ||
| "version": "0.1.1", | ||
| "description": "Soniox transcription adapter for TanStack AI", | ||
@@ -5,0 +5,0 @@ "author": "Soniox Inc.", |
@@ -15,2 +15,36 @@ import { BaseTranscriptionAdapter } from '@tanstack/ai/adapters' | ||
| /** | ||
| * Base error class for Soniox transcription errors | ||
| */ | ||
| export class SonioxTranscriptionError extends Error { | ||
| constructor(message: string) { | ||
| super(message) | ||
| this.name = 'SonioxTranscriptionError' | ||
| } | ||
| } | ||
| /** | ||
| * Error thrown when a transcription job times out | ||
| */ | ||
| export class SonioxTranscriptionTimeoutError extends SonioxTranscriptionError { | ||
| constructor(message = 'Transcription job polling timed out') { | ||
| super(message) | ||
| this.name = 'SonioxTranscriptionTimeoutError' | ||
| } | ||
| } | ||
| /** | ||
| * Error thrown when the Soniox API returns an error response | ||
| */ | ||
| export class SonioxApiError extends SonioxTranscriptionError { | ||
| constructor( | ||
| public readonly status: number, | ||
| public readonly statusText: string, | ||
| public readonly body?: string, | ||
| ) { | ||
| super(`Soniox API error (${status} ${statusText})${body ? `: ${body}` : ''}`) | ||
| this.name = 'SonioxApiError' | ||
| } | ||
| } | ||
| interface SonioxTranscriptToken { | ||
@@ -53,2 +87,3 @@ text: string | ||
| pollingIntervalMs?: number | ||
| timeout?: number | ||
| } | ||
@@ -71,2 +106,3 @@ | ||
| private readonly pollingIntervalMs = 1000 | ||
| private readonly timeout = 5 * 60 * 1000 | ||
| protected override config: SonioxTranscriptionConfig | ||
@@ -84,4 +120,4 @@ | ||
| const file = this.prepareAudioFile(audio) | ||
| const headers = this.buildHeaders() | ||
| const audioUrl = this.extractAudioUrl(audio) | ||
@@ -92,15 +128,19 @@ let fileId: string | undefined | ||
| try { | ||
| const formData = new FormData() | ||
| formData.append('file', file) | ||
| // If not a URL, upload the file first | ||
| if (!audioUrl) { | ||
| const file = this.prepareAudioFile(audio) | ||
| const formData = new FormData() | ||
| formData.append('file', file) | ||
| const uploadResponse = await this.requestJson<SonioxUploadResponse>( | ||
| '/v1/files', | ||
| { | ||
| method: 'POST', | ||
| headers, | ||
| body: formData, | ||
| }, | ||
| ) | ||
| const uploadResponse = await this.requestJson<SonioxUploadResponse>( | ||
| '/v1/files', | ||
| { | ||
| method: 'POST', | ||
| headers, | ||
| body: formData, | ||
| }, | ||
| ) | ||
| fileId = uploadResponse.id | ||
| fileId = uploadResponse.id | ||
| } | ||
@@ -120,3 +160,4 @@ const mapped = this.mapProviderOptions(modelOptions, language) | ||
| model, | ||
| file_id: fileId, | ||
| // Use audio_url if URL provided, otherwise use file_id | ||
| ...(audioUrl ? { audio_url: audioUrl } : { file_id: fileId }), | ||
| language_hints: mapped.languageHints, | ||
@@ -178,2 +219,3 @@ language_hints_strict: mapped.languageHintsStrict, | ||
| } finally { | ||
| // Always clean up transcription | ||
| await this.tryDeleteResource( | ||
@@ -183,2 +225,3 @@ transcriptionId ? `/v1/transcriptions/${transcriptionId}` : undefined, | ||
| ) | ||
| // Clean up file if we uploaded one | ||
| await this.tryDeleteResource( | ||
@@ -191,2 +234,23 @@ fileId ? `/v1/files/${fileId}` : undefined, | ||
| /** | ||
| * Extracts URL string if the audio input is a URL (object or string). | ||
| * Returns undefined for non-URL inputs. | ||
| */ | ||
| private extractAudioUrl( | ||
| audio: string | URL | File | Blob | ArrayBuffer | Uint8Array, | ||
| ): string | undefined { | ||
| if (audio instanceof URL) { | ||
| return audio.toString() | ||
| } | ||
| if ( | ||
| typeof audio === 'string' && | ||
| (audio.startsWith('https://') || audio.startsWith('http://')) | ||
| ) { | ||
| return audio | ||
| } | ||
| return undefined | ||
| } | ||
| private mapProviderOptions( | ||
@@ -307,3 +371,3 @@ options: SonioxTranscriptionProviderOptions | undefined, | ||
| const start = Date.now() | ||
| const timeoutMs = this.config.timeout ?? 3 * 60 * 1000 | ||
| const timeoutMs = this.config.timeout ?? this.timeout | ||
| const interval = this.config.pollingIntervalMs ?? this.pollingIntervalMs | ||
@@ -313,3 +377,3 @@ | ||
| if (Date.now() - start > timeoutMs) { | ||
| throw new Error('Transcription job polling timed out') | ||
| throw new SonioxTranscriptionTimeoutError() | ||
| } | ||
@@ -331,3 +395,3 @@ | ||
| if (statusResponse.status === 'error') { | ||
| throw new Error( | ||
| throw new SonioxTranscriptionError( | ||
| `Transcription failed: ${statusResponse.error_message ?? 'Unknown error'}`, | ||
@@ -347,7 +411,3 @@ ) | ||
| const text = await this.safeReadText(response) | ||
| throw new Error( | ||
| `Soniox API error (${response.status} ${response.statusText})${ | ||
| text ? `: ${text}` : '' | ||
| }`, | ||
| ) | ||
| throw new SonioxApiError(response.status, response.statusText, text) | ||
| } | ||
@@ -363,3 +423,3 @@ | ||
| } catch (error) { | ||
| throw new Error('Failed to parse Soniox response JSON') | ||
| throw new SonioxTranscriptionError('Failed to parse Soniox response JSON') | ||
| } | ||
@@ -403,4 +463,8 @@ } | ||
| /** | ||
| * Prepares audio input as a File for upload. | ||
| * Only called for non-URL inputs (URLs use audio_url parameter directly). | ||
| */ | ||
| private prepareAudioFile( | ||
| audio: string | File | Blob | ArrayBuffer | Uint8Array, | ||
| audio: string | URL | File | Blob | ArrayBuffer | Uint8Array, | ||
| ): File { | ||
@@ -441,2 +505,3 @@ if (typeof File !== 'undefined' && audio instanceof File) { | ||
| // Assume base64 string | ||
| const binaryStr = atob(audio) | ||
@@ -443,0 +508,0 @@ const bytes = new Uint8Array(binaryStr.length) |
+3
-0
@@ -5,2 +5,5 @@ export { | ||
| sonioxTranscription, | ||
| SonioxTranscriptionError, | ||
| SonioxTranscriptionTimeoutError, | ||
| SonioxApiError, | ||
| type SonioxTranscriptionConfig, | ||
@@ -7,0 +10,0 @@ type SonioxTranscriptionModel, |
69907
11.79%1060
15.34%