Skip to content

feat: support the TTS provider integration type (B.20) - #21

Open
Pierre-Gilles wants to merge 1 commit into
mainfrom
claude/sdk-changes-pr-2746-lmaqxo
Open

feat: support the TTS provider integration type (B.20)#21
Pierre-Gilles wants to merge 1 commit into
mainfrom
claude/sdk-changes-pr-2746-lmaqxo

Conversation

@Pierre-Gilles

@Pierre-Gilles Pierre-Gilles commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

SDK counterpart of GladysAssistant/Gladys#2746 (B.20 "TTS provider" type): integrations declaring type: "tts" in their manifest become selectable as the voice of the instance, and answer synthesis requests over the existing WebSocket command channel.

What's added

  • WEBSOCKET_MESSAGE_TYPES.EXTERNAL_INTEGRATION.TTS_SYNTHESIZE (external-integration.tts.synthesize), routed in _handleMessage like the other auto-acked commands.
  • gladys.onTtsSynthesize(cb)(text, language) => Promise<string> resolving the audio data-URI (<content_type>;base64,...), acked back as data.audio (contract C.8). language is normalized to null when absent (best-effort hint: the user's language for the voice assistant, null in scenes).
  • SDK-side validation of the resolved audio (_mapTtsAudio, same pattern as the sync-webhook response mapping): curated content types (audio/mpeg, audio/wav, audio/ogg, audio/aac), decoded audio of 1 byte to 5 MB — the same bounds the core enforces in externalIntegration.registerProxyService.js, so a bad audio fails the ack with an explicit message on the integration side instead of an opaque EXTERNAL_INTEGRATION_INVALID_TTS_AUDIO on the Gladys side.
  • Typings (onTtsSynthesize, TTS_SYNTHESIZE) + type-test coverage.
  • Tests (test/tts.test.js, 10 cases): success ack with data.audio, language defaulting to null, handler throw, missing handler ("not implemented"), non-string / missing separator / non-curated content type / empty audio / > 5 MB rejections, exactly-5 MB accepted.
  • README: handler-table row + a "TTS provider (the voice of the instance)" section, mirroring the camera/communication sections.

Checks

  • npm test: 190/190 pass
  • npm run lint, npm run check-types, npm run prettier-check: clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01LCCieF9vzy56ULFi9r6b2g


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added text-to-speech synthesis support for integrations.
    • Handlers can receive text with an optional language hint and return playable base64 audio.
    • Supports MP3, WAV, OGG, and AAC audio up to 5 MB.
    • Added validation for audio formats, data, and response limits.
  • Documentation

    • Documented TTS provider setup, configuration, playback behavior, input limits, and validation requirements.
    • Added SDK API documentation for registering TTS synthesis handlers.

Mirror of GladysAssistant/Gladys#2746: integrations declaring
type: "tts" in their manifest receive the
external-integration.tts.synthesize command and answer it through the
new onTtsSynthesize(cb) handler — (text, language) => Promise<string>
resolving the audio data-URI, acked back as data.audio (30 s ack
delay). The SDK enforces the core bounds before acking (curated content
types audio/mpeg|wav|ogg|aac, decoded audio of 1 byte to 5 MB) so a bad
audio fails with an explicit message on the integration side.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCCieF9vzy56ULFi9r6b2g
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

TTS synthesis

Layer / File(s) Summary
Protocol and public API contracts
lib/constants.js, index.d.ts
Adds the TTS WebSocket message type, audio limits, supported content types, and onTtsSynthesize declarations.
Synthesis dispatch and audio validation
lib/gladys-integration.js
Registers the callback, dispatches synthesis requests, and validates returned base64 audio data URIs.
Contract validation and integration documentation
test/tts.test.js, test/types/api.test-d.ts, README.md
Adds runtime and type tests. Documents TTS configuration, playback, limits, formats, and acknowledgement timing.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GladysServer
  participant GladysIntegration
  participant onTtsSynthesize
  GladysServer->>GladysIntegration: TTS_SYNTHESIZE request
  GladysIntegration->>onTtsSynthesize: text and normalized language
  onTtsSynthesize-->>GladysIntegration: audio data URI
  GladysIntegration->>GladysIntegration: validate audio format, base64 data, and decoded size
  GladysIntegration-->>GladysServer: acknowledgement with audio
Loading

Possibly related PRs

Poem

I’m a rabbit with audio in flight,
TTS hops through the socket tonight.
Five megabytes fit,
Bad formats don’t pass it,
And clear words become sound just right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: support for the TTS provider integration type.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/sdk-changes-pr-2746-lmaqxo

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/gladys-integration.js`:
- Around line 1277-1288: Update the Base64 handling in the onTtsSynthesize flow
to validate the payload before calling Buffer.from, rejecting non-empty inputs
containing invalid Base64 characters or malformed padding while preserving valid
decoding and existing size/empty checks. Add a test covering a non-empty
malformed payload such as invalid characters.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3ee24485-ad49-4b70-9c36-aca132a2f004

📥 Commits

Reviewing files that changed from the base of the PR and between 800bd7d and 2c88df0.

📒 Files selected for processing (6)
  • README.md
  • index.d.ts
  • lib/constants.js
  • lib/gladys-integration.js
  • test/tts.test.js
  • test/types/api.test-d.ts

Comment thread lib/gladys-integration.js
Comment on lines +1277 to +1288
const separatorIndex = audio.indexOf(';base64,');
const contentType = separatorIndex === -1 ? '' : audio.substring(0, separatorIndex);
if (!TTS_AUDIO_CONTENT_TYPES.includes(contentType)) {
throw new Error(`onTtsSynthesize: the audio content type must be one of ${TTS_AUDIO_CONTENT_TYPES.join(', ')}`);
}
const decoded = Buffer.from(audio.substring(separatorIndex + ';base64,'.length), 'base64');
if (decoded.length === 0) {
throw new Error('onTtsSynthesize: the synthesized audio is empty');
}
if (decoded.length > MAX_TTS_AUDIO_SIZE) {
throw new Error(`onTtsSynthesize: maximum decoded audio size is ${MAX_TTS_AUDIO_SIZE} bytes (5 MB)`);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

According to the Node.js Buffer documentation, does Buffer.from(value, 'base64') reject invalid Base64 characters and invalid padding, or can it decode permissively?

💡 Result:

According to the official Node.js Buffer documentation, the Buffer.from(value, 'base64') method assumes that the provided input string is valid [1][2]. It does not perform strict validation to reject invalid Base64 characters or incorrect padding; instead, it operates permissively [1][2]. Key characteristics of this behavior include: 1. Permissive Decoding: The implementation is designed to be lenient. For example, it automatically ignores whitespace characters—such as spaces, tabs, and new lines—contained within the Base64-encoded string [1][2]. 2. Input Assumptions: Because the method assumes valid input for performance reasons, providing malformed data may result in unpredictable output rather than a thrown error [1][2]. 3. Non-Base64 Data: If a string contains characters that are neither valid Base64 nor ignorable whitespace, the resulting Buffer may contain data that does not correspond to the original intent, and the length of the created Buffer may differ from what one might expect if the input were strictly validated [1][2]. 4. Padding: The decoder is generally lenient regarding padding characters (the '=' character), accepting both padded and unpadded Base64 strings [3]. In summary, the Node.js Buffer API does not strictly validate Base64 input and will decode it permissively, meaning it does not automatically reject invalid characters or improperly padded strings [1][3][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant constants and validator block without running repository code.
if [ -f lib/gladys-integration.js ]; then
  sed -n '1,35p;1036,1058p;1268,1292p' lib/gladys-integration.js | cat -n
else
  fd -a 'gladys-integration\.js$' . | head -5
fi

# Probe Node.js Buffer.from base64 behavior for the malformed example(s) from the comment.
node - <<'JS'
const { Buffer } = require('buffer');
const inputs = [
  'Zm9v!',
  'Zm9v!!',
  'Zm9v!!!',
  'Zm9v!!!!',
  'Zm9v----!!!!',
  '   Zm9v   ',
  'Zm9v',
  'Zm9v==',
  'Zm9v=  =',
  '',
];
for (const input of inputs) {
  const b = Buffer.from(input, 'base64');
  console.log(JSON.stringify({
    input,
    decodedLength: b.length,
    decodedHex: b.toString('hex'),
    decodedString: b.toString('utf8'),
    strictBase64Like: /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3})?$/.test(input),
  }));
}
JS

Repository: GladysAssistant/integration-sdk-js

Length of output: 5133


Validate the Base64 payload before decoding.

Buffer.from(..., 'base64') accepts invalid characters and whitespace, so malformed audio/mpeg;base64,Zm9v! passes validation and returns bytes. Reject malformed Base64 before decoding, and add a test for non-empty malformed input.

Proposed fix
-    const decoded = Buffer.from(audio.substring(separatorIndex + ';base64,'.length), 'base64');
+    const base64 = audio.substring(separatorIndex + ';base64,'.length);
+    if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3})?$/.test(base64)) {
+      throw new Error('onTtsSynthesize: the synthesized audio must contain valid base64 data');
+    }
+    const decoded = Buffer.from(base64, 'base64');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const separatorIndex = audio.indexOf(';base64,');
const contentType = separatorIndex === -1 ? '' : audio.substring(0, separatorIndex);
if (!TTS_AUDIO_CONTENT_TYPES.includes(contentType)) {
throw new Error(`onTtsSynthesize: the audio content type must be one of ${TTS_AUDIO_CONTENT_TYPES.join(', ')}`);
}
const decoded = Buffer.from(audio.substring(separatorIndex + ';base64,'.length), 'base64');
if (decoded.length === 0) {
throw new Error('onTtsSynthesize: the synthesized audio is empty');
}
if (decoded.length > MAX_TTS_AUDIO_SIZE) {
throw new Error(`onTtsSynthesize: maximum decoded audio size is ${MAX_TTS_AUDIO_SIZE} bytes (5 MB)`);
}
const separatorIndex = audio.indexOf(';base64,');
const contentType = separatorIndex === -1 ? '' : audio.substring(0, separatorIndex);
if (!TTS_AUDIO_CONTENT_TYPES.includes(contentType)) {
throw new Error(`onTtsSynthesize: the audio content type must be one of ${TTS_AUDIO_CONTENT_TYPES.join(', ')}`);
}
const base64 = audio.substring(separatorIndex + ';base64,'.length);
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3})?$/.test(base64)) {
throw new Error('onTtsSynthesize: the synthesized audio must contain valid base64 data');
}
const decoded = Buffer.from(base64, 'base64');
if (decoded.length === 0) {
throw new Error('onTtsSynthesize: the synthesized audio is empty');
}
if (decoded.length > MAX_TTS_AUDIO_SIZE) {
throw new Error(`onTtsSynthesize: maximum decoded audio size is ${MAX_TTS_AUDIO_SIZE} bytes (5 MB)`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/gladys-integration.js` around lines 1277 - 1288, Update the Base64
handling in the onTtsSynthesize flow to validate the payload before calling
Buffer.from, rejecting non-empty inputs containing invalid Base64 characters or
malformed padding while preserving valid decoding and existing size/empty
checks. Add a test covering a non-empty malformed payload such as invalid
characters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants