@openclaw/gradium-speech 0.0.0 → 2026.6.9
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 +11 -2
- package/dist/index.js +13 -0
- package/dist/shared.js +38 -0
- package/dist/speech-provider.js +107 -0
- package/dist/tts.js +40 -0
- package/npm-shrinkwrap.json +12 -0
- package/openclaw.plugin.json +22 -0
- package/package.json +44 -8
package/README.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OpenClaw Gradium Plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official OpenClaw plugin for Gradium.
|
|
4
|
+
|
|
5
|
+
Install from OpenClaw:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
openclaw plugins install @openclaw/gradium-speech
|
|
9
|
+
openclaw gateway restart
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
See <https://docs.openclaw.ai/providers/gradium> for setup and configuration.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { buildGradiumSpeechProvider } from "./speech-provider.js";
|
|
2
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
|
+
//#region extensions/gradium/index.ts
|
|
4
|
+
var gradium_default = definePluginEntry({
|
|
5
|
+
id: "gradium",
|
|
6
|
+
name: "Gradium Speech",
|
|
7
|
+
description: "Bundled Gradium speech provider",
|
|
8
|
+
register(api) {
|
|
9
|
+
api.registerSpeechProvider(buildGradiumSpeechProvider());
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
//#endregion
|
|
13
|
+
export { gradium_default as default };
|
package/dist/shared.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region extensions/gradium/shared.ts
|
|
2
|
+
const DEFAULT_GRADIUM_BASE_URL = "https://api.gradium.ai";
|
|
3
|
+
const DEFAULT_GRADIUM_VOICE_ID = "YTpq7expH9539ERJ";
|
|
4
|
+
const GRADIUM_VOICES = [
|
|
5
|
+
{
|
|
6
|
+
id: "YTpq7expH9539ERJ",
|
|
7
|
+
name: "Emma"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
id: "LFZvm12tW_z0xfGo",
|
|
11
|
+
name: "Kent"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: "Eu9iL_CYe8N-Gkx_",
|
|
15
|
+
name: "Tiffany"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "2H4HY2CBNyJHBCrP",
|
|
19
|
+
name: "Christina"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "jtEKaLYNn6iif5PR",
|
|
23
|
+
name: "Sydney"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "KWJiFWu2O9nMPYcR",
|
|
27
|
+
name: "John"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "3jUdJyOi9pgbxBTK",
|
|
31
|
+
name: "Arthur"
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
function normalizeGradiumBaseUrl(baseUrl) {
|
|
35
|
+
return (baseUrl?.trim())?.replace(/\/+$/, "") || DEFAULT_GRADIUM_BASE_URL;
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { DEFAULT_GRADIUM_VOICE_ID, GRADIUM_VOICES, normalizeGradiumBaseUrl };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { GRADIUM_VOICES, normalizeGradiumBaseUrl } from "./shared.js";
|
|
2
|
+
import { gradiumTTS } from "./tts.js";
|
|
3
|
+
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
4
|
+
import { asObject, trimToUndefined } from "openclaw/plugin-sdk/speech";
|
|
5
|
+
//#region extensions/gradium/speech-provider.ts
|
|
6
|
+
const DEFAULT_GENERATED_AUDIO_MAX_BYTES = 16 * 1024 * 1024;
|
|
7
|
+
function normalizeGradiumProviderConfig(rawConfig) {
|
|
8
|
+
const raw = asObject(asObject(rawConfig.providers)?.gradium) ?? asObject(rawConfig.gradium);
|
|
9
|
+
return {
|
|
10
|
+
apiKey: normalizeResolvedSecretInputString({
|
|
11
|
+
value: raw?.apiKey,
|
|
12
|
+
path: "messages.tts.providers.gradium.apiKey"
|
|
13
|
+
}),
|
|
14
|
+
baseUrl: normalizeGradiumBaseUrl(trimToUndefined(raw?.baseUrl)),
|
|
15
|
+
voiceId: trimToUndefined(raw?.voiceId) ?? "YTpq7expH9539ERJ"
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function readGradiumProviderConfig(config) {
|
|
19
|
+
const defaults = normalizeGradiumProviderConfig({});
|
|
20
|
+
return {
|
|
21
|
+
apiKey: trimToUndefined(config.apiKey) ?? defaults.apiKey,
|
|
22
|
+
baseUrl: normalizeGradiumBaseUrl(trimToUndefined(config.baseUrl) ?? defaults.baseUrl),
|
|
23
|
+
voiceId: trimToUndefined(config.voiceId) ?? defaults.voiceId
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function resolveGeneratedAudioMaxBytes(req) {
|
|
27
|
+
const configured = req.cfg.agents?.defaults?.mediaMaxMb;
|
|
28
|
+
if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) return Math.floor(configured * 1024 * 1024);
|
|
29
|
+
return DEFAULT_GENERATED_AUDIO_MAX_BYTES;
|
|
30
|
+
}
|
|
31
|
+
function parseDirectiveToken(ctx) {
|
|
32
|
+
switch (ctx.key) {
|
|
33
|
+
case "voice":
|
|
34
|
+
case "voice_id":
|
|
35
|
+
case "voiceid":
|
|
36
|
+
case "gradium_voice":
|
|
37
|
+
case "gradiumvoice":
|
|
38
|
+
if (!ctx.policy.allowVoice) return { handled: true };
|
|
39
|
+
return {
|
|
40
|
+
handled: true,
|
|
41
|
+
overrides: {
|
|
42
|
+
...ctx.currentOverrides,
|
|
43
|
+
voiceId: ctx.value
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
default: return { handled: false };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function buildGradiumSpeechProvider() {
|
|
50
|
+
return {
|
|
51
|
+
id: "gradium",
|
|
52
|
+
label: "Gradium",
|
|
53
|
+
autoSelectOrder: 30,
|
|
54
|
+
voices: GRADIUM_VOICES.map((v) => v.id),
|
|
55
|
+
resolveConfig: ({ rawConfig }) => normalizeGradiumProviderConfig(rawConfig),
|
|
56
|
+
parseDirectiveToken,
|
|
57
|
+
listVoices: async () => GRADIUM_VOICES.map((v) => ({
|
|
58
|
+
id: v.id,
|
|
59
|
+
name: v.name
|
|
60
|
+
})),
|
|
61
|
+
isConfigured: ({ providerConfig }) => Boolean(readGradiumProviderConfig(providerConfig).apiKey || process.env.GRADIUM_API_KEY),
|
|
62
|
+
synthesize: async (req) => {
|
|
63
|
+
const config = readGradiumProviderConfig(req.providerConfig);
|
|
64
|
+
const overrides = req.providerOverrides ?? {};
|
|
65
|
+
const apiKey = config.apiKey || process.env.GRADIUM_API_KEY;
|
|
66
|
+
if (!apiKey) throw new Error("Gradium API key missing");
|
|
67
|
+
const wantsVoiceNote = req.target === "voice-note";
|
|
68
|
+
const outputFormat = wantsVoiceNote ? "opus" : "wav";
|
|
69
|
+
return {
|
|
70
|
+
audioBuffer: await gradiumTTS({
|
|
71
|
+
text: req.text,
|
|
72
|
+
apiKey,
|
|
73
|
+
baseUrl: config.baseUrl,
|
|
74
|
+
voiceId: trimToUndefined(overrides.voiceId) ?? config.voiceId,
|
|
75
|
+
outputFormat,
|
|
76
|
+
timeoutMs: req.timeoutMs,
|
|
77
|
+
maxBytes: resolveGeneratedAudioMaxBytes(req)
|
|
78
|
+
}),
|
|
79
|
+
outputFormat,
|
|
80
|
+
fileExtension: wantsVoiceNote ? ".opus" : ".wav",
|
|
81
|
+
voiceCompatible: wantsVoiceNote
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
synthesizeTelephony: async (req) => {
|
|
85
|
+
const config = readGradiumProviderConfig(req.providerConfig);
|
|
86
|
+
const overrides = req.providerOverrides ?? {};
|
|
87
|
+
const apiKey = config.apiKey || process.env.GRADIUM_API_KEY;
|
|
88
|
+
if (!apiKey) throw new Error("Gradium API key missing");
|
|
89
|
+
const outputFormat = "ulaw_8000";
|
|
90
|
+
return {
|
|
91
|
+
audioBuffer: await gradiumTTS({
|
|
92
|
+
text: req.text,
|
|
93
|
+
apiKey,
|
|
94
|
+
baseUrl: config.baseUrl,
|
|
95
|
+
voiceId: trimToUndefined(overrides.voiceId) ?? config.voiceId,
|
|
96
|
+
outputFormat,
|
|
97
|
+
timeoutMs: req.timeoutMs,
|
|
98
|
+
maxBytes: resolveGeneratedAudioMaxBytes(req)
|
|
99
|
+
}),
|
|
100
|
+
outputFormat,
|
|
101
|
+
sampleRate: 8e3
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { buildGradiumSpeechProvider };
|
package/dist/tts.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { normalizeGradiumBaseUrl } from "./shared.js";
|
|
2
|
+
import { assertOkOrThrowProviderError } from "openclaw/plugin-sdk/provider-http";
|
|
3
|
+
import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
|
|
4
|
+
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
5
|
+
//#region extensions/gradium/tts.ts
|
|
6
|
+
const DEFAULT_TTS_MAX_BYTES = 16 * 1024 * 1024;
|
|
7
|
+
async function gradiumTTS(params) {
|
|
8
|
+
const { text, apiKey, baseUrl, voiceId, outputFormat, timeoutMs, maxBytes = DEFAULT_TTS_MAX_BYTES } = params;
|
|
9
|
+
const normalizedBaseUrl = normalizeGradiumBaseUrl(baseUrl);
|
|
10
|
+
const url = `${normalizedBaseUrl}/api/post/speech/tts`;
|
|
11
|
+
const hostname = new URL(normalizedBaseUrl).hostname;
|
|
12
|
+
const { response, release } = await fetchWithSsrFGuard({
|
|
13
|
+
url,
|
|
14
|
+
init: {
|
|
15
|
+
method: "POST",
|
|
16
|
+
headers: {
|
|
17
|
+
"x-api-key": apiKey,
|
|
18
|
+
"Content-Type": "application/json"
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
text,
|
|
22
|
+
voice_id: voiceId,
|
|
23
|
+
only_audio: true,
|
|
24
|
+
output_format: outputFormat,
|
|
25
|
+
json_config: JSON.stringify({ padding_bonus: 0 })
|
|
26
|
+
})
|
|
27
|
+
},
|
|
28
|
+
timeoutMs,
|
|
29
|
+
policy: { hostnameAllowlist: [hostname] },
|
|
30
|
+
auditContext: "gradium.tts"
|
|
31
|
+
});
|
|
32
|
+
try {
|
|
33
|
+
await assertOkOrThrowProviderError(response, "Gradium API error");
|
|
34
|
+
return await readResponseWithLimit(response, maxBytes, { onOverflow: ({ maxBytes: maxBytesLocal }) => /* @__PURE__ */ new Error(`Gradium TTS audio response exceeds ${maxBytesLocal} bytes`) });
|
|
35
|
+
} finally {
|
|
36
|
+
await release();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { gradiumTTS };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "gradium",
|
|
3
|
+
"activation": {
|
|
4
|
+
"onStartup": false
|
|
5
|
+
},
|
|
6
|
+
"setup": {
|
|
7
|
+
"providers": [
|
|
8
|
+
{
|
|
9
|
+
"id": "gradium",
|
|
10
|
+
"envVars": ["GRADIUM_API_KEY"]
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"contracts": {
|
|
15
|
+
"speechProviders": ["gradium"]
|
|
16
|
+
},
|
|
17
|
+
"configSchema": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"additionalProperties": false,
|
|
20
|
+
"properties": {}
|
|
21
|
+
}
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/gradium-speech",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"license": "MIT",
|
|
3
|
+
"version": "2026.6.9",
|
|
4
|
+
"description": "OpenClaw Gradium speech plugin.",
|
|
6
5
|
"repository": {
|
|
7
6
|
"type": "git",
|
|
8
|
-
"url": "
|
|
7
|
+
"url": "https://github.com/openclaw/openclaw"
|
|
9
8
|
},
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
9
|
+
"type": "module",
|
|
10
|
+
"openclaw": {
|
|
11
|
+
"extensions": [
|
|
12
|
+
"./index.ts"
|
|
13
|
+
],
|
|
14
|
+
"install": {
|
|
15
|
+
"clawhubSpec": "clawhub:@openclaw/gradium-speech",
|
|
16
|
+
"npmSpec": "@openclaw/gradium-speech",
|
|
17
|
+
"defaultChoice": "npm",
|
|
18
|
+
"minHostVersion": ">=2026.6.8"
|
|
19
|
+
},
|
|
20
|
+
"compat": {
|
|
21
|
+
"pluginApi": ">=2026.6.9"
|
|
22
|
+
},
|
|
23
|
+
"build": {
|
|
24
|
+
"openclawVersion": "2026.6.9",
|
|
25
|
+
"bundledDist": false
|
|
26
|
+
},
|
|
27
|
+
"release": {
|
|
28
|
+
"publishToClawHub": true,
|
|
29
|
+
"publishToNpm": true
|
|
30
|
+
},
|
|
31
|
+
"runtimeExtensions": [
|
|
32
|
+
"./dist/index.js"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist/**",
|
|
37
|
+
"openclaw.plugin.json",
|
|
38
|
+
"npm-shrinkwrap.json",
|
|
39
|
+
"README.md"
|
|
40
|
+
],
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"openclaw": ">=2026.6.9"
|
|
43
|
+
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"openclaw": {
|
|
46
|
+
"optional": true
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"bundledDependencies": []
|
|
14
50
|
}
|