@openclaw/fish-audio-speech 2026.7.2-beta.7 → 2026.8.1-beta.2
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/README.md +10 -3
- package/dist/index.js +4 -4
- package/dist/speech-provider.js +13 -27
- package/dist/tts.js +4 -3
- package/openclaw.plugin.json +2 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Official OpenClaw speech provider for Fish Audio's hosted S2.1 API.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Install it with:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
openclaw plugins install @openclaw/fish-audio-speech
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The plugin id is `fish-audio-speech`; the speech provider and TTS config id
|
|
12
|
+
remain `fish-audio`. Configure `tts.provider: "fish-audio"` and set
|
|
13
|
+
`FISH_API_KEY`. The provider supports buffered audio, HTTP-streamed playback,
|
|
14
|
+
native Opus voice notes, 8 kHz PCM telephony, and Fish Audio voice discovery.
|
|
8
15
|
|
|
9
16
|
See [Fish Audio](https://docs.openclaw.ai/providers/fish-audio) for setup,
|
|
10
17
|
models, voice selection, expressive tags, and local macOS MLX usage.
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { buildFishAudioSpeechProvider } from "./speech-provider.js";
|
|
2
2
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
|
-
//#region extensions/fish-audio/index.ts
|
|
4
|
-
var
|
|
5
|
-
id: "fish-audio",
|
|
3
|
+
//#region extensions/fish-audio-speech/index.ts
|
|
4
|
+
var fish_audio_speech_default = definePluginEntry({
|
|
5
|
+
id: "fish-audio-speech",
|
|
6
6
|
name: "Fish Audio Speech",
|
|
7
7
|
description: "Hosted Fish Audio S2.1 text-to-speech provider",
|
|
8
8
|
register(api) {
|
|
@@ -10,4 +10,4 @@ var fish_audio_default = definePluginEntry({
|
|
|
10
10
|
}
|
|
11
11
|
});
|
|
12
12
|
//#endregion
|
|
13
|
-
export {
|
|
13
|
+
export { fish_audio_speech_default as default };
|
package/dist/speech-provider.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { FISH_AUDIO_STREAM_MAX_BYTES, fishAudioTts, fishAudioTtsStream, listFishAudioVoices, normalizeFishAudioBaseUrl } from "./tts.js";
|
|
2
2
|
import { resolveGeneratedMediaMaxBytes } from "openclaw/plugin-sdk/media-generation-runtime";
|
|
3
3
|
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
4
|
-
import { asBoolean,
|
|
5
|
-
|
|
4
|
+
import { asBoolean, parseSpeechDirectiveNumberOverride, resolveSpeechProviderApiKey, trimToUndefined } from "openclaw/plugin-sdk/speech-core";
|
|
5
|
+
import { asFiniteNumberInRange, asOptionalRecord, parseBooleanValue } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
6
|
+
//#region extensions/fish-audio-speech/speech-provider.ts
|
|
6
7
|
const FISH_AUDIO_MODELS = [
|
|
7
8
|
"s2.1-pro-free",
|
|
8
9
|
"s2.1-pro",
|
|
@@ -25,14 +26,16 @@ function normalizeLatency(value) {
|
|
|
25
26
|
throw new Error(`invalid Fish Audio latency "${latency}"`);
|
|
26
27
|
}
|
|
27
28
|
function normalizeNumber(value, min, max) {
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
return asFiniteNumberInRange(value, {
|
|
30
|
+
min,
|
|
31
|
+
max
|
|
32
|
+
});
|
|
30
33
|
}
|
|
31
34
|
function resolveReferenceId(raw) {
|
|
32
35
|
return trimToUndefined(raw?.speakerVoiceId ?? raw?.voiceId ?? raw?.referenceId);
|
|
33
36
|
}
|
|
34
37
|
function normalizeProviderConfig(rawConfig) {
|
|
35
|
-
const raw =
|
|
38
|
+
const raw = asOptionalRecord(asOptionalRecord(rawConfig.providers)?.["fish-audio"]) ?? asOptionalRecord(rawConfig["fish-audio"]);
|
|
36
39
|
return {
|
|
37
40
|
apiKey: normalizeResolvedSecretInputString({
|
|
38
41
|
value: raw?.apiKey,
|
|
@@ -50,7 +53,7 @@ function normalizeProviderConfig(rawConfig) {
|
|
|
50
53
|
}
|
|
51
54
|
function readProviderConfig(config) {
|
|
52
55
|
const defaults = normalizeProviderConfig({});
|
|
53
|
-
const raw =
|
|
56
|
+
const raw = asOptionalRecord(config) ?? {};
|
|
54
57
|
return {
|
|
55
58
|
apiKey: trimToUndefined(raw.apiKey) ?? defaults.apiKey,
|
|
56
59
|
baseUrl: normalizeFishAudioBaseUrl(trimToUndefined(raw.baseUrl) ?? defaults.baseUrl),
|
|
@@ -64,7 +67,7 @@ function readProviderConfig(config) {
|
|
|
64
67
|
};
|
|
65
68
|
}
|
|
66
69
|
function readOverrides(overrides) {
|
|
67
|
-
const raw =
|
|
70
|
+
const raw = asOptionalRecord(overrides) ?? {};
|
|
68
71
|
return {
|
|
69
72
|
model: trimToUndefined(raw.model ?? raw.modelId) ? normalizeModel(raw.model ?? raw.modelId) : void 0,
|
|
70
73
|
referenceId: resolveReferenceId(raw),
|
|
@@ -164,29 +167,12 @@ function parseDirectiveToken(ctx) {
|
|
|
164
167
|
case "normalize":
|
|
165
168
|
case "fish_normalize": {
|
|
166
169
|
if (!ctx.policy.allowNormalization) return { handled: true };
|
|
167
|
-
const
|
|
168
|
-
if (
|
|
169
|
-
"true",
|
|
170
|
-
"1",
|
|
171
|
-
"yes",
|
|
172
|
-
"on"
|
|
173
|
-
].includes(value)) return {
|
|
170
|
+
const normalize = parseBooleanValue(ctx.value);
|
|
171
|
+
if (normalize !== void 0) return {
|
|
174
172
|
handled: true,
|
|
175
173
|
overrides: {
|
|
176
174
|
...ctx.currentOverrides,
|
|
177
|
-
normalize
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
if ([
|
|
181
|
-
"false",
|
|
182
|
-
"0",
|
|
183
|
-
"no",
|
|
184
|
-
"off"
|
|
185
|
-
].includes(value)) return {
|
|
186
|
-
handled: true,
|
|
187
|
-
overrides: {
|
|
188
|
-
...ctx.currentOverrides,
|
|
189
|
-
normalize: false
|
|
175
|
+
normalize
|
|
190
176
|
}
|
|
191
177
|
};
|
|
192
178
|
return {
|
package/dist/tts.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
1
2
|
import { MAX_AUDIO_BYTES } from "openclaw/plugin-sdk/media-runtime";
|
|
2
3
|
import { assertOkOrThrowProviderError, assertProviderBinaryResponseContent, readProviderBinaryResponse, readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
|
|
3
|
-
import {
|
|
4
|
+
import { trimToUndefined } from "openclaw/plugin-sdk/speech";
|
|
4
5
|
import { fetchWithSsrFGuard, ssrfPolicyFromHttpBaseUrlAllowedHostname } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
5
|
-
//#region extensions/fish-audio/tts.ts
|
|
6
|
+
//#region extensions/fish-audio-speech/tts.ts
|
|
6
7
|
const FISH_AUDIO_BASE_URL = "https://api.fish.audio";
|
|
7
8
|
const FISH_AUDIO_VOICES_MAX_BYTES = 2 * 1024 * 1024;
|
|
8
9
|
const FISH_AUDIO_VOICE_PAGE_SIZE = 100;
|
|
@@ -139,7 +140,7 @@ async function fishAudioTtsStream(params) {
|
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
function parseVoiceItem(value) {
|
|
142
|
-
const item =
|
|
143
|
+
const item = asOptionalRecord(value);
|
|
143
144
|
const id = trimToUndefined(item?.["_id"]);
|
|
144
145
|
if (!id) return;
|
|
145
146
|
const languages = Array.isArray(item?.languages) ? item.languages.flatMap((entry) => typeof entry === "string" && entry.trim() ? [entry.trim()] : []) : [];
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/fish-audio-speech",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.8.1-beta.2",
|
|
4
4
|
"description": "OpenClaw Fish Audio speech plugin.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
"minHostVersion": ">=2026.7.2"
|
|
19
19
|
},
|
|
20
20
|
"compat": {
|
|
21
|
-
"pluginApi": ">=2026.
|
|
21
|
+
"pluginApi": ">=2026.8.1-beta.2"
|
|
22
22
|
},
|
|
23
23
|
"build": {
|
|
24
|
-
"openclawVersion": "2026.
|
|
24
|
+
"openclawVersion": "2026.8.1-beta.2",
|
|
25
25
|
"bundledDist": false
|
|
26
26
|
},
|
|
27
27
|
"release": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"README.md"
|
|
39
39
|
],
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"openclaw": ">=2026.
|
|
41
|
+
"openclaw": ">=2026.8.1-beta.2"
|
|
42
42
|
},
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"openclaw": {
|