@ai-sdk/fish-audio 3.0.29 → 3.0.30

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/index.js CHANGED
@@ -1,561 +1,439 @@
1
- // src/fish-audio-provider.ts
2
- import {
3
- NoSuchModelError
4
- } from "@ai-sdk/provider";
5
- import {
6
- loadApiKey,
7
- withUserAgentSuffix
8
- } from "@ai-sdk/provider-utils";
9
-
10
- // src/fish-audio-speech-model.ts
11
- import {
12
- combineHeaders,
13
- createBinaryResponseHandler,
14
- parseProviderOptions,
15
- postJsonToApi,
16
- serializeModelOptions,
17
- WORKFLOW_DESERIALIZE,
18
- WORKFLOW_SERIALIZE
19
- } from "@ai-sdk/provider-utils";
20
-
21
- // src/fish-audio-error.ts
22
- import { createJsonErrorResponseHandler } from "@ai-sdk/provider-utils";
1
+ import { NoSuchModelError } from "@ai-sdk/provider";
2
+ import { WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE, combineHeaders, convertBase64ToUint8Array, createBinaryResponseHandler, createJsonErrorResponseHandler, createJsonResponseHandler, loadApiKey, mediaTypeToExtension, parseProviderOptions, postFormDataToApi, postJsonToApi, serializeModelOptions, withUserAgentSuffix } from "@ai-sdk/provider-utils";
23
3
  import { z } from "zod/v4";
