Move server-side summarization out of lib/tts/summarization.js into a shared lib/text module exposing tts, note and notification modes. TTS and notification runtimes now delegate to the shared service, note mode is used to distill a selected excerpt into a short project note. Expose the new endpoint via /api/text/summarize and route it through the common request middleware. Client voice/summarize.ts accepts a mode option and points at the new endpoint with an absolute URL that respects the desktop server origin. VS Code webview swaps the stubbed tts summarize path for /api/text/summarize and hardens URL parsing with window.location.href as the base.
7.2 KiB
TTS Module Documentation
Purpose
This module provides server-side Text-to-Speech services using OpenAI's TTS API. Shared text summarization now lives in packages/web/server/lib/text/ and is consumed here in tts mode.
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 and sanitization utilities using opencode.ai zen API.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 }): Shared text summarizer. TTS usesmode: 'tts'.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
SUMMARIZE_TIMEOUT_MS: 30000 (30 seconds timeout for zen API requests).
Default values
summarizeTextdefaults:threshold= 200,maxLength= 500,zenModel= 'gpt-5-nano',mode= 'tts'.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 summary text or original text (if not summarized).summarized: Boolean indicating if summarization was performed.reason: Optional string explaining why summarization was skipped (e.g., 'Text under threshold', 'Request timed out').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.
- Summarizing long messages before TTS synthesis.
- Sanitizing text to remove non-speakable content.
The summarization logic itself is shared with notifications and notes, but this module uses it only in tts mode.
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.summarizeTextcatches zen API errors 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 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.