@tanstack/ai-groq 0.5.3 → 0.5.4
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/dist/esm/adapters/text.js +89 -61
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/adapters/transcription.js +210 -172
- package/dist/esm/adapters/transcription.js.map +1 -1
- package/dist/esm/adapters/tts.js +128 -66
- package/dist/esm/adapters/tts.js.map +1 -1
- package/dist/esm/audio/audio-provider-options.js +11 -8
- package/dist/esm/audio/audio-provider-options.js.map +1 -1
- package/dist/esm/index.js +1 -15
- package/dist/esm/model-meta.js +300 -69
- package/dist/esm/model-meta.js.map +1 -1
- package/dist/esm/tools/function-tool.js +31 -26
- package/dist/esm/tools/function-tool.js.map +1 -1
- package/dist/esm/tools/index.js +1 -5
- package/dist/esm/tools/tool-converter.js +12 -7
- package/dist/esm/tools/tool-converter.js.map +1 -1
- package/dist/esm/utils/client.js +23 -16
- package/dist/esm/utils/client.js.map +1 -1
- package/dist/esm/utils/schema-converter.d.ts +0 -2
- package/dist/esm/utils/schema-converter.js +66 -66
- package/dist/esm/utils/schema-converter.js.map +1 -1
- package/package.json +7 -7
- package/src/utils/schema-converter.ts +0 -3
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/tools/index.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcription.js","sources":["../../../src/adapters/transcription.ts"],"sourcesContent":["import { BaseTranscriptionAdapter } from '@tanstack/ai/adapters'\nimport { base64ToArrayBuffer, generateId } from '@tanstack/ai-utils'\nimport { getGroqApiKeyFromEnv, withGroqDefaults } from '../utils/client'\nimport type {\n TranscriptionOptions,\n TranscriptionResult,\n TranscriptionSegment,\n} from '@tanstack/ai'\nimport type { GroqTranscriptionModel } from '../model-meta'\nimport type { GroqTranscriptionProviderOptions } from '../audio/transcription-provider-options'\nimport type { GroqClientConfig } from '../utils/client'\n\n/**\n * Configuration for the Groq Transcription adapter.\n */\nexport interface GroqTranscriptionConfig extends GroqClientConfig {}\n\n/**\n * Flattens the `openai` SDK's `HeadersLike` config value into a plain record so\n * it can be merged into the raw `fetch` request this adapter issues. Handles\n * the shapes callers actually pass (`Headers`, an entries array, or a plain\n * object); null/undefined values are dropped.\n *\n * ponytail: doesn't unwrap the SDK's internal `NullableHeaders` class; forward\n * that shape here if the SDK ever hands it to adapter config.\n */\nfunction normalizeHeaders(\n headers: GroqTranscriptionConfig['defaultHeaders'],\n): Record<string, string> {\n const out: Record<string, string> = {}\n if (!headers) return out\n const assign = (key: string, value: unknown) => {\n if (value != null) out[key] = String(value)\n }\n if (headers instanceof Headers) {\n headers.forEach((value, key) => assign(key, value))\n } else if (Array.isArray(headers)) {\n for (const [key, value] of headers) assign(key, value)\n } else {\n for (const [key, value] of Object.entries(headers)) assign(key, value)\n }\n return out\n}\n\n// Shape of Groq's verbose_json transcription response\ninterface GroqVerboseTranscriptionResponse {\n task?: string\n language?: string\n duration?: number\n text: string\n segments?: Array<{\n id: number\n seek?: number\n start: number\n end: number\n text: string\n tokens?: Array<number>\n temperature?: number\n avg_logprob: number\n compression_ratio?: number\n no_speech_prob?: number\n }>\n words?: Array<{ word: string; start: number; end: number }>\n x_groq?: { id?: string }\n}\n\n// Shape of Groq's json transcription response\ninterface GroqJsonTranscriptionResponse {\n text: string\n x_groq?: { id?: string }\n}\n\n/**\n * Groq Transcription (Speech-to-Text) Adapter\n *\n * Tree-shakeable adapter for Groq audio transcription. Supports\n * whisper-large-v3 and whisper-large-v3-turbo.\n *\n * Features:\n * - Audio file uploads (File, Blob, ArrayBuffer, base64/data URL)\n * - Remote audio URLs passed directly via Groq's `url` field — no upload needed\n * - Verbose JSON response with segment and word timestamps\n * - Language detection or specification (ISO-639-1)\n * - Confidence scores derived from segment avg_logprob\n */\nexport class GroqTranscriptionAdapter<\n TModel extends GroqTranscriptionModel,\n> extends BaseTranscriptionAdapter<TModel, GroqTranscriptionProviderOptions> {\n readonly name = 'groq' as const\n\n private readonly apiKey: string\n private readonly baseURL: string\n private readonly defaultHeaders: Record<string, string>\n\n constructor(config: GroqTranscriptionConfig, model: TModel) {\n super(model, {})\n const resolved = withGroqDefaults(config)\n this.apiKey = resolved.apiKey\n this.baseURL = resolved.baseURL ?? 'https://api.groq.com/openai/v1'\n this.defaultHeaders = normalizeHeaders(resolved.defaultHeaders)\n }\n\n async transcribe(\n options: TranscriptionOptions<GroqTranscriptionProviderOptions>,\n ): Promise<TranscriptionResult> {\n const { model, audio, language, prompt, responseFormat, modelOptions } =\n options\n\n // Groq's transcription endpoint only accepts 'json', 'text', and\n // 'verbose_json'. Reject 'srt'/'vtt' up front so callers get a clear\n // message instead of an opaque Groq HTTP error.\n if (responseFormat === 'srt' || responseFormat === 'vtt') {\n throw new Error(\n `Groq transcription does not support responseFormat='${responseFormat}'. ` +\n `Supported values: 'json', 'text', 'verbose_json'.`,\n )\n }\n\n // Default to verbose_json so callers get language, duration, and timestamps\n // without having to opt in explicitly. Both Groq whisper models support it.\n const effectiveFormat = responseFormat ?? 'verbose_json'\n const useVerbose = effectiveFormat === 'verbose_json'\n\n const form = new FormData()\n form.append('model', model)\n form.append('response_format', effectiveFormat)\n if (language !== undefined) form.append('language', language)\n if (prompt !== undefined) form.append('prompt', prompt)\n if (modelOptions?.temperature !== undefined) {\n form.append('temperature', String(modelOptions.temperature))\n }\n if (modelOptions?.timestamp_granularities !== undefined) {\n for (const g of modelOptions.timestamp_granularities) {\n form.append('timestamp_granularities[]', g)\n }\n }\n\n // HTTP/HTTPS URLs are forwarded directly via Groq's `url` field, which\n // avoids a round-trip upload. All other inputs (File, Blob, ArrayBuffer,\n // base64, data URL) are converted to a File and sent as `file`.\n if (typeof audio === 'string' && /^https?:\\/\\//.test(audio)) {\n form.append('url', audio)\n } else {\n form.append('file', this.prepareAudioFile(audio))\n }\n\n try {\n options.logger.request(\n `activity=transcription provider=${this.name} model=${model} verbose=${useVerbose}`,\n { provider: this.name, model },\n )\n\n const response = await fetch(`${this.baseURL}/audio/transcriptions`, {\n method: 'POST',\n headers: {\n ...this.defaultHeaders,\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: form,\n })\n\n if (!response.ok) {\n const body = await response\n .json()\n .catch(() => null as Record<string, unknown> | null)\n const message =\n (body?.error as { message?: string } | undefined)?.message ??\n `Groq API error ${response.status}`\n throw new Error(message)\n }\n\n if (useVerbose) {\n const data = (await response.json()) as GroqVerboseTranscriptionResponse\n const requestId = data.x_groq?.id ?? generateId(this.name)\n\n // `TranscriptionResult` declares optional fields without `| undefined`,\n // so under exactOptionalPropertyTypes we must omit absent fields rather\n // than assigning `undefined`.\n const segments = data.segments?.map(\n (seg): TranscriptionSegment => ({\n id: seg.id,\n start: seg.start,\n end: seg.end,\n text: seg.text,\n confidence: Math.exp(seg.avg_logprob),\n }),\n )\n const words = data.words?.map((w) => ({\n word: w.word,\n start: w.start,\n end: w.end,\n }))\n\n return {\n id: requestId,\n model,\n text: data.text,\n ...(data.language !== undefined && { language: data.language }),\n ...(data.duration !== undefined && { duration: data.duration }),\n ...(segments !== undefined && { segments }),\n ...(words !== undefined && { words }),\n }\n } else if (effectiveFormat === 'text') {\n const text = await response.text()\n return {\n id: generateId(this.name),\n model,\n text,\n ...(language !== undefined && { language }),\n }\n } else {\n const data = (await response.json()) as GroqJsonTranscriptionResponse\n return {\n id: data.x_groq?.id ?? generateId(this.name),\n model,\n text: data.text,\n ...(language !== undefined && { language }),\n }\n }\n } catch (error: unknown) {\n options.logger.errors(`${this.name}.transcribe fatal`, {\n error,\n source: `${this.name}.transcribe`,\n })\n throw error\n }\n }\n\n private prepareAudioFile(audio: string | File | Blob | ArrayBuffer): File {\n if (typeof File !== 'undefined' && audio instanceof File) {\n return audio\n }\n if (typeof Blob !== 'undefined' && audio instanceof Blob) {\n this.ensureFileSupport()\n return new File([audio], 'audio.mp3', {\n type: audio.type || 'audio/mpeg',\n })\n }\n if (typeof ArrayBuffer !== 'undefined' && audio instanceof ArrayBuffer) {\n this.ensureFileSupport()\n return new File([audio], 'audio.mp3', { type: 'audio/mpeg' })\n }\n if (typeof audio === 'string') {\n this.ensureFileSupport()\n\n if (audio.startsWith('data:')) {\n const parts = audio.split(',')\n const header = parts[0]\n const base64Data = parts[1] || ''\n const mimeMatch = header?.match(/data:([^;]+)/)\n const mimeType = mimeMatch?.[1] || 'audio/mpeg'\n const bytes = base64ToArrayBuffer(base64Data)\n const extension = mimeType.split('/')[1] || 'mp3'\n return new File([bytes], `audio.${extension}`, { type: mimeType })\n }\n\n const bytes = base64ToArrayBuffer(audio)\n return new File([bytes], 'audio.mp3', { type: 'audio/mpeg' })\n }\n\n throw new Error('Invalid audio input type')\n }\n\n // Throws on Node < 20 where the global `File` constructor is unavailable.\n private ensureFileSupport(): void {\n if (typeof File === 'undefined') {\n throw new Error(\n '`File` is not available in this environment. ' +\n 'Use Node.js 20 or newer, or pass a File object directly.',\n )\n }\n }\n}\n\n/**\n * Creates a Groq transcription adapter with an explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'whisper-large-v3-turbo')\n * @param apiKey - Your Groq API key\n * @param config - Optional additional configuration\n * @returns Configured Groq transcription adapter instance\n *\n * @example\n * ```typescript\n * const adapter = createGroqTranscription('whisper-large-v3-turbo', 'gsk_...');\n *\n * const result = await generateTranscription({\n * adapter,\n * audio: audioFile,\n * language: 'en',\n * });\n * ```\n */\nexport function createGroqTranscription<TModel extends GroqTranscriptionModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GroqTranscriptionConfig, 'apiKey'>,\n): GroqTranscriptionAdapter<TModel> {\n return new GroqTranscriptionAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Groq transcription adapter using the `GROQ_API_KEY` environment\n * variable. Type resolution happens here at the call site.\n *\n * Looks for `GROQ_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (browser with injected env)\n *\n * @param model - The model name (e.g., 'whisper-large-v3-turbo')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Groq transcription adapter instance\n * @throws Error if GROQ_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * const adapter = groqTranscription('whisper-large-v3-turbo');\n *\n * const result = await generateTranscription({\n * adapter,\n * audio: 'https://example.com/audio.mp3',\n * });\n *\n * console.log(result.text)\n * ```\n */\nexport function groqTranscription<TModel extends GroqTranscriptionModel>(\n model: TModel,\n config?: Omit<GroqTranscriptionConfig, 'apiKey'>,\n): GroqTranscriptionAdapter<TModel> {\n const apiKey = getGroqApiKeyFromEnv()\n return createGroqTranscription(model, apiKey, config)\n}\n"],"names":["bytes"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transcription.js","names":[],"sources":["../../../src/adapters/transcription.ts"],"sourcesContent":["import { BaseTranscriptionAdapter } from '@tanstack/ai/adapters'\nimport { base64ToArrayBuffer, generateId } from '@tanstack/ai-utils'\nimport { getGroqApiKeyFromEnv, withGroqDefaults } from '../utils/client'\nimport type {\n TranscriptionOptions,\n TranscriptionResult,\n TranscriptionSegment,\n} from '@tanstack/ai'\nimport type { GroqTranscriptionModel } from '../model-meta'\nimport type { GroqTranscriptionProviderOptions } from '../audio/transcription-provider-options'\nimport type { GroqClientConfig } from '../utils/client'\n\n/**\n * Configuration for the Groq Transcription adapter.\n */\nexport interface GroqTranscriptionConfig extends GroqClientConfig {}\n\n/**\n * Flattens the `openai` SDK's `HeadersLike` config value into a plain record so\n * it can be merged into the raw `fetch` request this adapter issues. Handles\n * the shapes callers actually pass (`Headers`, an entries array, or a plain\n * object); null/undefined values are dropped.\n *\n * ponytail: doesn't unwrap the SDK's internal `NullableHeaders` class; forward\n * that shape here if the SDK ever hands it to adapter config.\n */\nfunction normalizeHeaders(\n headers: GroqTranscriptionConfig['defaultHeaders'],\n): Record<string, string> {\n const out: Record<string, string> = {}\n if (!headers) return out\n const assign = (key: string, value: unknown) => {\n if (value != null) out[key] = String(value)\n }\n if (headers instanceof Headers) {\n headers.forEach((value, key) => assign(key, value))\n } else if (Array.isArray(headers)) {\n for (const [key, value] of headers) assign(key, value)\n } else {\n for (const [key, value] of Object.entries(headers)) assign(key, value)\n }\n return out\n}\n\n// Shape of Groq's verbose_json transcription response\ninterface GroqVerboseTranscriptionResponse {\n task?: string\n language?: string\n duration?: number\n text: string\n segments?: Array<{\n id: number\n seek?: number\n start: number\n end: number\n text: string\n tokens?: Array<number>\n temperature?: number\n avg_logprob: number\n compression_ratio?: number\n no_speech_prob?: number\n }>\n words?: Array<{ word: string; start: number; end: number }>\n x_groq?: { id?: string }\n}\n\n// Shape of Groq's json transcription response\ninterface GroqJsonTranscriptionResponse {\n text: string\n x_groq?: { id?: string }\n}\n\n/**\n * Groq Transcription (Speech-to-Text) Adapter\n *\n * Tree-shakeable adapter for Groq audio transcription. Supports\n * whisper-large-v3 and whisper-large-v3-turbo.\n *\n * Features:\n * - Audio file uploads (File, Blob, ArrayBuffer, base64/data URL)\n * - Remote audio URLs passed directly via Groq's `url` field — no upload needed\n * - Verbose JSON response with segment and word timestamps\n * - Language detection or specification (ISO-639-1)\n * - Confidence scores derived from segment avg_logprob\n */\nexport class GroqTranscriptionAdapter<\n TModel extends GroqTranscriptionModel,\n> extends BaseTranscriptionAdapter<TModel, GroqTranscriptionProviderOptions> {\n readonly name = 'groq' as const\n\n private readonly apiKey: string\n private readonly baseURL: string\n private readonly defaultHeaders: Record<string, string>\n\n constructor(config: GroqTranscriptionConfig, model: TModel) {\n super(model, {})\n const resolved = withGroqDefaults(config)\n this.apiKey = resolved.apiKey\n this.baseURL = resolved.baseURL ?? 'https://api.groq.com/openai/v1'\n this.defaultHeaders = normalizeHeaders(resolved.defaultHeaders)\n }\n\n async transcribe(\n options: TranscriptionOptions<GroqTranscriptionProviderOptions>,\n ): Promise<TranscriptionResult> {\n const { model, audio, language, prompt, responseFormat, modelOptions } =\n options\n\n // Groq's transcription endpoint only accepts 'json', 'text', and\n // 'verbose_json'. Reject 'srt'/'vtt' up front so callers get a clear\n // message instead of an opaque Groq HTTP error.\n if (responseFormat === 'srt' || responseFormat === 'vtt') {\n throw new Error(\n `Groq transcription does not support responseFormat='${responseFormat}'. ` +\n `Supported values: 'json', 'text', 'verbose_json'.`,\n )\n }\n\n // Default to verbose_json so callers get language, duration, and timestamps\n // without having to opt in explicitly. Both Groq whisper models support it.\n const effectiveFormat = responseFormat ?? 'verbose_json'\n const useVerbose = effectiveFormat === 'verbose_json'\n\n const form = new FormData()\n form.append('model', model)\n form.append('response_format', effectiveFormat)\n if (language !== undefined) form.append('language', language)\n if (prompt !== undefined) form.append('prompt', prompt)\n if (modelOptions?.temperature !== undefined) {\n form.append('temperature', String(modelOptions.temperature))\n }\n if (modelOptions?.timestamp_granularities !== undefined) {\n for (const g of modelOptions.timestamp_granularities) {\n form.append('timestamp_granularities[]', g)\n }\n }\n\n // HTTP/HTTPS URLs are forwarded directly via Groq's `url` field, which\n // avoids a round-trip upload. All other inputs (File, Blob, ArrayBuffer,\n // base64, data URL) are converted to a File and sent as `file`.\n if (typeof audio === 'string' && /^https?:\\/\\//.test(audio)) {\n form.append('url', audio)\n } else {\n form.append('file', this.prepareAudioFile(audio))\n }\n\n try {\n options.logger.request(\n `activity=transcription provider=${this.name} model=${model} verbose=${useVerbose}`,\n { provider: this.name, model },\n )\n\n const response = await fetch(`${this.baseURL}/audio/transcriptions`, {\n method: 'POST',\n headers: {\n ...this.defaultHeaders,\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: form,\n })\n\n if (!response.ok) {\n const body = await response\n .json()\n .catch(() => null as Record<string, unknown> | null)\n const message =\n (body?.error as { message?: string } | undefined)?.message ??\n `Groq API error ${response.status}`\n throw new Error(message)\n }\n\n if (useVerbose) {\n const data = (await response.json()) as GroqVerboseTranscriptionResponse\n const requestId = data.x_groq?.id ?? generateId(this.name)\n\n // `TranscriptionResult` declares optional fields without `| undefined`,\n // so under exactOptionalPropertyTypes we must omit absent fields rather\n // than assigning `undefined`.\n const segments = data.segments?.map(\n (seg): TranscriptionSegment => ({\n id: seg.id,\n start: seg.start,\n end: seg.end,\n text: seg.text,\n confidence: Math.exp(seg.avg_logprob),\n }),\n )\n const words = data.words?.map((w) => ({\n word: w.word,\n start: w.start,\n end: w.end,\n }))\n\n return {\n id: requestId,\n model,\n text: data.text,\n ...(data.language !== undefined && { language: data.language }),\n ...(data.duration !== undefined && { duration: data.duration }),\n ...(segments !== undefined && { segments }),\n ...(words !== undefined && { words }),\n }\n } else if (effectiveFormat === 'text') {\n const text = await response.text()\n return {\n id: generateId(this.name),\n model,\n text,\n ...(language !== undefined && { language }),\n }\n } else {\n const data = (await response.json()) as GroqJsonTranscriptionResponse\n return {\n id: data.x_groq?.id ?? generateId(this.name),\n model,\n text: data.text,\n ...(language !== undefined && { language }),\n }\n }\n } catch (error: unknown) {\n options.logger.errors(`${this.name}.transcribe fatal`, {\n error,\n source: `${this.name}.transcribe`,\n })\n throw error\n }\n }\n\n private prepareAudioFile(audio: string | File | Blob | ArrayBuffer): File {\n if (typeof File !== 'undefined' && audio instanceof File) {\n return audio\n }\n if (typeof Blob !== 'undefined' && audio instanceof Blob) {\n this.ensureFileSupport()\n return new File([audio], 'audio.mp3', {\n type: audio.type || 'audio/mpeg',\n })\n }\n if (typeof ArrayBuffer !== 'undefined' && audio instanceof ArrayBuffer) {\n this.ensureFileSupport()\n return new File([audio], 'audio.mp3', { type: 'audio/mpeg' })\n }\n if (typeof audio === 'string') {\n this.ensureFileSupport()\n\n if (audio.startsWith('data:')) {\n const parts = audio.split(',')\n const header = parts[0]\n const base64Data = parts[1] || ''\n const mimeMatch = header?.match(/data:([^;]+)/)\n const mimeType = mimeMatch?.[1] || 'audio/mpeg'\n const bytes = base64ToArrayBuffer(base64Data)\n const extension = mimeType.split('/')[1] || 'mp3'\n return new File([bytes], `audio.${extension}`, { type: mimeType })\n }\n\n const bytes = base64ToArrayBuffer(audio)\n return new File([bytes], 'audio.mp3', { type: 'audio/mpeg' })\n }\n\n throw new Error('Invalid audio input type')\n }\n\n // Throws on Node < 20 where the global `File` constructor is unavailable.\n private ensureFileSupport(): void {\n if (typeof File === 'undefined') {\n throw new Error(\n '`File` is not available in this environment. ' +\n 'Use Node.js 20 or newer, or pass a File object directly.',\n )\n }\n }\n}\n\n/**\n * Creates a Groq transcription adapter with an explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'whisper-large-v3-turbo')\n * @param apiKey - Your Groq API key\n * @param config - Optional additional configuration\n * @returns Configured Groq transcription adapter instance\n *\n * @example\n * ```typescript\n * const adapter = createGroqTranscription('whisper-large-v3-turbo', 'gsk_...');\n *\n * const result = await generateTranscription({\n * adapter,\n * audio: audioFile,\n * language: 'en',\n * });\n * ```\n */\nexport function createGroqTranscription<TModel extends GroqTranscriptionModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GroqTranscriptionConfig, 'apiKey'>,\n): GroqTranscriptionAdapter<TModel> {\n return new GroqTranscriptionAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Groq transcription adapter using the `GROQ_API_KEY` environment\n * variable. Type resolution happens here at the call site.\n *\n * Looks for `GROQ_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (browser with injected env)\n *\n * @param model - The model name (e.g., 'whisper-large-v3-turbo')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Groq transcription adapter instance\n * @throws Error if GROQ_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * const adapter = groqTranscription('whisper-large-v3-turbo');\n *\n * const result = await generateTranscription({\n * adapter,\n * audio: 'https://example.com/audio.mp3',\n * });\n *\n * console.log(result.text)\n * ```\n */\nexport function groqTranscription<TModel extends GroqTranscriptionModel>(\n model: TModel,\n config?: Omit<GroqTranscriptionConfig, 'apiKey'>,\n): GroqTranscriptionAdapter<TModel> {\n const apiKey = getGroqApiKeyFromEnv()\n return createGroqTranscription(model, apiKey, config)\n}\n"],"mappings":"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"}
|
package/dist/esm/adapters/tts.js
CHANGED
|
@@ -1,73 +1,135 @@
|
|
|
1
|
+
import { getGroqApiKeyFromEnv, withGroqDefaults } from "../utils/client.js";
|
|
2
|
+
import { validateAudioInput } from "../audio/audio-provider-options.js";
|
|
1
3
|
import OpenAI from "openai";
|
|
4
|
+
import { arrayBufferToBase64, generateId } from "@tanstack/ai-utils";
|
|
2
5
|
import { BaseTTSAdapter } from "@tanstack/ai/adapters";
|
|
3
6
|
import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
7
|
+
//#region src/adapters/tts.ts
|
|
8
|
+
/**
|
|
9
|
+
* Groq Text-to-Speech Adapter
|
|
10
|
+
*
|
|
11
|
+
* Tree-shakeable adapter for Groq TTS functionality. Groq exposes an
|
|
12
|
+
* OpenAI-compatible `/audio/speech` endpoint, so the adapter drives it with
|
|
13
|
+
* the OpenAI SDK via a `baseURL` override (the same pattern as the Groq text
|
|
14
|
+
* adapter).
|
|
15
|
+
*
|
|
16
|
+
* Supports `canopylabs/orpheus-v1-english` and
|
|
17
|
+
* `canopylabs/orpheus-arabic-saudi`.
|
|
18
|
+
*
|
|
19
|
+
* Features:
|
|
20
|
+
* - English voices: autumn(f), diana(f), hannah(f), austin(m), daniel(m), troy(m)
|
|
21
|
+
* - Arabic voices: fahad(m), sultan(m), lulwa(f), noura(f)
|
|
22
|
+
* - Output formats: flac, mp3, mulaw, ogg, wav (default wav)
|
|
23
|
+
* - Speed control
|
|
24
|
+
* - Configurable sample rate via `modelOptions`
|
|
25
|
+
*/
|
|
26
|
+
var GroqTTSAdapter = class extends BaseTTSAdapter {
|
|
27
|
+
name = "groq";
|
|
28
|
+
client;
|
|
29
|
+
constructor(config, model) {
|
|
30
|
+
super(model, {});
|
|
31
|
+
this.client = new OpenAI(withGroqDefaults(config));
|
|
32
|
+
}
|
|
33
|
+
async generateSpeech(options) {
|
|
34
|
+
const { model, text, voice, format, speed, modelOptions } = options;
|
|
35
|
+
validateAudioInput({
|
|
36
|
+
input: text,
|
|
37
|
+
model: this.model
|
|
38
|
+
});
|
|
39
|
+
const request = {
|
|
40
|
+
model,
|
|
41
|
+
input: text,
|
|
42
|
+
voice: voice ?? "autumn",
|
|
43
|
+
response_format: format ?? "wav",
|
|
44
|
+
...speed !== void 0 && { speed },
|
|
45
|
+
...modelOptions ?? {}
|
|
46
|
+
};
|
|
47
|
+
try {
|
|
48
|
+
options.logger.request(`activity=tts provider=${this.name} model=${model} format=${request.response_format ?? "default"} voice=${request.voice}`, {
|
|
49
|
+
provider: this.name,
|
|
50
|
+
model
|
|
51
|
+
});
|
|
52
|
+
const base64 = arrayBufferToBase64(await (await this.client.audio.speech.create(request)).arrayBuffer());
|
|
53
|
+
const outputFormat = request.response_format ?? "wav";
|
|
54
|
+
const contentType = this.getContentType(outputFormat);
|
|
55
|
+
return {
|
|
56
|
+
id: generateId(this.name),
|
|
57
|
+
model,
|
|
58
|
+
audio: base64,
|
|
59
|
+
format: outputFormat,
|
|
60
|
+
contentType
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
options.logger.errors(`${this.name}.generateSpeech fatal`, {
|
|
64
|
+
error: toRunErrorPayload(error, `${this.name}.generateSpeech failed`),
|
|
65
|
+
source: `${this.name}.generateSpeech`
|
|
66
|
+
});
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
getContentType(format) {
|
|
71
|
+
return {
|
|
72
|
+
flac: "audio/flac",
|
|
73
|
+
mp3: "audio/mpeg",
|
|
74
|
+
mulaw: "audio/basic",
|
|
75
|
+
ogg: "audio/ogg",
|
|
76
|
+
wav: "audio/wav"
|
|
77
|
+
}[format] || "audio/wav";
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Creates a Groq speech adapter with explicit API key.
|
|
82
|
+
* Type resolution happens here at the call site.
|
|
83
|
+
*
|
|
84
|
+
* @param model - The model name (e.g., 'canopylabs/orpheus-v1-english')
|
|
85
|
+
* @param apiKey - Your Groq API key
|
|
86
|
+
* @param config - Optional additional configuration
|
|
87
|
+
* @returns Configured Groq speech adapter instance with resolved types
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* const adapter = createGroqSpeech('canopylabs/orpheus-v1-english', 'gsk_...')
|
|
92
|
+
*
|
|
93
|
+
* const result = await generateSpeech({
|
|
94
|
+
* adapter,
|
|
95
|
+
* text: 'Hello, world!',
|
|
96
|
+
* voice: 'autumn',
|
|
97
|
+
* })
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
61
100
|
function createGroqSpeech(model, apiKey, config) {
|
|
62
|
-
|
|
101
|
+
return new GroqTTSAdapter({
|
|
102
|
+
apiKey,
|
|
103
|
+
...config
|
|
104
|
+
}, model);
|
|
63
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Creates a Groq speech adapter with automatic API key detection from
|
|
108
|
+
* environment variables.
|
|
109
|
+
*
|
|
110
|
+
* Looks for `GROQ_API_KEY` in the environment.
|
|
111
|
+
*
|
|
112
|
+
* @param model - The model name (e.g., 'canopylabs/orpheus-v1-english')
|
|
113
|
+
* @param config - Optional configuration (excluding apiKey which is auto-detected)
|
|
114
|
+
* @returns Configured Groq speech adapter instance with resolved types
|
|
115
|
+
* @throws Error if GROQ_API_KEY is not found in environment
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* const adapter = groqSpeech('canopylabs/orpheus-v1-english')
|
|
120
|
+
*
|
|
121
|
+
* const result = await generateSpeech({
|
|
122
|
+
* adapter,
|
|
123
|
+
* text: 'Welcome to TanStack AI!',
|
|
124
|
+
* voice: 'autumn',
|
|
125
|
+
* format: 'wav',
|
|
126
|
+
* })
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
64
129
|
function groqSpeech(model, config) {
|
|
65
|
-
|
|
66
|
-
return createGroqSpeech(model, apiKey, config);
|
|
130
|
+
return createGroqSpeech(model, getGroqApiKeyFromEnv(), config);
|
|
67
131
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
};
|
|
73
|
-
//# sourceMappingURL=tts.js.map
|
|
132
|
+
//#endregion
|
|
133
|
+
export { GroqTTSAdapter, createGroqSpeech, groqSpeech };
|
|
134
|
+
|
|
135
|
+
//# sourceMappingURL=tts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tts.js","sources":["../../../src/adapters/tts.ts"],"sourcesContent":["import OpenAI from 'openai'\nimport { BaseTTSAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { arrayBufferToBase64, generateId } from '@tanstack/ai-utils'\nimport { getGroqApiKeyFromEnv, withGroqDefaults } from '../utils/client'\nimport { validateAudioInput } from '../audio/audio-provider-options'\nimport type { TTSOptions, TTSResult } from '@tanstack/ai'\nimport type OpenAI_SDK from 'openai'\nimport type { GroqTTSModel } from '../model-meta'\nimport type { GroqTTSProviderOptions } from '../audio/tts-provider-options'\nimport type { GroqClientConfig } from '../utils/client'\n\n/**\n * Configuration for Groq TTS adapter\n */\nexport interface GroqTTSConfig extends GroqClientConfig {}\n\n/**\n * Groq Text-to-Speech Adapter\n *\n * Tree-shakeable adapter for Groq TTS functionality. Groq exposes an\n * OpenAI-compatible `/audio/speech` endpoint, so the adapter drives it with\n * the OpenAI SDK via a `baseURL` override (the same pattern as the Groq text\n * adapter).\n *\n * Supports `canopylabs/orpheus-v1-english` and\n * `canopylabs/orpheus-arabic-saudi`.\n *\n * Features:\n * - English voices: autumn(f), diana(f), hannah(f), austin(m), daniel(m), troy(m)\n * - Arabic voices: fahad(m), sultan(m), lulwa(f), noura(f)\n * - Output formats: flac, mp3, mulaw, ogg, wav (default wav)\n * - Speed control\n * - Configurable sample rate via `modelOptions`\n */\nexport class GroqTTSAdapter<TModel extends GroqTTSModel> extends BaseTTSAdapter<\n TModel,\n GroqTTSProviderOptions\n> {\n readonly name = 'groq' as const\n\n protected client: OpenAI\n\n constructor(config: GroqTTSConfig, model: TModel) {\n super(model, {})\n this.client = new OpenAI(withGroqDefaults(config))\n }\n\n async generateSpeech(\n options: TTSOptions<GroqTTSProviderOptions>,\n ): Promise<TTSResult> {\n const { model, text, voice, format, speed, modelOptions } = options\n\n validateAudioInput({ input: text, model: this.model })\n\n // Spreading optional inputs conditionally keeps the request compatible\n // with the vendor SDK shape under exactOptionalPropertyTypes. `sample_rate`\n // is a Groq-only body field carried via modelOptions.\n const request: OpenAI_SDK.Audio.SpeechCreateParams = {\n model,\n input: text,\n voice: voice ?? 'autumn',\n response_format: format ?? 'wav',\n ...(speed !== undefined && { speed }),\n ...(modelOptions ?? {}),\n }\n\n try {\n options.logger.request(\n `activity=tts provider=${this.name} model=${model} format=${request.response_format ?? 'default'} voice=${request.voice}`,\n { provider: this.name, model },\n )\n const response = await this.client.audio.speech.create(request)\n\n const arrayBuffer = await response.arrayBuffer()\n const base64 = arrayBufferToBase64(arrayBuffer)\n\n const outputFormat = request.response_format ?? 'wav'\n const contentType = this.getContentType(outputFormat)\n\n return {\n id: generateId(this.name),\n model,\n audio: base64,\n format: outputFormat,\n contentType,\n }\n } catch (error: unknown) {\n // Narrow before logging: raw SDK errors can carry request metadata\n // (including auth headers) which we must never surface to user loggers.\n options.logger.errors(`${this.name}.generateSpeech fatal`, {\n error: toRunErrorPayload(error, `${this.name}.generateSpeech failed`),\n source: `${this.name}.generateSpeech`,\n })\n throw error\n }\n }\n\n private getContentType(format: string): string {\n const contentTypes: Record<string, string> = {\n flac: 'audio/flac',\n mp3: 'audio/mpeg',\n mulaw: 'audio/basic',\n ogg: 'audio/ogg',\n wav: 'audio/wav',\n }\n return contentTypes[format] || 'audio/wav'\n }\n}\n\n/**\n * Creates a Groq speech adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'canopylabs/orpheus-v1-english')\n * @param apiKey - Your Groq API key\n * @param config - Optional additional configuration\n * @returns Configured Groq speech adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGroqSpeech('canopylabs/orpheus-v1-english', 'gsk_...')\n *\n * const result = await generateSpeech({\n * adapter,\n * text: 'Hello, world!',\n * voice: 'autumn',\n * })\n * ```\n */\nexport function createGroqSpeech<TModel extends GroqTTSModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GroqTTSConfig, 'apiKey'>,\n): GroqTTSAdapter<TModel> {\n return new GroqTTSAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Groq speech adapter with automatic API key detection from\n * environment variables.\n *\n * Looks for `GROQ_API_KEY` in the environment.\n *\n * @param model - The model name (e.g., 'canopylabs/orpheus-v1-english')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Groq speech adapter instance with resolved types\n * @throws Error if GROQ_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * const adapter = groqSpeech('canopylabs/orpheus-v1-english')\n *\n * const result = await generateSpeech({\n * adapter,\n * text: 'Welcome to TanStack AI!',\n * voice: 'autumn',\n * format: 'wav',\n * })\n * ```\n */\nexport function groqSpeech<TModel extends GroqTTSModel>(\n model: TModel,\n config?: Omit<GroqTTSConfig, 'apiKey'>,\n): GroqTTSAdapter<TModel> {\n const apiKey = getGroqApiKeyFromEnv()\n return createGroqSpeech(model, apiKey, config)\n}\n"],"
|
|
1
|
+
{"version":3,"file":"tts.js","names":[],"sources":["../../../src/adapters/tts.ts"],"sourcesContent":["import OpenAI from 'openai'\nimport { BaseTTSAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { arrayBufferToBase64, generateId } from '@tanstack/ai-utils'\nimport { getGroqApiKeyFromEnv, withGroqDefaults } from '../utils/client'\nimport { validateAudioInput } from '../audio/audio-provider-options'\nimport type { TTSOptions, TTSResult } from '@tanstack/ai'\nimport type OpenAI_SDK from 'openai'\nimport type { GroqTTSModel } from '../model-meta'\nimport type { GroqTTSProviderOptions } from '../audio/tts-provider-options'\nimport type { GroqClientConfig } from '../utils/client'\n\n/**\n * Configuration for Groq TTS adapter\n */\nexport interface GroqTTSConfig extends GroqClientConfig {}\n\n/**\n * Groq Text-to-Speech Adapter\n *\n * Tree-shakeable adapter for Groq TTS functionality. Groq exposes an\n * OpenAI-compatible `/audio/speech` endpoint, so the adapter drives it with\n * the OpenAI SDK via a `baseURL` override (the same pattern as the Groq text\n * adapter).\n *\n * Supports `canopylabs/orpheus-v1-english` and\n * `canopylabs/orpheus-arabic-saudi`.\n *\n * Features:\n * - English voices: autumn(f), diana(f), hannah(f), austin(m), daniel(m), troy(m)\n * - Arabic voices: fahad(m), sultan(m), lulwa(f), noura(f)\n * - Output formats: flac, mp3, mulaw, ogg, wav (default wav)\n * - Speed control\n * - Configurable sample rate via `modelOptions`\n */\nexport class GroqTTSAdapter<TModel extends GroqTTSModel> extends BaseTTSAdapter<\n TModel,\n GroqTTSProviderOptions\n> {\n readonly name = 'groq' as const\n\n protected client: OpenAI\n\n constructor(config: GroqTTSConfig, model: TModel) {\n super(model, {})\n this.client = new OpenAI(withGroqDefaults(config))\n }\n\n async generateSpeech(\n options: TTSOptions<GroqTTSProviderOptions>,\n ): Promise<TTSResult> {\n const { model, text, voice, format, speed, modelOptions } = options\n\n validateAudioInput({ input: text, model: this.model })\n\n // Spreading optional inputs conditionally keeps the request compatible\n // with the vendor SDK shape under exactOptionalPropertyTypes. `sample_rate`\n // is a Groq-only body field carried via modelOptions.\n const request: OpenAI_SDK.Audio.SpeechCreateParams = {\n model,\n input: text,\n voice: voice ?? 'autumn',\n response_format: format ?? 'wav',\n ...(speed !== undefined && { speed }),\n ...(modelOptions ?? {}),\n }\n\n try {\n options.logger.request(\n `activity=tts provider=${this.name} model=${model} format=${request.response_format ?? 'default'} voice=${request.voice}`,\n { provider: this.name, model },\n )\n const response = await this.client.audio.speech.create(request)\n\n const arrayBuffer = await response.arrayBuffer()\n const base64 = arrayBufferToBase64(arrayBuffer)\n\n const outputFormat = request.response_format ?? 'wav'\n const contentType = this.getContentType(outputFormat)\n\n return {\n id: generateId(this.name),\n model,\n audio: base64,\n format: outputFormat,\n contentType,\n }\n } catch (error: unknown) {\n // Narrow before logging: raw SDK errors can carry request metadata\n // (including auth headers) which we must never surface to user loggers.\n options.logger.errors(`${this.name}.generateSpeech fatal`, {\n error: toRunErrorPayload(error, `${this.name}.generateSpeech failed`),\n source: `${this.name}.generateSpeech`,\n })\n throw error\n }\n }\n\n private getContentType(format: string): string {\n const contentTypes: Record<string, string> = {\n flac: 'audio/flac',\n mp3: 'audio/mpeg',\n mulaw: 'audio/basic',\n ogg: 'audio/ogg',\n wav: 'audio/wav',\n }\n return contentTypes[format] || 'audio/wav'\n }\n}\n\n/**\n * Creates a Groq speech adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'canopylabs/orpheus-v1-english')\n * @param apiKey - Your Groq API key\n * @param config - Optional additional configuration\n * @returns Configured Groq speech adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGroqSpeech('canopylabs/orpheus-v1-english', 'gsk_...')\n *\n * const result = await generateSpeech({\n * adapter,\n * text: 'Hello, world!',\n * voice: 'autumn',\n * })\n * ```\n */\nexport function createGroqSpeech<TModel extends GroqTTSModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GroqTTSConfig, 'apiKey'>,\n): GroqTTSAdapter<TModel> {\n return new GroqTTSAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Groq speech adapter with automatic API key detection from\n * environment variables.\n *\n * Looks for `GROQ_API_KEY` in the environment.\n *\n * @param model - The model name (e.g., 'canopylabs/orpheus-v1-english')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Groq speech adapter instance with resolved types\n * @throws Error if GROQ_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * const adapter = groqSpeech('canopylabs/orpheus-v1-english')\n *\n * const result = await generateSpeech({\n * adapter,\n * text: 'Welcome to TanStack AI!',\n * voice: 'autumn',\n * format: 'wav',\n * })\n * ```\n */\nexport function groqSpeech<TModel extends GroqTTSModel>(\n model: TModel,\n config?: Omit<GroqTTSConfig, 'apiKey'>,\n): GroqTTSAdapter<TModel> {\n const apiKey = getGroqApiKeyFromEnv()\n return createGroqSpeech(model, apiKey, config)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,IAAa,iBAAb,cAAiE,eAG/D;CACA,OAAgB;CAEhB;CAEA,YAAY,QAAuB,OAAe;EAChD,MAAM,OAAO,CAAC,CAAC;EACf,KAAK,SAAS,IAAI,OAAO,iBAAiB,MAAM,CAAC;CACnD;CAEA,MAAM,eACJ,SACoB;EACpB,MAAM,EAAE,OAAO,MAAM,OAAO,QAAQ,OAAO,iBAAiB;EAE5D,mBAAmB;GAAE,OAAO;GAAM,OAAO,KAAK;EAAM,CAAC;EAKrD,MAAM,UAA+C;GACnD;GACA,OAAO;GACP,OAAO,SAAS;GAChB,iBAAiB,UAAU;GAC3B,GAAI,UAAU,KAAA,KAAa,EAAE,MAAM;GACnC,GAAI,gBAAgB,CAAC;EACvB;EAEA,IAAI;GACF,QAAQ,OAAO,QACb,yBAAyB,KAAK,KAAK,SAAS,MAAM,UAAU,QAAQ,mBAAmB,UAAU,SAAS,QAAQ,SAClH;IAAE,UAAU,KAAK;IAAM;GAAM,CAC/B;GAIA,MAAM,SAAS,oBAAoB,OADT,MAFH,KAAK,OAAO,MAAM,OAAO,OAAO,OAAO,EAAA,CAE3B,YAAY,CACD;GAE9C,MAAM,eAAe,QAAQ,mBAAmB;GAChD,MAAM,cAAc,KAAK,eAAe,YAAY;GAEpD,OAAO;IACL,IAAI,WAAW,KAAK,IAAI;IACxB;IACA,OAAO;IACP,QAAQ;IACR;GACF;EACF,SAAS,OAAgB;GAGvB,QAAQ,OAAO,OAAO,GAAG,KAAK,KAAK,wBAAwB;IACzD,OAAO,kBAAkB,OAAO,GAAG,KAAK,KAAK,uBAAuB;IACpE,QAAQ,GAAG,KAAK,KAAK;GACvB,CAAC;GACD,MAAM;EACR;CACF;CAEA,eAAuB,QAAwB;EAQ7C,OAAO;GANL,MAAM;GACN,KAAK;GACL,OAAO;GACP,KAAK;GACL,KAAK;EAEA,EAAa,WAAW;CACjC;AACF;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,iBACd,OACA,QACA,QACwB;CACxB,OAAO,IAAI,eAAe;EAAE;EAAQ,GAAG;CAAO,GAAG,KAAK;AACxD;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,WACd,OACA,QACwB;CAExB,OAAO,iBAAiB,OADT,qBACgB,GAAQ,MAAM;AAC/C"}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
//#region src/audio/audio-provider-options.ts
|
|
2
|
+
/**
|
|
3
|
+
* Validates that the audio input text does not exceed the maximum length.
|
|
4
|
+
* @throws Error if input text exceeds 200 characters
|
|
5
|
+
*/
|
|
6
|
+
var validateAudioInput = (options) => {
|
|
7
|
+
if (options.input.length > 200) throw new Error("Input text exceeds maximum length of 200 characters.");
|
|
5
8
|
};
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
//# sourceMappingURL=audio-provider-options.js.map
|
|
9
|
+
//#endregion
|
|
10
|
+
export { validateAudioInput };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=audio-provider-options.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audio-provider-options.js","sources":["../../../src/audio/audio-provider-options.ts"],"sourcesContent":["/**\n * Common audio provider options for Groq audio endpoints.\n */\nexport interface AudioProviderOptions {\n /**\n * The text to generate audio for.\n * Maximum length is 200 characters.\n * Use [directions] for vocal control (English voices only).\n */\n input: string\n /**\n * The audio model to use for generation.\n */\n model: string\n}\n\n/**\n * Validates that the audio input text does not exceed the maximum length.\n * @throws Error if input text exceeds 200 characters\n */\nexport const validateAudioInput = (options: AudioProviderOptions) => {\n if (options.input.length > 200) {\n throw new Error('Input text exceeds maximum length of 200 characters.')\n }\n}\n"],"
|
|
1
|
+
{"version":3,"file":"audio-provider-options.js","names":[],"sources":["../../../src/audio/audio-provider-options.ts"],"sourcesContent":["/**\n * Common audio provider options for Groq audio endpoints.\n */\nexport interface AudioProviderOptions {\n /**\n * The text to generate audio for.\n * Maximum length is 200 characters.\n * Use [directions] for vocal control (English voices only).\n */\n input: string\n /**\n * The audio model to use for generation.\n */\n model: string\n}\n\n/**\n * Validates that the audio input text does not exceed the maximum length.\n * @throws Error if input text exceeds 200 characters\n */\nexport const validateAudioInput = (options: AudioProviderOptions) => {\n if (options.input.length > 200) {\n throw new Error('Input text exceeds maximum length of 200 characters.')\n }\n}\n"],"mappings":";;;;;AAoBA,IAAa,sBAAsB,YAAkC;CACnE,IAAI,QAAQ,MAAM,SAAS,KACzB,MAAM,IAAI,MAAM,sDAAsD;AAE1E"}
|
package/dist/esm/index.js
CHANGED
|
@@ -2,18 +2,4 @@ import { GroqTextAdapter, createGroqText, groqText } from "./adapters/text.js";
|
|
|
2
2
|
import { GroqTranscriptionAdapter, createGroqTranscription, groqTranscription } from "./adapters/transcription.js";
|
|
3
3
|
import { GroqTTSAdapter, createGroqSpeech, groqSpeech } from "./adapters/tts.js";
|
|
4
4
|
import { GROQ_CHAT_MODELS, GROQ_TRANSCRIPTION_MODELS, GROQ_TTS_MODELS } from "./model-meta.js";
|
|
5
|
-
export {
|
|
6
|
-
GROQ_CHAT_MODELS,
|
|
7
|
-
GROQ_TRANSCRIPTION_MODELS,
|
|
8
|
-
GROQ_TTS_MODELS,
|
|
9
|
-
GroqTTSAdapter,
|
|
10
|
-
GroqTextAdapter,
|
|
11
|
-
GroqTranscriptionAdapter,
|
|
12
|
-
createGroqSpeech,
|
|
13
|
-
createGroqText,
|
|
14
|
-
createGroqTranscription,
|
|
15
|
-
groqSpeech,
|
|
16
|
-
groqText,
|
|
17
|
-
groqTranscription
|
|
18
|
-
};
|
|
19
|
-
//# sourceMappingURL=index.js.map
|
|
5
|
+
export { GROQ_CHAT_MODELS, GROQ_TRANSCRIPTION_MODELS, GROQ_TTS_MODELS, GroqTTSAdapter, GroqTextAdapter, GroqTranscriptionAdapter, createGroqSpeech, createGroqText, createGroqTranscription, groqSpeech, groqText, groqTranscription };
|