Disable the active Zen summarization flow because the unauthenticated/free Zen provider is no longer available and now returns usage-limit errors for this feature.
Keep /api/text/summarize as an API-compatible stub that returns local sanitized or distilled fallback text with summarized=false, rather than attempting external model calls.
Remove notification and voice playback summary behavior from runtime paths. Notification {last_message} now always uses normalized truncated text, and TTS playback ignores historical summarize request fields.
Hide the notification summary settings and voice summarize-before-playback controls while preserving legacy persisted settings for compatibility. Also disable Zen model startup validation and make Zen model list routes return empty results.
Update module documentation and tests to describe the retired provider behavior and the remaining compatibility stubs.
7.3 KiB
TTS Module Documentation
Purpose
This module provides server-side Text-to-Speech services using OpenAI's TTS API. The historical shared text summarization endpoint now lives in packages/web/server/lib/text/ as an API-compatible stub because the previous Zen model provider is unavailable.
Entrypoints and structure
packages/web/server/lib/tts/index.js: Public entrypoint imported bypackages/web/server/index.js.packages/web/server/lib/tts/routes.js: Express route registration for/api/voice/*,/api/tts/*, and/api/stt/*endpoints.packages/web/server/lib/tts/capability-runtime.js: runtime helper for probing local macOSsayTTS voice capability.packages/web/server/lib/tts/service.js: TTS service implementation with OpenAI integration.packages/web/server/lib/text/summarization.js: Shared text summarization stub and sanitization utilities. It performs no external Zen calls.packages/web/server/lib/tts/stt.js: STT proxy for OpenAI-compatible transcription endpoints.packages/web/server/lib/tts/base-url.js: shared base URL validation and normalization for custom OpenAI-compatible endpoints.
Public exports
TTS Service (from service.js)
ttsService: Singleton instance of TTSService class.TTSService: TTS service class for OpenAI audio generation.TTS_VOICES: Array of supported OpenAI voice identifiers.
Shared text summarization (re-exported from ../text/summarization.js)
summarizeText({ text, threshold, maxLength, zenModel, mode }): Retired shared text summarizer retained as a stub. TTS usesmode: 'tts';zenModelis ignored.sanitizeForTTS(text): Sanitizes text by removing markdown, URLs, file paths, and other non-speakable content.sanitizeForNote(text): Re-exported for note-mode callers that still import through the TTS surface.
Capability runtime (capability-runtime.js)
detectSayTtsCapability(processLike): probes localsay -v "?"support and returns{ available, voices, reason }.
Constants
Voice identifiers
TTS_VOICES: Array of supported OpenAI voices:['alloy', 'ash', 'ballad', 'coral', 'echo', 'fable', 'nova', 'onyx', 'sage', 'shimmer', 'verse', 'marin', 'cedar'].
Summarization defaults
- No model request timeout is used; the summarization provider is disabled.
Default values
summarizeTextdefaults:threshold= 200,maxLength= 500,mode= 'tts'.zenModelis ignored.generateSpeechStreamdefaults:voice= 'coral',model= 'gpt-4o-mini-tts',speed= 1.0.generateSpeechBufferdefaults:voice= 'coral',model= 'gpt-4o-mini-tts',speed= 1.0.
TTSService methods
isAvailable()
Returns boolean indicating whether OpenAI API key is configured (checks environment variable OPENAI_API_KEY or OpenCode auth file).
generateSpeechStream(options)
Generates speech and returns as a web stream for direct streaming to clients.
- Options:
text(required),voice,model,speed,instructions,apiKey. - Returns:
{ stream: ReadableStream, contentType: 'audio/mpeg' }. - Throws: Error if API key not configured or text is empty.
generateSpeechBuffer(options)
Generates speech and returns as Buffer for caching purposes.
- Options:
text(required),voice,model,speed,instructions. - Returns: Buffer containing MP3 audio data.
- Throws: Error if API key not configured or text is empty.
Response contracts
summarizeText
Returns object with:
summary: Sanitized or locally distilled fallback text.summarized: Alwaysfalsewhile the model provider is unavailable.reason: String explaining why summarization was skipped.originalLength: Optional number for original text length.summaryLength: Optional number for summarized text length.
The route-level text summarize API is now /api/text/summarize.
sanitizeForTTS
Returns sanitized string with markdown, URLs, file paths, and special characters removed.
generateSpeechStream
Returns object with:
stream: ReadableStream of MP3 audio data.contentType: Always 'audio/mpeg'.
generateSpeechBuffer
Returns Buffer containing MP3 audio data.
API key resolution
OpenAI API keys are resolved in order:
- Environment variable
OPENAI_API_KEY. - OpenCode auth file (
auth.openai,auth.codex, orauth.chatgpt). - Supports both string format (just token) and object format (with
accessortokenfields).
Usage in web server
The TTS module is used by packages/web/server/index.js for:
- Generating speech streams for client playback.
- Generating speech buffers for caching.
- Sanitizing text before TTS synthesis. Historical summarization calls now return local fallback text.
- Sanitizing text to remove non-speakable content.
The historical summarization API is shared with notifications and notes, but currently acts as a no-model fallback/stub.
The server-side TTS approach bypasses mobile Safari's audio context restrictions by generating audio on the server and streaming to clients.
Notes for contributors
Adding new TTS features
- Add new methods to
packages/web/server/lib/tts/service.jsTTSService class. - Export public functions from
packages/web/server/lib/tts/index.js. - Follow existing patterns for API key resolution and error handling.
- Ensure all text is sanitized before TTS synthesis.
- Consider adding new voice options to
TTS_VOICESconstant.
Text sanitization
- Always call
sanitizeForTTSon text before passing to TTS generation. - The sanitization removes markdown, code blocks, URLs, file paths, shell commands, and special characters.
- This prevents the TTS from reading out technical formatting that sounds unnatural.
Error handling
generateSpeechStreamandgenerateSpeechBufferthrow descriptive errors for missing API keys or empty text.summarizeTextdoes not call Zen and returns mode-specific fallback text withsummarized: false.- All errors are logged to console with
[TTSService]or[Summarize]prefix.
API key management
- TTSService caches OpenAI client instance and recreates when API key changes.
- API key changes are detected by comparing with
_lastApiKeyproperty. - This allows dynamic API key updates without server restart.
Testing
- Run
bun run type-check,bun run lint, andbun run buildbefore finalizing changes. - Test API key resolution with environment variable and auth file.
- Test speech generation with various text lengths and voice options.
- Test summarization stub behavior above and below threshold.
- Test sanitization with markdown, URLs, and code blocks.
- Verify streaming and buffer generation produce valid MP3 audio.
Verification notes
Manual verification
- Configure OpenAI API key via environment variable or OpenCode settings.
- Test
ttsService.isAvailable()returns true. - Call
ttsService.generateSpeechStream({ text: 'Hello world' })and verify stream is returned. - Call
ttsService.generateSpeechBuffer({ text: 'Hello world' })and verify Buffer is returned. - Test
summarizeTextwith text above and below threshold. - Test
sanitizeForTTSwith markdown, URLs, and code blocks.
API endpoint verification
- Start web server and access TTS endpoint via client.
- Verify audio plays correctly in browser.
- Test on mobile Safari to verify bypass of audio context restrictions.
- Test with long messages to verify summarization is triggered.