@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 CHANGED
@@ -1,3 +1,12 @@
1
- # @openclaw/gradium-speech
1
+ # OpenClaw Gradium Plugin
2
2
 
3
- Reserved placeholder for the official OpenClaw plugin package. Use a published release version instead.
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,12 @@
1
+ {
2
+ "name": "@openclaw/gradium-speech",
3
+ "version": "2026.6.9",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "@openclaw/gradium-speech",
9
+ "version": "2026.6.9"
10
+ }
11
+ }
12
+ }
@@ -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": "0.0.0",
4
- "description": "Reserved package name for an official OpenClaw plugin.",
5
- "license": "MIT",
3
+ "version": "2026.6.9",
4
+ "description": "OpenClaw Gradium speech plugin.",
6
5
  "repository": {
7
6
  "type": "git",
8
- "url": "git+https://github.com/openclaw/openclaw.git"
7
+ "url": "https://github.com/openclaw/openclaw"
9
8
  },
10
- "publishConfig": {
11
- "access": "public",
12
- "tag": "placeholder"
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
  }