24
- var fishAudioErrorDataSchema = z.object({
25
- status: z.number().nullish(),
26
- message: z.string().nullish()
4
+ //#region src/fish-audio-error.ts
5
+ const fishAudioErrorDataSchema = z.object({
6
+ status: z.number().nullish(),
7
+ message: z.string().nullish()
27
8
  });
28
- var fishAudioFailedResponseHandler = createJsonErrorResponseHandler({
29
- errorSchema: fishAudioErrorDataSchema,
30
- errorToMessage: (data) => data.message ?? "Unknown Fish Audio error"
9
+ const fishAudioFailedResponseHandler = createJsonErrorResponseHandler({
10
+ errorSchema: fishAudioErrorDataSchema,
11
+ errorToMessage: (data) => data.message ?? "Unknown Fish Audio error"
31
12
  });
32
-
33
- // src/fish-audio-speech-model-options.ts
34
- import { z as z2 } from "zod/v4";
35
- var fishAudioSpeechModelOptionsSchema = z2.object({
36
- /**
37
- * Voice model ID(s). A single ID selects one speaker; an array enables
38
- * multi-speaker dialogue (S2-Pro models), in which case the text must mark
39
- * turns with speaker tokens such as `<|speaker:0|>`. The speaker index maps
40
- * to the position in this array.
41
- *
42
- * Takes precedence over the top-level `voice` option.
43
- */
44
- referenceId: z2.union([z2.string(), z2.array(z2.string())]).optional(),
45
- /**
46
- * Output sample rate in Hz. Falls back to the format default when unset
47
- * (44100 Hz for wav/pcm/mp3, 48000 Hz for opus).
48
- */
49
- sampleRate: z2.number().int().positive().optional(),
50
- /**
51
- * Bitrate in kbps for mp3 output. Ignored for other formats.
52
- */
53
- mp3Bitrate: z2.union([z2.literal(64), z2.literal(128), z2.literal(192)]).optional(),
54
- /**
55
- * Bitrate in bps for opus output, where `-1000` selects automatic. Ignored
56
- * for other formats.
57
- */
58
- opusBitrate: z2.union([
59
- z2.literal(-1e3),
60
- z2.literal(24e3),
61
- z2.literal(32e3),
62
- z2.literal(48e3),
63
- z2.literal(64e3)
64
- ]).optional(),
65
- /**
66
- * Latency/quality tradeoff. `normal` gives the best quality, `balanced`
67
- * reduces latency, and `low` is the fastest.
68
- */
69
- latency: z2.enum(["low", "normal", "balanced"]).optional(),
70
- /**
71
- * Volume offset in dB. Negative values are quieter.
72
- */
73
- volume: z2.number().optional(),
74
- /**
75
- * Loudness normalization. Supported by the S2 family (`s2-pro` and
76
- * `s2.1-pro`). Fish Audio accepts it on `s1` but ignores it, so the provider
77
- * emits a warning in that case.
78
- */
79
- normalizeLoudness: z2.boolean().optional(),
80
- /**
81
- * Governs expressiveness. Higher values are more varied, lower values more
82
- * consistent.
83
- */
84
- temperature: z2.number().min(0).max(1).optional(),
85
- /**
86
- * Controls diversity via nucleus sampling.
87
- */
88
- topP: z2.number().min(0).max(1).optional(),
89
- /**
90
- * Text segment size for processing.
91
- */
92
- chunkLength: z2.number().int().min(100).max(300).optional(),
93
- /**
94
- * Minimum characters before splitting into a new chunk.
95
- */
96
- minChunkLength: z2.number().int().min(0).max(100).optional(),
97
- /**
98
- * Text normalization for English and Chinese. Helps stability with numbers.
99
- */
100
- normalize: z2.boolean().optional(),
101
- /**
102
- * Maximum audio tokens to generate per text chunk.
103
- */
104
- maxNewTokens: z2.number().int().positive().optional(),
105
- /**
106
- * Values above 1.0 discourage repeated audio patterns.
107
- */
108
- repetitionPenalty: z2.number().optional(),
109
- /**
110
- * Reuse prior audio as context for voice consistency across chunks.
111
- */
112
- conditionOnPreviousChunks: z2.boolean().optional(),
113
- /**
114
- * Early-stop threshold used in batch processing.
115
- */
116
- earlyStopThreshold: z2.number().min(0).max(1).optional(),
117
- /**
118
- * Request-scoped flags passed through to the inference backend, e.g.
119
- * `['quality-guard']`.
120
- */
121
- features: z2.array(z2.string()).optional()
13
+ //#endregion
14
+ //#region src/fish-audio-speech-model-options.ts
15
+ const fishAudioSpeechModelOptionsSchema = z.object({
16
+ /**
17
+ * Voice model ID(s). A single ID selects one speaker; an array enables
18
+ * multi-speaker dialogue (S2-Pro models), in which case the text must mark
19
+ * turns with speaker tokens such as `<|speaker:0|>`. The speaker index maps
20
+ * to the position in this array.
21
+ *
22
+ * Takes precedence over the top-level `voice` option.
23
+ */
24
+ referenceId: z.union([z.string(), z.array(z.string())]).optional(),
25
+ /**
26
+ * Output sample rate in Hz. Falls back to the format default when unset
27
+ * (44100 Hz for wav/pcm/mp3, 48000 Hz for opus).
28
+ */
29
+ sampleRate: z.number().int().positive().optional(),
30
+ /**
31
+ * Bitrate in kbps for mp3 output. Ignored for other formats.
32
+ */
33
+ mp3Bitrate: z.union([
34
+ z.literal(64),
35
+ z.literal(128),
36
+ z.literal(192)
37
+ ]).optional(),
38
+ /**
39
+ * Bitrate in bps for opus output, where `-1000` selects automatic. Ignored
40
+ * for other formats.
41
+ */
42
+ opusBitrate: z.union([
43
+ z.literal(-1e3),
44
+ z.literal(24e3),
45
+ z.literal(32e3),
46
+ z.literal(48e3),
47
+ z.literal(64e3)
48
+ ]).optional(),
49
+ /**
50
+ * Latency/quality tradeoff. `normal` gives the best quality, `balanced`
51
+ * reduces latency, and `low` is the fastest.
52
+ */
53
+ latency: z.enum([
54
+ "low",
55
+ "normal",
56
+ "balanced"
57
+ ]).optional(),
58
+ /**
59
+ * Volume offset in dB. Negative values are quieter.
60
+ */
61
+ volume: z.number().optional(),
62
+ /**
63
+ * Loudness normalization. Supported by the S2 family (`s2-pro` and
64
+ * `s2.1-pro`). Fish Audio accepts it on `s1` but ignores it, so the provider
65
+ * emits a warning in that case.
66
+ */
67
+ normalizeLoudness: z.boolean().optional(),
68
+ /**
69
+ * Governs expressiveness. Higher values are more varied, lower values more
70
+ * consistent.
71
+ */
72
+ temperature: z.number().min(0).max(1).optional(),
73
+ /**
74
+ * Controls diversity via nucleus sampling.
75
+ */
76
+ topP: z.number().min(0).max(1).optional(),
77
+ /**
78
+ * Text segment size for processing.
79
+ */
80
+ chunkLength: z.number().int().min(100).max(300).optional(),
81
+ /**
82
+ * Minimum characters before splitting into a new chunk.
83
+ */
84
+ minChunkLength: z.number().int().min(0).max(100).optional(),
85
+ /**
86
+ * Text normalization for English and Chinese. Helps stability with numbers.
87
+ */
88
+ normalize: z.boolean().optional(),
89
+ /**
90
+ * Maximum audio tokens to generate per text chunk.
91
+ */
92
+ maxNewTokens: z.number().int().positive().optional(),
93
+ /**
94
+ * Values above 1.0 discourage repeated audio patterns.
95
+ */
96
+ repetitionPenalty: z.number().optional(),
97
+ /**
98
+ * Reuse prior audio as context for voice consistency across chunks.
99
+ */
100
+ conditionOnPreviousChunks: z.boolean().optional(),
101
+ /**
102
+ * Early-stop threshold used in batch processing.
103
+ */
104
+ earlyStopThreshold: z.number().min(0).max(1).optional(),
105
+ /**
106
+ * Request-scoped flags passed through to the inference backend, e.g.
107
+ * `['quality-guard']`.
108
+ */
109
+ features: z.array(z.string()).optional()
122
110
  });
123
-
124
- // src/fish-audio-speech-model.ts
125
- var SUPPORTED_FORMATS = [
126
- "wav",
127
- "pcm",
128
- "mp3",
129
- "opus"
111
+ //#endregion
112
+ //#region src/fish-audio-speech-model.ts
113
+ const SUPPORTED_FORMATS = [
114
+ "wav",
115
+ "pcm",
116
+ "mp3",
117
+ "opus"
130
118
  ];
131
- var DEFAULT_FORMAT = "mp3";
132
- var MIN_SPEED = 0.5;
133
- var MAX_SPEED = 2;
134
- function resolveFormat({
135
- outputFormat,
136
- warnings
137
- }) {
138
- if (outputFormat == null) {
139
- return DEFAULT_FORMAT;
140
- }
141
- const normalized = outputFormat.toLowerCase();
142
- const matched = SUPPORTED_FORMATS.find((format) => format === normalized);
143
- if (matched == null) {
144
- warnings.push({
145
- type: "unsupported",
146
- feature: "outputFormat",
147
- details: `Fish Audio does not support the output format "${outputFormat}". Falling back to ${DEFAULT_FORMAT}. Supported formats are ${SUPPORTED_FORMATS.join(", ")}.`
148
- });
149
- return DEFAULT_FORMAT;
150
- }
151
- return matched;
119
+ const DEFAULT_FORMAT = "mp3";
120
+ const MIN_SPEED = .5;
121
+ const MAX_SPEED = 2;
122
+ function resolveFormat({ outputFormat, warnings }) {
123
+ if (outputFormat == null) return DEFAULT_FORMAT;
124
+ const normalized = outputFormat.toLowerCase();
125
+ const matched = SUPPORTED_FORMATS.find((format) => format === normalized);
126
+ if (matched == null) {
127
+ warnings.push({
128
+ type: "unsupported",
129
+ feature: "outputFormat",
130
+ details: `Fish Audio does not support the output format "${outputFormat}". Falling back to ${DEFAULT_FORMAT}. Supported formats are ${SUPPORTED_FORMATS.join(", ")}.`
131
+ });
132
+ return DEFAULT_FORMAT;
133
+ }
134
+ return matched;
152
135
  }
153
- var FishAudioSpeechModel = class _FishAudioSpeechModel {
154
- constructor(modelId, config) {
155
- this.modelId = modelId;
156
- this.config = config;
157
- this.specificationVersion = "v4";
158
- }
159
- get provider() {
160
- return this.config.provider;
161
- }
162
- static [WORKFLOW_SERIALIZE](model) {
163
- return serializeModelOptions({
164
- modelId: model.modelId,
165
- config: model.config
166
- });
167
- }
168
- static [WORKFLOW_DESERIALIZE](options) {
169
- return new _FishAudioSpeechModel(options.modelId, options.config);
170
- }
171
- async getArgs({
172
- text,
173
- voice,
174
- outputFormat,
175
- instructions,
176
- language,
177
- speed,
178
- providerOptions
179
- }) {
180
- const warnings = [];
181
- const fishAudioOptions = await parseProviderOptions({
182
- provider: "fishAudio",
183
- providerOptions,
184
- schema: fishAudioSpeechModelOptionsSchema
185
- });
186
- const format = resolveFormat({ outputFormat, warnings });
187
- const requestBody = {
188
- text,
189
- format
190
- };
191
- const referenceId = fishAudioOptions?.referenceId ?? voice;
192
- if (referenceId != null) {
193
- requestBody.reference_id = referenceId;
194
- }
195
- const prosody = {};
196
- if (speed != null) {
197
- if (speed >= MIN_SPEED && speed <= MAX_SPEED) {
198
- prosody.speed = speed;
199
- } else {
200
- warnings.push({
201
- type: "unsupported",
202
- feature: "speed",
203
- details: `Fish Audio speed must be between ${MIN_SPEED} and ${MAX_SPEED}. The speed option was ignored.`
204
- });
205
- }
206
- }
207
- if (fishAudioOptions?.volume != null) {
208
- prosody.volume = fishAudioOptions.volume;
209
- }
210
- if (fishAudioOptions?.normalizeLoudness != null) {
211
- if (this.modelId === "s1") {
212
- warnings.push({
213
- type: "unsupported",
214
- feature: "providerOptions.fishAudio.normalizeLoudness",
215
- details: "Fish Audio ignores normalizeLoudness on s1. It is supported by the S2 family (s2-pro, s2.1-pro)."
216
- });
217
- } else {
218
- prosody.normalize_loudness = fishAudioOptions.normalizeLoudness;
219
- }
220
- }
221
- if (Object.keys(prosody).length > 0) {
222
- requestBody.prosody = prosody;
223
- }
224
- if (language != null) {
225
- warnings.push({
226
- type: "unsupported",
227
- feature: "language",
228
- details: "Fish Audio infers the language from the input text and the selected voice, and has no language parameter. The language option was ignored."
229
- });
230
- }
231
- if (instructions != null) {
232
- warnings.push({
233
- type: "unsupported",
234
- feature: "instructions",
235
- details: "Fish Audio does not support instructions. The instructions option was ignored."
236
- });
237
- }
238
- if (fishAudioOptions != null) {
239
- if (fishAudioOptions.sampleRate != null) {
240
- requestBody.sample_rate = fishAudioOptions.sampleRate;
241
- }
242
- if (fishAudioOptions.mp3Bitrate != null) {
243
- if (format === "mp3") {
244
- requestBody.mp3_bitrate = fishAudioOptions.mp3Bitrate;
245
- } else {
246
- warnings.push({
247
- type: "unsupported",
248
- feature: "providerOptions.fishAudio.mp3Bitrate",
249
- details: `mp3Bitrate only applies to mp3 output. The option was ignored for ${format} output.`
250
- });
251
- }
252
- }
253
- if (fishAudioOptions.opusBitrate != null) {
254
- if (format === "opus") {
255
- requestBody.opus_bitrate = fishAudioOptions.opusBitrate;
256
- } else {
257
- warnings.push({
258
- type: "unsupported",
259
- feature: "providerOptions.fishAudio.opusBitrate",
260
- details: `opusBitrate only applies to opus output. The option was ignored for ${format} output.`
261
- });
262
- }
263
- }
264
- if (fishAudioOptions.latency != null) {
265
- requestBody.latency = fishAudioOptions.latency;
266
- }
267
- if (fishAudioOptions.temperature != null) {
268
- requestBody.temperature = fishAudioOptions.temperature;
269
- }
270
- if (fishAudioOptions.topP != null) {
271
- requestBody.top_p = fishAudioOptions.topP;
272
- }
273
- if (fishAudioOptions.chunkLength != null) {
274
- requestBody.chunk_length = fishAudioOptions.chunkLength;
275
- }
276
- if (fishAudioOptions.minChunkLength != null) {
277
- requestBody.min_chunk_length = fishAudioOptions.minChunkLength;
278
- }
279
- if (fishAudioOptions.normalize != null) {
280
- requestBody.normalize = fishAudioOptions.normalize;
281
- }
282
- if (fishAudioOptions.maxNewTokens != null) {
283
- requestBody.max_new_tokens = fishAudioOptions.maxNewTokens;
284
- }
285
- if (fishAudioOptions.repetitionPenalty != null) {
286
- requestBody.repetition_penalty = fishAudioOptions.repetitionPenalty;
287
- }
288
- if (fishAudioOptions.conditionOnPreviousChunks != null) {
289
- requestBody.condition_on_previous_chunks = fishAudioOptions.conditionOnPreviousChunks;
290
- }
291
- if (fishAudioOptions.earlyStopThreshold != null) {
292
- requestBody.early_stop_threshold = fishAudioOptions.earlyStopThreshold;
293
- }
294
- if (fishAudioOptions.features != null) {
295
- requestBody.features = fishAudioOptions.features;
296
- }
297
- }
298
- return { requestBody, warnings };
299
- }
300
- async doGenerate(options) {
301
- const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
302
- const { requestBody, warnings } = await this.getArgs(options);
303
- const {
304
- value: audio,
305
- responseHeaders,
306
- rawValue: rawResponse
307
- } = await postJsonToApi({
308
- url: this.config.url({ path: "/v1/tts", modelId: this.modelId }),
309
- // Fish Audio selects the TTS model with a `model` HTTP header rather
310
- // than a request body field.
311
- headers: combineHeaders(
312
- this.config.headers?.(),
313
- { model: this.modelId },
314
- options.headers
315
- ),
316
- body: requestBody,
317
- failedResponseHandler: fishAudioFailedResponseHandler,
318
- successfulResponseHandler: createBinaryResponseHandler(),
319
- abortSignal: options.abortSignal,
320
- fetch: this.config.fetch
321
- });
322
- return {
323
- audio,
324
- warnings,
325
- request: {
326
- body: JSON.stringify(requestBody)
327
- },
328
- response: {
329
- timestamp: currentDate,
330
- modelId: this.modelId,
331
- headers: responseHeaders,
332
- body: rawResponse
333
- }
334
- };
335
- }
136
+ var FishAudioSpeechModel = class FishAudioSpeechModel {
137
+ get provider() {
138
+ return this.config.provider;
139
+ }
140
+ static [WORKFLOW_SERIALIZE](model) {
141
+ return serializeModelOptions({
142
+ modelId: model.modelId,
143
+ config: model.config
144
+ });
145
+ }
146
+ static [WORKFLOW_DESERIALIZE](options) {
147
+ return new FishAudioSpeechModel(options.modelId, options.config);
148
+ }
149
+ constructor(modelId, config) {
150
+ this.modelId = modelId;
151
+ this.config = config;
152
+ this.specificationVersion = "v4";
153
+ }
154
+ async getArgs({ text, voice, outputFormat, instructions, language, speed, providerOptions }) {
155
+ const warnings = [];
156
+ const fishAudioOptions = await parseProviderOptions({
157
+ provider: "fishAudio",
158
+ providerOptions,
159
+ schema: fishAudioSpeechModelOptionsSchema
160
+ });
161
+ const format = resolveFormat({
162
+ outputFormat,
163
+ warnings
164
+ });
165
+ const requestBody = {
166
+ text,
167
+ format
168
+ };
169
+ const referenceId = fishAudioOptions?.referenceId ?? voice;
170
+ if (referenceId != null) requestBody.reference_id = referenceId;
171
+ const prosody = {};
172
+ if (speed != null) {
173
+ if (speed >= MIN_SPEED && speed <= MAX_SPEED) prosody.speed = speed;
174
+ else warnings.push({
175
+ type: "unsupported",
176
+ feature: "speed",
177
+ details: `Fish Audio speed must be between ${MIN_SPEED} and ${MAX_SPEED}. The speed option was ignored.`
178
+ });
179
+ }
180
+ if (fishAudioOptions?.volume != null) prosody.volume = fishAudioOptions.volume;
181
+ if (fishAudioOptions?.normalizeLoudness != null) {
182
+ if (this.modelId === "s1") warnings.push({
183
+ type: "unsupported",
184
+ feature: "providerOptions.fishAudio.normalizeLoudness",
185
+ details: "Fish Audio ignores normalizeLoudness on s1. It is supported by the S2 family (s2-pro, s2.1-pro)."
186
+ });
187
+ else prosody.normalize_loudness = fishAudioOptions.normalizeLoudness;
188
+ }
189
+ if (Object.keys(prosody).length > 0) requestBody.prosody = prosody;
190
+ if (language != null) warnings.push({
191
+ type: "unsupported",
192
+ feature: "language",
193
+ details: "Fish Audio infers the language from the input text and the selected voice, and has no language parameter. The language option was ignored."
194
+ });
195
+ if (instructions != null) warnings.push({
196
+ type: "unsupported",
197
+ feature: "instructions",
198
+ details: "Fish Audio does not support instructions. The instructions option was ignored."
199
+ });
200
+ if (fishAudioOptions != null) {
201
+ if (fishAudioOptions.sampleRate != null) requestBody.sample_rate = fishAudioOptions.sampleRate;
202
+ if (fishAudioOptions.mp3Bitrate != null) {
203
+ if (format === "mp3") requestBody.mp3_bitrate = fishAudioOptions.mp3Bitrate;
204
+ else warnings.push({
205
+ type: "unsupported",
206
+ feature: "providerOptions.fishAudio.mp3Bitrate",
207
+ details: `mp3Bitrate only applies to mp3 output. The option was ignored for ${format} output.`
208
+ });
209
+ }
210
+ if (fishAudioOptions.opusBitrate != null) {
211
+ if (format === "opus") requestBody.opus_bitrate = fishAudioOptions.opusBitrate;
212
+ else warnings.push({
213
+ type: "unsupported",
214
+ feature: "providerOptions.fishAudio.opusBitrate",
215
+ details: `opusBitrate only applies to opus output. The option was ignored for ${format} output.`
216
+ });
217
+ }
218
+ if (fishAudioOptions.latency != null) requestBody.latency = fishAudioOptions.latency;
219
+ if (fishAudioOptions.temperature != null) requestBody.temperature = fishAudioOptions.temperature;
220
+ if (fishAudioOptions.topP != null) requestBody.top_p = fishAudioOptions.topP;
221
+ if (fishAudioOptions.chunkLength != null) requestBody.chunk_length = fishAudioOptions.chunkLength;
222
+ if (fishAudioOptions.minChunkLength != null) requestBody.min_chunk_length = fishAudioOptions.minChunkLength;
223
+ if (fishAudioOptions.normalize != null) requestBody.normalize = fishAudioOptions.normalize;
224
+ if (fishAudioOptions.maxNewTokens != null) requestBody.max_new_tokens = fishAudioOptions.maxNewTokens;
225
+ if (fishAudioOptions.repetitionPenalty != null) requestBody.repetition_penalty = fishAudioOptions.repetitionPenalty;
226
+ if (fishAudioOptions.conditionOnPreviousChunks != null) requestBody.condition_on_previous_chunks = fishAudioOptions.conditionOnPreviousChunks;
227
+ if (fishAudioOptions.earlyStopThreshold != null) requestBody.early_stop_threshold = fishAudioOptions.earlyStopThreshold;
228
+ if (fishAudioOptions.features != null) requestBody.features = fishAudioOptions.features;
229
+ }
230
+ return {
231
+ requestBody,
232
+ warnings
233
+ };
234
+ }
235
+ async doGenerate(options) {
236
+ const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
237
+ const { requestBody, warnings } = await this.getArgs(options);
238
+ const { value: audio, responseHeaders, rawValue: rawResponse } = await postJsonToApi({
239
+ url: this.config.url({
240
+ path: "/v1/tts",
241
+ modelId: this.modelId
242
+ }),
243
+ headers: combineHeaders(this.config.headers?.(), { model: this.modelId }, options.headers),
244
+ body: requestBody,
245
+ failedResponseHandler: fishAudioFailedResponseHandler,
246
+ successfulResponseHandler: createBinaryResponseHandler(),
247
+ abortSignal: options.abortSignal,
248
+ fetch: this.config.fetch
249
+ });
250
+ return {
251
+ audio,
252
+ warnings,
253
+ request: { body: JSON.stringify(requestBody) },
254
+ response: {
255
+ timestamp: currentDate,
256
+ modelId: this.modelId,
257
+ headers: responseHeaders,
258
+ body: rawResponse
259
+ }
260
+ };
261
+ }
336
262
  };
337
-
338
- // src/fish-audio-transcription-model.ts
339
- import {
340
- combineHeaders as combineHeaders2,
341
- convertBase64ToUint8Array,
342
- createJsonResponseHandler,
343
- mediaTypeToExtension,
344
- parseProviderOptions as parseProviderOptions2,
345
- postFormDataToApi,
346
- serializeModelOptions as serializeModelOptions2,
347
- WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE2,
348
- WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE2
349
- } from "@ai-sdk/provider-utils";
350
- import { z as z4 } from "zod/v4";
351
-
352
- // src/fish-audio-transcription-model-options.ts
353
- import { z as z3 } from "zod/v4";
354
- var fishAudioTranscriptionModelOptionsSchema = z3.object({
355
- /**
356
- * Language of the audio.
357
- *
358
- * A hint only. Fish Audio passes it to the model, but auto-detection is
359
- * authoritative and overrides it, so this changes neither the transcript nor
360
- * the reported language. The detected language is reported as
361
- * `result.language`.
362
- */
363
- language: z3.string().optional(),
364
- /**
365
- * Whether to skip precise timestamps. Mirrors the Fish Audio
366
- * `ignore_timestamps` parameter, whose API default is `true`.
367
- *
368
- * This provider defaults it to `false` so that `result.segments` is
369
- * populated. Fish Audio documents an added latency cost for audio shorter
370
- * than 30 seconds; set this to `true` to trade segments for that latency.
371
- */
372
- ignoreTimestamps: z3.boolean().optional()
263
+ //#endregion
264
+ //#region src/fish-audio-transcription-model-options.ts
265
+ const fishAudioTranscriptionModelOptionsSchema = z.object({
266
+ /**
267
+ * Language of the audio.
268
+ *
269
+ * A hint only. Fish Audio passes it to the model, but auto-detection is
270
+ * authoritative and overrides it, so this changes neither the transcript nor
271
+ * the reported language. The detected language is reported as
272
+ * `result.language`.
273
+ */
274
+ language: z.string().optional(),
275
+ /**
276
+ * Whether to skip precise timestamps. Mirrors the Fish Audio
277
+ * `ignore_timestamps` parameter, whose API default is `true`.
278
+ *
279
+ * This provider defaults it to `false` so that `result.segments` is
280
+ * populated. Fish Audio documents an added latency cost for audio shorter
281
+ * than 30 seconds; set this to `true` to trade segments for that latency.
282
+ */
283
+ ignoreTimestamps: z.boolean().optional()
373
284
  });
374
-
375
- // src/fish-audio-transcription-model.ts
376
- var FishAudioTranscriptionModel = class _FishAudioTranscriptionModel {
377
- constructor(modelId, config) {
378
- this.modelId = modelId;
379
- this.config = config;
380
- this.specificationVersion = "v4";
381
- }
382
- get provider() {
383
- return this.config.provider;
384
- }
385
- static [WORKFLOW_SERIALIZE2](model) {
386
- return serializeModelOptions2({
387
- modelId: model.modelId,
388
- config: model.config
389
- });
390
- }
391
- static [WORKFLOW_DESERIALIZE2](options) {
392
- return new _FishAudioTranscriptionModel(options.modelId, options.config);
393
- }
394
- async getArgs({
395
- audio,
396
- mediaType,
397
- providerOptions
398
- }) {
399
- const warnings = [];
400
- const fishAudioOptions = await parseProviderOptions2({
401
- provider: "fishAudio",
402
- providerOptions,
403
- schema: fishAudioTranscriptionModelOptionsSchema
404
- });
405
- const formData = new FormData();
406
- const blob = audio instanceof Uint8Array ? new Blob([audio]) : new Blob([convertBase64ToUint8Array(audio)]);
407
- formData.append(
408
- "audio",
409
- new File([blob], "audio", { type: mediaType }),
410
- `audio.${mediaTypeToExtension(mediaType)}`
411
- );
412
- if (fishAudioOptions?.language != null) {
413
- formData.append("language", fishAudioOptions.language);
414
- }
415
- formData.append(
416
- "ignore_timestamps",
417
- String(fishAudioOptions?.ignoreTimestamps ?? false)
418
- );
419
- return { formData, warnings };
420
- }
421
- async doGenerate(options) {
422
- const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
423
- const { formData, warnings } = await this.getArgs(options);
424
- const {
425
- value: response,
426
- responseHeaders,
427
- rawValue: rawResponse
428
- } = await postFormDataToApi({
429
- url: this.config.url({ path: "/v1/asr", modelId: this.modelId }),
430
- headers: combineHeaders2(this.config.headers?.(), options.headers),
431
- formData,
432
- failedResponseHandler: fishAudioFailedResponseHandler,
433
- successfulResponseHandler: createJsonResponseHandler(
434
- fishAudioTranscriptionResponseSchema
435
- ),
436
- abortSignal: options.abortSignal,
437
- fetch: this.config.fetch
438
- });
439
- const segments = response.segments?.map((segment) => ({
440
- text: segment.text,
441
- startSecond: segment.start,
442
- endSecond: segment.end
443
- })) ?? [];
444
- return {
445
- text: response.text,
446
- segments,
447
- // `language_code` is absent from the documented response schema but is
448
- // returned in practice, and reflects the detected language rather than
449
- // the requested one.
450
- language: response.language_code ?? void 0,
451
- durationInSeconds: response.duration ?? void 0,
452
- warnings,
453
- response: {
454
- timestamp: currentDate,
455
- modelId: this.modelId,
456
- headers: responseHeaders,
457
- body: rawResponse
458
- },
459
- ...response.language != null && {
460
- providerMetadata: {
461
- fishAudio: {
462
- // Human-readable display name, e.g. `English`. Its exact form is
463
- // not guaranteed, so `language` above (the ISO-639-1 code) is the
464
- // value to branch on.
465
- language: response.language
466
- }
467
- }
468
- }
469
- };
470
- }
285
+ //#endregion
286
+ //#region src/fish-audio-transcription-model.ts
287
+ var FishAudioTranscriptionModel = class FishAudioTranscriptionModel {
288
+ get provider() {
289
+ return this.config.provider;
290
+ }
291
+ static [WORKFLOW_SERIALIZE](model) {
292
+ return serializeModelOptions({
293
+ modelId: model.modelId,
294
+ config: model.config
295
+ });
296
+ }
297
+ static [WORKFLOW_DESERIALIZE](options) {
298
+ return new FishAudioTranscriptionModel(options.modelId, options.config);
299
+ }
300
+ constructor(modelId, config) {
301
+ this.modelId = modelId;
302
+ this.config = config;
303
+ this.specificationVersion = "v4";
304
+ }
305
+ async getArgs({ audio, mediaType, providerOptions }) {
306
+ const warnings = [];
307
+ const fishAudioOptions = await parseProviderOptions({
308
+ provider: "fishAudio",
309
+ providerOptions,
310
+ schema: fishAudioTranscriptionModelOptionsSchema
311
+ });
312
+ const formData = new FormData();
313
+ const blob = audio instanceof Uint8Array ? new Blob([audio]) : new Blob([convertBase64ToUint8Array(audio)]);
314
+ formData.append("audio", new File([blob], "audio", { type: mediaType }), `audio.${mediaTypeToExtension(mediaType)}`);
315
+ if (fishAudioOptions?.language != null) formData.append("language", fishAudioOptions.language);
316
+ formData.append("ignore_timestamps", String(fishAudioOptions?.ignoreTimestamps ?? false));
317
+ return {
318
+ formData,
319
+ warnings
320
+ };
321
+ }
322
+ async doGenerate(options) {
323
+ const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
324
+ const { formData, warnings } = await this.getArgs(options);
325
+ const { value: response, responseHeaders, rawValue: rawResponse } = await postFormDataToApi({
326
+ url: this.config.url({
327
+ path: "/v1/asr",
328
+ modelId: this.modelId
329
+ }),
330
+ headers: combineHeaders(this.config.headers?.(), options.headers),
331
+ formData,
332
+ failedResponseHandler: fishAudioFailedResponseHandler,
333
+ successfulResponseHandler: createJsonResponseHandler(fishAudioTranscriptionResponseSchema),
334
+ abortSignal: options.abortSignal,
335
+ fetch: this.config.fetch
336
+ });
337
+ const segments = response.segments?.map((segment) => ({
338
+ text: segment.text,
339
+ startSecond: segment.start,
340
+ endSecond: segment.end
341
+ })) ?? [];
342
+ return {
343
+ text: response.text,
344
+ segments,
345
+ language: response.language_code ?? void 0,
346
+ durationInSeconds: response.duration ?? void 0,
347
+ warnings,
348
+ response: {
349
+ timestamp: currentDate,
350
+ modelId: this.modelId,
351
+ headers: responseHeaders,
352
+ body: rawResponse
353
+ },
354
+ ...response.language != null && { providerMetadata: { fishAudio: { language: response.language } } }
355
+ };
356
+ }
471
357
  };
472
- var fishAudioTranscriptionResponseSchema = z4.object({
473
- text: z4.string(),
474
- // `language` and `language_code` are undocumented but returned in practice.
475
- // Human-readable name, e.g. `English`.
476
- language: z4.string().nullish(),
477
- // ISO-639-1 code, e.g. `en`.
478
- language_code: z4.string().nullish(),
479
- duration: z4.number().nullish(),
480
- segments: z4.array(
481
- z4.object({
482
- text: z4.string(),
483
- start: z4.number(),
484
- end: z4.number()
485
- })
486
- ).nullish()
358
+ const fishAudioTranscriptionResponseSchema = z.object({
359
+ text: z.string(),
360
+ language: z.string().nullish(),
361
+ language_code: z.string().nullish(),
362
+ duration: z.number().nullish(),
363
+ segments: z.array(z.object({
364
+ text: z.string(),
365
+ start: z.number(),
366
+ end: z.number()
367
+ })).nullish()
487
368
  });
488
-
489
- // src/version.ts
490
- var VERSION = true ? "3.0.29" : "0.0.0-test";
491
-
492
- // src/fish-audio-provider.ts
493
- var DEFAULT_BASE_URL = "https://api.fish.audio";
369
+ //#endregion
370
+ //#region src/version.ts
371
+ const VERSION = "3.0.30";
372
+ //#endregion
373
+ //#region src/fish-audio-provider.ts
374
+ const DEFAULT_BASE_URL = "https://api.fish.audio";
375
+ /**
376
+ * Create a Fish Audio provider instance.
377
+ */
494
378
  function createFishAudio(options = {}) {
495
- const baseURL = options.baseURL?.replace(/\/$/, "") ?? DEFAULT_BASE_URL;
496
- const getHeaders = () => withUserAgentSuffix(
497
- {
498
- Authorization: `Bearer ${loadApiKey({
499
- apiKey: options.apiKey,
500
- environmentVariableName: "FISH_AUDIO_API_KEY",
501
- description: "Fish Audio"
502
- })}`,
503
- ...options.headers
504
- },
505
- `ai-sdk-fish-audio/${VERSION}`
506
- );
507
- const createSpeechModel = (modelId) => new FishAudioSpeechModel(modelId, {
508
- provider: "fish-audio.speech",
509
- url: ({ path }) => `${baseURL}${path}`,
510
- headers: getHeaders,
511
- fetch: options.fetch
512
- });
513
- const createTranscriptionModel = (modelId = "transcribe-1") => new FishAudioTranscriptionModel(modelId, {
514
- provider: "fish-audio.transcription",
515
- url: ({ path }) => `${baseURL}${path}`,
516
- headers: getHeaders,
517
- fetch: options.fetch
518
- });
519
- const provider = function(modelId) {
520
- return {
521
- speech: createSpeechModel(modelId)
522
- };
523
- };
524
- provider.specificationVersion = "v4";
525
- provider.speech = createSpeechModel;
526
- provider.speechModel = createSpeechModel;
527
- provider.transcription = createTranscriptionModel;
528
- provider.transcriptionModel = createTranscriptionModel;
529
- provider.languageModel = (modelId) => {
530
- throw new NoSuchModelError({
531
- modelId,
532
- modelType: "languageModel",
533
- message: "Fish Audio does not provide language models"
534
- });
535
- };
536
- provider.embeddingModel = (modelId) => {
537
- throw new NoSuchModelError({
538
- modelId,
539
- modelType: "embeddingModel",
540
- message: "Fish Audio does not provide embedding models"
541
- });
542
- };
543
- provider.textEmbeddingModel = provider.embeddingModel;
544
- provider.imageModel = (modelId) => {
545
- throw new NoSuchModelError({
546
- modelId,
547
- modelType: "imageModel",
548
- message: "Fish Audio does not provide image models"
549
- });
550
- };
551
- return provider;
379
+ const baseURL = options.baseURL?.replace(/\/$/, "") ?? DEFAULT_BASE_URL;
380
+ const getHeaders = () => withUserAgentSuffix({
381
+ Authorization: `Bearer ${loadApiKey({
382
+ apiKey: options.apiKey,
383
+ environmentVariableName: "FISH_AUDIO_API_KEY",
384
+ description: "Fish Audio"
385
+ })}`,
386
+ ...options.headers
387
+ }, `ai-sdk-fish-audio/${VERSION}`);
388
+ const createSpeechModel = (modelId) => new FishAudioSpeechModel(modelId, {
389
+ provider: "fish-audio.speech",
390
+ url: ({ path }) => `${baseURL}${path}`,
391
+ headers: getHeaders,
392
+ fetch: options.fetch
393
+ });
394
+ const createTranscriptionModel = (modelId = "transcribe-1") => new FishAudioTranscriptionModel(modelId, {
395
+ provider: "fish-audio.transcription",
396
+ url: ({ path }) => `${baseURL}${path}`,
397
+ headers: getHeaders,
398
+ fetch: options.fetch
399
+ });
400
+ const provider = function(modelId) {
401
+ return { speech: createSpeechModel(modelId) };
402
+ };
403
+ provider.specificationVersion = "v4";
404
+ provider.speech = createSpeechModel;
405
+ provider.speechModel = createSpeechModel;
406
+ provider.transcription = createTranscriptionModel;
407
+ provider.transcriptionModel = createTranscriptionModel;
408
+ provider.languageModel = (modelId) => {
409
+ throw new NoSuchModelError({
410
+ modelId,
411
+ modelType: "languageModel",
412
+ message: "Fish Audio does not provide language models"
413
+ });
414
+ };
415
+ provider.embeddingModel = (modelId) => {
416
+ throw new NoSuchModelError({
417
+ modelId,
418
+ modelType: "embeddingModel",
419
+ message: "Fish Audio does not provide embedding models"
420
+ });
421
+ };
422
+ provider.textEmbeddingModel = provider.embeddingModel;
423
+ provider.imageModel = (modelId) => {
424
+ throw new NoSuchModelError({
425
+ modelId,
426
+ modelType: "imageModel",
427
+ message: "Fish Audio does not provide image models"
428
+ });
429
+ };
430
+ return provider;
552
431
  }
553
- var fishAudio = createFishAudio();
554
- export {
555
- FishAudioSpeechModel,
556
- FishAudioTranscriptionModel,
557
- VERSION,
558
- createFishAudio,
559
- fishAudio
560
- };
432
+ /**
433
+ * Default Fish Audio provider instance.
434
+ */
435
+ const fishAudio = createFishAudio();
436
+ //#endregion
437
+ export { FishAudioSpeechModel, FishAudioTranscriptionModel, VERSION, createFishAudio, fishAudio };
438
+
561
439
  //# sourceMappingURL=index.js.map