@ai-sdk/fish-audio 0.0.0 → 2.0.1
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 +14 -0
- package/LICENSE +13 -0
- package/README.md +61 -3
- package/dist/index.d.ts +159 -0
- package/dist/index.js +545 -0
- package/dist/index.js.map +1 -0
- package/docs/190-fish-audio.mdx +346 -0
- package/package.json +71 -15
- package/src/fish-audio-config.ts +8 -0
- package/src/fish-audio-error.ts +17 -0
- package/src/fish-audio-provider.ts +163 -0
- package/src/fish-audio-speech-api-types.ts +126 -0
- package/src/fish-audio-speech-model-options.ts +115 -0
- package/src/fish-audio-speech-model.ts +280 -0
- package/src/fish-audio-speech-options.ts +21 -0
- package/src/fish-audio-transcription-model-options.ts +28 -0
- package/src/fish-audio-transcription-model.ts +149 -0
- package/src/fish-audio-transcription-options.ts +11 -0
- package/src/index.ts +15 -0
- package/src/version.ts +6 -0
- package/index.js +0 -1
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Fish Audio
|
|
3
|
+
description: Learn how to use the Fish Audio provider for the AI SDK.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Fish Audio Provider
|
|
7
|
+
|
|
8
|
+
The [Fish Audio](https://fish.audio/) provider contains speech generation (S1 and
|
|
9
|
+
S2 models) and speech-to-text transcription support.
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
The Fish Audio provider is available in the `@ai-sdk/fish-audio` module. You can install it with
|
|
14
|
+
|
|
15
|
+
<Tabs items={['pnpm', 'npm', 'yarn', 'bun']}>
|
|
16
|
+
<Tab>
|
|
17
|
+
<Snippet text="pnpm add @ai-sdk/fish-audio" dark />
|
|
18
|
+
</Tab>
|
|
19
|
+
<Tab>
|
|
20
|
+
<Snippet text="npm install @ai-sdk/fish-audio" dark />
|
|
21
|
+
</Tab>
|
|
22
|
+
<Tab>
|
|
23
|
+
<Snippet text="yarn add @ai-sdk/fish-audio" dark />
|
|
24
|
+
</Tab>
|
|
25
|
+
|
|
26
|
+
<Tab>
|
|
27
|
+
<Snippet text="bun add @ai-sdk/fish-audio" dark />
|
|
28
|
+
</Tab>
|
|
29
|
+
</Tabs>
|
|
30
|
+
|
|
31
|
+
## Provider Instance
|
|
32
|
+
|
|
33
|
+
You can import the default provider instance `fishAudio` from `@ai-sdk/fish-audio`:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { fishAudio } from '@ai-sdk/fish-audio';
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If you need a customized setup, you can import `createFishAudio` from `@ai-sdk/fish-audio` and create a provider instance with your settings:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { createFishAudio } from '@ai-sdk/fish-audio';
|
|
43
|
+
|
|
44
|
+
const fishAudio = createFishAudio({
|
|
45
|
+
// custom settings, e.g.
|
|
46
|
+
fetch: customFetch,
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
You can use the following optional settings to customize the Fish Audio provider instance:
|
|
51
|
+
|
|
52
|
+
- **apiKey** _string_
|
|
53
|
+
|
|
54
|
+
API key that is being sent using the `Authorization` header.
|
|
55
|
+
It defaults to the `FISH_AUDIO_API_KEY` environment variable.
|
|
56
|
+
|
|
57
|
+
- **baseURL** _string_
|
|
58
|
+
|
|
59
|
+
Base URL for the API calls.
|
|
60
|
+
Defaults to `https://api.fish.audio`.
|
|
61
|
+
|
|
62
|
+
- **headers** _Record<string,string>_
|
|
63
|
+
|
|
64
|
+
Custom headers to include in the requests.
|
|
65
|
+
|
|
66
|
+
- **fetch** _(input: RequestInfo, init?: RequestInit) => Promise<Response>_
|
|
67
|
+
|
|
68
|
+
Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
|
|
69
|
+
Defaults to the global `fetch` function.
|
|
70
|
+
You can use it as a middleware to intercept requests,
|
|
71
|
+
or to provide a custom fetch implementation for e.g. testing.
|
|
72
|
+
|
|
73
|
+
## Speech Models
|
|
74
|
+
|
|
75
|
+
You can create models that call the [Fish Audio text-to-speech API](https://docs.fish.audio/api-reference/endpoint/openapi-v1/text-to-speech)
|
|
76
|
+
using the `.speech()` factory method.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { fishAudio } from '@ai-sdk/fish-audio';
|
|
80
|
+
import { generateSpeech } from 'ai';
|
|
81
|
+
|
|
82
|
+
const { audio } = await generateSpeech({
|
|
83
|
+
model: fishAudio.speech('s1'),
|
|
84
|
+
text: 'Hello from Fish Audio!',
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The `voice` option selects a Fish Audio voice model ID (`reference_id`), either
|
|
89
|
+
from the [Fish Audio voice library](https://fish.audio/) or one of your own
|
|
90
|
+
uploaded models. Omit it to use the default voice.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
const { audio } = await generateSpeech({
|
|
94
|
+
model: fishAudio.speech('s1'),
|
|
95
|
+
text: 'Hello from Fish Audio!',
|
|
96
|
+
voice: '933563129e564b19a115bedd57b7406a',
|
|
97
|
+
outputFormat: 'opus',
|
|
98
|
+
speed: 1.1,
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Voice listing is not part of the AI SDK speech model specification, so browse
|
|
103
|
+
voices with the [Fish Audio list-models
|
|
104
|
+
endpoint](https://docs.fish.audio/api-reference/endpoint/openapi-v1/list-models)
|
|
105
|
+
directly:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const response = await fetch(
|
|
109
|
+
'https://api.fish.audio/model?page_size=20&sort_by=task_count',
|
|
110
|
+
{ headers: { Authorization: `Bearer ${process.env.FISH_AUDIO_API_KEY}` } },
|
|
111
|
+
);
|
|
112
|
+
const { items } = await response.json();
|
|
113
|
+
// Each item's `_id` is a value you can pass as `voice`.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Add `self=true` to list only your own uploaded models, or `language=en` / `tag=narration` to filter.
|
|
117
|
+
|
|
118
|
+
### Provider Options
|
|
119
|
+
|
|
120
|
+
The following provider options are available:
|
|
121
|
+
|
|
122
|
+
- **referenceId** _string | array of strings_
|
|
123
|
+
|
|
124
|
+
Voice model ID(s). A single ID selects one speaker; an array enables
|
|
125
|
+
multi-speaker dialogue (S2-Pro models). Takes precedence over the top-level
|
|
126
|
+
`voice` option.
|
|
127
|
+
Optional.
|
|
128
|
+
|
|
129
|
+
- **sampleRate** _number_
|
|
130
|
+
|
|
131
|
+
Output sample rate in Hz. Falls back to the format default when unset
|
|
132
|
+
(44100 Hz for `wav`/`pcm`/`mp3`, 48000 Hz for `opus`).
|
|
133
|
+
Optional.
|
|
134
|
+
|
|
135
|
+
- **mp3Bitrate** _64 | 128 | 192_
|
|
136
|
+
|
|
137
|
+
Bitrate in kbps for `mp3` output. Ignored for other formats.
|
|
138
|
+
Optional.
|
|
139
|
+
|
|
140
|
+
- **opusBitrate** _-1000 | 24000 | 32000 | 48000 | 64000_
|
|
141
|
+
|
|
142
|
+
Bitrate in bps for `opus` output, where `-1000` selects automatic. Ignored for
|
|
143
|
+
other formats.
|
|
144
|
+
Optional.
|
|
145
|
+
|
|
146
|
+
- **latency** _'low' | 'normal' | 'balanced'_
|
|
147
|
+
|
|
148
|
+
Latency/quality tradeoff. `normal` gives the best quality, `balanced` reduces
|
|
149
|
+
latency, and `low` is the fastest.
|
|
150
|
+
Optional.
|
|
151
|
+
|
|
152
|
+
- **volume** _number_
|
|
153
|
+
|
|
154
|
+
Volume offset in dB. Negative values are quieter.
|
|
155
|
+
Optional.
|
|
156
|
+
|
|
157
|
+
- **normalizeLoudness** _boolean_
|
|
158
|
+
|
|
159
|
+
Loudness normalization. Supported by the S2 family (`s2-pro` and
|
|
160
|
+
`s2.1-pro`). Fish Audio accepts it on `s1` but ignores it, so the provider
|
|
161
|
+
drops it and emits a warning in that case.
|
|
162
|
+
Optional.
|
|
163
|
+
|
|
164
|
+
- **temperature** _number_
|
|
165
|
+
|
|
166
|
+
Governs expressiveness (0 to 1). Higher values are more varied.
|
|
167
|
+
Optional.
|
|
168
|
+
|
|
169
|
+
- **topP** _number_
|
|
170
|
+
|
|
171
|
+
Controls diversity via nucleus sampling (0 to 1).
|
|
172
|
+
Optional.
|
|
173
|
+
|
|
174
|
+
- **chunkLength** _number_
|
|
175
|
+
|
|
176
|
+
Text segment size for processing (100 to 300).
|
|
177
|
+
Optional.
|
|
178
|
+
|
|
179
|
+
- **minChunkLength** _number_
|
|
180
|
+
|
|
181
|
+
Minimum characters before splitting into a new chunk (0 to 100).
|
|
182
|
+
Optional.
|
|
183
|
+
|
|
184
|
+
- **normalize** _boolean_
|
|
185
|
+
|
|
186
|
+
Text normalization for English and Chinese. Helps stability with numbers.
|
|
187
|
+
Optional.
|
|
188
|
+
|
|
189
|
+
- **maxNewTokens** _number_
|
|
190
|
+
|
|
191
|
+
Maximum audio tokens to generate per text chunk.
|
|
192
|
+
Optional.
|
|
193
|
+
|
|
194
|
+
- **repetitionPenalty** _number_
|
|
195
|
+
|
|
196
|
+
Values above 1.0 discourage repeated audio patterns.
|
|
197
|
+
Optional.
|
|
198
|
+
|
|
199
|
+
- **conditionOnPreviousChunks** _boolean_
|
|
200
|
+
|
|
201
|
+
Reuse prior audio as context for voice consistency across chunks.
|
|
202
|
+
Optional.
|
|
203
|
+
|
|
204
|
+
- **earlyStopThreshold** _number_
|
|
205
|
+
|
|
206
|
+
Early-stop threshold used in batch processing (0 to 1).
|
|
207
|
+
Optional.
|
|
208
|
+
|
|
209
|
+
- **features** _array of strings_
|
|
210
|
+
|
|
211
|
+
Request-scoped flags passed through to the inference backend, e.g.
|
|
212
|
+
`['quality-guard']`.
|
|
213
|
+
Optional.
|
|
214
|
+
|
|
215
|
+
### Multi-Speaker Dialogue
|
|
216
|
+
|
|
217
|
+
S2-Pro models support multi-speaker dialogue. Pass an array of voice model IDs
|
|
218
|
+
via `referenceId` and mark turns in the text with `<|speaker:N|>`, where `N`
|
|
219
|
+
indexes into that array.
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
const { audio } = await generateSpeech({
|
|
223
|
+
model: fishAudio.speech('s2-pro'),
|
|
224
|
+
text: '<|speaker:0|>Hello!<|speaker:1|>Hi there!',
|
|
225
|
+
providerOptions: {
|
|
226
|
+
fishAudio: {
|
|
227
|
+
referenceId: [
|
|
228
|
+
'933563129e564b19a115bedd57b7406a',
|
|
229
|
+
'bf322df2096a46f18c579d0baa36f41d',
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Output Formats
|
|
237
|
+
|
|
238
|
+
Fish Audio supports the `wav`, `pcm`, `mp3`, and `opus` output formats. Any other
|
|
239
|
+
value falls back to `mp3` and produces a warning.
|
|
240
|
+
|
|
241
|
+
<Note>
|
|
242
|
+
Fish Audio infers the language from the input text and the selected voice, and
|
|
243
|
+
has no language parameter. The `language` and `instructions` options are not
|
|
244
|
+
supported and produce warnings.
|
|
245
|
+
</Note>
|
|
246
|
+
|
|
247
|
+
### Model Capabilities
|
|
248
|
+
|
|
249
|
+
| Model | Multi-Speaker | Notes |
|
|
250
|
+
| --------------- | ------------------- | ------------------------------------------------------------------------- |
|
|
251
|
+
| `s1` | | Ignores `normalizeLoudness` |
|
|
252
|
+
| `s2-pro` | <Check size={18} /> | Supports `normalizeLoudness` |
|
|
253
|
+
| `s2.1-pro` | <Check size={18} /> | Recommended default; supports `normalizeLoudness` |
|
|
254
|
+
| `s2.1-pro-free` | | Free developer tier; no time-to-first-audio or data-processing guarantees |
|
|
255
|
+
|
|
256
|
+
<Note>
|
|
257
|
+
Streaming text-to-speech (Fish Audio's TTS-live WebSocket and timestamped
|
|
258
|
+
streaming endpoints) is not currently supported, and neither is inline
|
|
259
|
+
zero-shot voice cloning via `references`, which requires a MessagePack request
|
|
260
|
+
body. Upload reference audio to Fish Audio and pass its `reference_id` via
|
|
261
|
+
`voice` or `referenceId` instead.
|
|
262
|
+
</Note>
|
|
263
|
+
|
|
264
|
+
## Transcription Models
|
|
265
|
+
|
|
266
|
+
You can create models that call the [Fish Audio speech-to-text API](https://docs.fish.audio/api-reference/endpoint/openapi-v1/speech-to-text)
|
|
267
|
+
using the `.transcription()` factory method.
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
import { fishAudio } from '@ai-sdk/fish-audio';
|
|
271
|
+
import { transcribe } from 'ai';
|
|
272
|
+
import { readFile } from 'node:fs/promises';
|
|
273
|
+
|
|
274
|
+
const result = await transcribe({
|
|
275
|
+
model: fishAudio.transcription(),
|
|
276
|
+
audio: await readFile('audio.mp3'),
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The Fish Audio speech-to-text endpoint currently exposes no model selector and
|
|
281
|
+
serves a single model, so the model ID is optional and defaults to
|
|
282
|
+
`'transcribe-1'`. It is a routing label and is not sent to the API. Fish Audio
|
|
283
|
+
expects to add more ASR models and to select them with the `model` HTTP header,
|
|
284
|
+
matching the text-to-speech endpoint.
|
|
285
|
+
|
|
286
|
+
### Provider Options
|
|
287
|
+
|
|
288
|
+
The following provider options are available:
|
|
289
|
+
|
|
290
|
+
- **language** _string_
|
|
291
|
+
|
|
292
|
+
Language of the audio. A hint only: Fish Audio passes it to the model, but
|
|
293
|
+
auto-detection is authoritative and overrides it, so it changes neither the
|
|
294
|
+
transcript nor the reported language.
|
|
295
|
+
Optional.
|
|
296
|
+
|
|
297
|
+
- **ignoreTimestamps** _boolean_
|
|
298
|
+
|
|
299
|
+
Whether to skip precise timestamps. Mirrors the Fish Audio
|
|
300
|
+
`ignore_timestamps` parameter, whose API default is `true`. This provider
|
|
301
|
+
defaults it to `false` so that `segments` is populated. Fish Audio documents
|
|
302
|
+
an added latency cost for audio shorter than 30 seconds; set this to `true` to
|
|
303
|
+
trade segments for that latency.
|
|
304
|
+
Optional.
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
const result = await transcribe({
|
|
308
|
+
model: fishAudio.transcription(),
|
|
309
|
+
audio: await readFile('audio.mp3'),
|
|
310
|
+
providerOptions: {
|
|
311
|
+
fishAudio: {
|
|
312
|
+
language: 'en',
|
|
313
|
+
ignoreTimestamps: false,
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
`result.language` reports the detected language as an ISO-639-1 code (e.g. `en`).
|
|
320
|
+
It is always a two-letter code, never a locale such as `en-US`, and is
|
|
321
|
+
`undefined` when Fish Audio detects no language.
|
|
322
|
+
|
|
323
|
+
The human-readable language name (e.g. `English`) is available as provider
|
|
324
|
+
metadata:
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
console.log(result.language); // 'en'
|
|
328
|
+
console.log(result.providerMetadata?.fishAudio?.language); // 'English'
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
<Note>
|
|
332
|
+
The provider metadata `language` is a display name intended for presentation.
|
|
333
|
+
Its exact form is not guaranteed, so avoid matching or branching on it — use
|
|
334
|
+
`result.language` for anything programmatic.
|
|
335
|
+
</Note>
|
|
336
|
+
|
|
337
|
+
<Note>
|
|
338
|
+
Setting `ignoreTimestamps` to `true` makes Fish Audio return an empty
|
|
339
|
+
`segments` array. The provider therefore requests timestamps by default.
|
|
340
|
+
</Note>
|
|
341
|
+
|
|
342
|
+
### Model Capabilities
|
|
343
|
+
|
|
344
|
+
| Model | Transcription | Duration | Segments | Language |
|
|
345
|
+
| -------------- | ------------------- | ------------------- | ------------------- | ------------------- |
|
|
346
|
+
| `transcribe-1` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
package/package.json
CHANGED
|
@@ -1,20 +1,76 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/fish-audio",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "AI SDK provider for Fish Audio",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
-
},
|
|
9
|
-
"keywords": [],
|
|
10
|
-
"author": "",
|
|
11
|
-
"license": "Apache-2.0",
|
|
3
|
+
"version": "2.0.1",
|
|
12
4
|
"type": "module",
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
16
9
|
"files": [
|
|
17
|
-
"
|
|
10
|
+
"dist/**/*",
|
|
11
|
+
"docs/**/*",
|
|
12
|
+
"src",
|
|
13
|
+
"!src/**/*.test.ts",
|
|
14
|
+
"!src/**/*.test-d.ts",
|
|
15
|
+
"!src/**/__snapshots__",
|
|
16
|
+
"!src/**/__fixtures__",
|
|
17
|
+
"CHANGELOG.md",
|
|
18
18
|
"README.md"
|
|
19
|
-
]
|
|
20
|
-
|
|
19
|
+
],
|
|
20
|
+
"directories": {
|
|
21
|
+
"doc": "./docs"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
"./package.json": "./package.json",
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@ai-sdk/provider": "3.0.14",
|
|
33
|
+
"@ai-sdk/provider-utils": "4.0.42"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "20.17.24",
|
|
37
|
+
"tsup": "^8",
|
|
38
|
+
"typescript": "5.6.3",
|
|
39
|
+
"zod": "3.25.76",
|
|
40
|
+
"@ai-sdk/test-server": "1.0.6",
|
|
41
|
+
"@vercel/ai-tsconfig": "0.0.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"zod": "^3.25.76 || ^4.1.8"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public",
|
|
51
|
+
"provenance": true
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://ai-sdk.dev/docs",
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "https://github.com/vercel/ai",
|
|
57
|
+
"directory": "packages/fish-audio"
|
|
58
|
+
},
|
|
59
|
+
"bugs": {
|
|
60
|
+
"url": "https://github.com/vercel/ai/issues"
|
|
61
|
+
},
|
|
62
|
+
"keywords": [
|
|
63
|
+
"ai"
|
|
64
|
+
],
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "tsup --tsconfig tsconfig.build.json",
|
|
67
|
+
"build:watch": "tsup --tsconfig tsconfig.build.json --watch",
|
|
68
|
+
"clean": "del-cli dist docs",
|
|
69
|
+
"type-check": "tsc --noEmit",
|
|
70
|
+
"test": "pnpm test:node && pnpm test:edge",
|
|
71
|
+
"test:update": "pnpm test:node -u",
|
|
72
|
+
"test:watch": "vitest --config vitest.node.config.js",
|
|
73
|
+
"test:edge": "vitest --config vitest.edge.config.js --run",
|
|
74
|
+
"test:node": "vitest --config vitest.node.config.js --run"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createJsonErrorResponseHandler } from '@ai-sdk/provider-utils';
|
|
2
|
+
import { z } from 'zod/v4';
|
|
3
|
+
|
|
4
|
+
// Fish Audio returns `{ status, message }` for documented error responses
|
|
5
|
+
// (401 no permission, 402 no payment).
|
|
6
|
+
// https://docs.fish.audio/api-reference/endpoint/openapi-v1/text-to-speech
|
|
7
|
+
export const fishAudioErrorDataSchema = z.object({
|
|
8
|
+
status: z.number().nullish(),
|
|
9
|
+
message: z.string().nullish(),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type FishAudioErrorData = z.infer<typeof fishAudioErrorDataSchema>;
|
|
13
|
+
|
|
14
|
+
export const fishAudioFailedResponseHandler = createJsonErrorResponseHandler({
|
|
15
|
+
errorSchema: fishAudioErrorDataSchema,
|
|
16
|
+
errorToMessage: data => data.message ?? 'Unknown Fish Audio error',
|
|
17
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NoSuchModelError,
|
|
3
|
+
type ProviderV3,
|
|
4
|
+
type SpeechModelV3,
|
|
5
|
+
type TranscriptionModelV3,
|
|
6
|
+
} from '@ai-sdk/provider';
|
|
7
|
+
import {
|
|
8
|
+
loadApiKey,
|
|
9
|
+
withUserAgentSuffix,
|
|
10
|
+
type FetchFunction,
|
|
11
|
+
} from '@ai-sdk/provider-utils';
|
|
12
|
+
import { FishAudioSpeechModel } from './fish-audio-speech-model';
|
|
13
|
+
import type { FishAudioSpeechModelId } from './fish-audio-speech-options';
|
|
14
|
+
import { FishAudioTranscriptionModel } from './fish-audio-transcription-model';
|
|
15
|
+
import type { FishAudioTranscriptionModelId } from './fish-audio-transcription-options';
|
|
16
|
+
import { VERSION } from './version';
|
|
17
|
+
|
|
18
|
+
export interface FishAudioProvider extends ProviderV3 {
|
|
19
|
+
(
|
|
20
|
+
modelId: FishAudioSpeechModelId,
|
|
21
|
+
settings?: {},
|
|
22
|
+
): {
|
|
23
|
+
speech: FishAudioSpeechModel;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a model for speech generation.
|
|
28
|
+
*/
|
|
29
|
+
speech(modelId: FishAudioSpeechModelId): SpeechModelV3;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates a model for speech generation.
|
|
33
|
+
*
|
|
34
|
+
* Narrowed to required: Fish Audio always provides speech models.
|
|
35
|
+
*/
|
|
36
|
+
speechModel(modelId: FishAudioSpeechModelId): SpeechModelV3;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates a model for transcription.
|
|
40
|
+
*/
|
|
41
|
+
transcription(modelId?: FishAudioTranscriptionModelId): TranscriptionModelV3;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Creates a model for transcription.
|
|
45
|
+
*
|
|
46
|
+
* Narrowed to required: Fish Audio always provides a transcription model.
|
|
47
|
+
*/
|
|
48
|
+
transcriptionModel(
|
|
49
|
+
modelId?: FishAudioTranscriptionModelId,
|
|
50
|
+
): TranscriptionModelV3;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface FishAudioProviderSettings {
|
|
54
|
+
/**
|
|
55
|
+
* API key for authenticating requests.
|
|
56
|
+
*/
|
|
57
|
+
apiKey?: string;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Base URL for the API calls.
|
|
61
|
+
*/
|
|
62
|
+
baseURL?: string;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Custom headers to include in the requests.
|
|
66
|
+
*/
|
|
67
|
+
headers?: Record<string, string>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Custom fetch implementation. You can use it as a middleware to intercept requests,
|
|
71
|
+
* or to provide a custom fetch implementation for e.g. testing.
|
|
72
|
+
*/
|
|
73
|
+
fetch?: FetchFunction;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const DEFAULT_BASE_URL = 'https://api.fish.audio';
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create a Fish Audio provider instance.
|
|
80
|
+
*/
|
|
81
|
+
export function createFishAudio(
|
|
82
|
+
options: FishAudioProviderSettings = {},
|
|
83
|
+
): FishAudioProvider {
|
|
84
|
+
const baseURL = options.baseURL?.replace(/\/$/, '') ?? DEFAULT_BASE_URL;
|
|
85
|
+
|
|
86
|
+
const getHeaders = () =>
|
|
87
|
+
withUserAgentSuffix(
|
|
88
|
+
{
|
|
89
|
+
Authorization: `Bearer ${loadApiKey({
|
|
90
|
+
apiKey: options.apiKey,
|
|
91
|
+
environmentVariableName: 'FISH_AUDIO_API_KEY',
|
|
92
|
+
description: 'Fish Audio',
|
|
93
|
+
})}`,
|
|
94
|
+
...options.headers,
|
|
95
|
+
},
|
|
96
|
+
`ai-sdk/fish-audio/${VERSION}`,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const createSpeechModel = (modelId: FishAudioSpeechModelId) =>
|
|
100
|
+
new FishAudioSpeechModel(modelId, {
|
|
101
|
+
provider: 'fish-audio.speech',
|
|
102
|
+
url: ({ path }) => `${baseURL}${path}`,
|
|
103
|
+
headers: getHeaders,
|
|
104
|
+
fetch: options.fetch,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// `/v1/asr` has no model selector, so the model ID is a routing label and
|
|
108
|
+
// defaults to `transcribe-1`.
|
|
109
|
+
const createTranscriptionModel = (
|
|
110
|
+
modelId: FishAudioTranscriptionModelId = 'transcribe-1',
|
|
111
|
+
) =>
|
|
112
|
+
new FishAudioTranscriptionModel(modelId, {
|
|
113
|
+
provider: 'fish-audio.transcription',
|
|
114
|
+
url: ({ path }) => `${baseURL}${path}`,
|
|
115
|
+
headers: getHeaders,
|
|
116
|
+
fetch: options.fetch,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const provider = function (modelId: FishAudioSpeechModelId) {
|
|
120
|
+
return {
|
|
121
|
+
speech: createSpeechModel(modelId),
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
provider.specificationVersion = 'v3' as const;
|
|
126
|
+
provider.speech = createSpeechModel;
|
|
127
|
+
provider.speechModel = createSpeechModel;
|
|
128
|
+
provider.transcription = createTranscriptionModel;
|
|
129
|
+
provider.transcriptionModel = createTranscriptionModel;
|
|
130
|
+
|
|
131
|
+
// Required ProviderV3 methods that are not supported
|
|
132
|
+
provider.languageModel = (modelId: string) => {
|
|
133
|
+
throw new NoSuchModelError({
|
|
134
|
+
modelId,
|
|
135
|
+
modelType: 'languageModel',
|
|
136
|
+
message: 'Fish Audio does not provide language models',
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
provider.embeddingModel = (modelId: string) => {
|
|
141
|
+
throw new NoSuchModelError({
|
|
142
|
+
modelId,
|
|
143
|
+
modelType: 'embeddingModel',
|
|
144
|
+
message: 'Fish Audio does not provide embedding models',
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
provider.textEmbeddingModel = provider.embeddingModel;
|
|
148
|
+
|
|
149
|
+
provider.imageModel = (modelId: string) => {
|
|
150
|
+
throw new NoSuchModelError({
|
|
151
|
+
modelId,
|
|
152
|
+
modelType: 'imageModel',
|
|
153
|
+
message: 'Fish Audio does not provide image models',
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return provider as FishAudioProvider;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Default Fish Audio provider instance.
|
|
162
|
+
*/
|
|
163
|
+
export const fishAudio = createFishAudio();
|