@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/CHANGELOG.md +9 -0
- package/dist/index.d.ts +134 -126
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +426 -548
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -1,561 +1,439 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
9
|
+
const fishAudioFailedResponseHandler = createJsonErrorResponseHandler({
|
|
10
|
+
errorSchema: fishAudioErrorDataSchema,
|
|
11
|
+
errorToMessage: (data) => data.message ?? "Unknown Fish Audio error"
|
|
31
12
|
});
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
function resolveFormat({
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
376
|
-
var FishAudioTranscriptionModel = class
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|