@molecule/api-ai-speech-molecule
Provider bond · ai-speech · API (Node) · v1.1.2 · Apache-2.0
molecule.dev hosted speech provider — text-to-speech and transcription billed to your molecule project, no vendor account
npm install @molecule/api-ai-speech-moleculenpm · Source on GitHub · Implements @molecule/api-ai-speech
How it works
@molecule/api-ai-speech-molecule is a provider bond on the API (Node) side: it implements the ai-speech core interface (@molecule/api-ai-speech) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
import { setProvider, requireProvider } from '@molecule/api-ai-speech'
import { provider } from '@molecule/api-ai-speech-molecule'
setProvider(provider) // reads MOLECULE_API_KEY from the environment
const speech = requireProvider()
const { audio, contentType } = await speech.synthesize!({
input: 'Your order shipped.',
voice: 'nova',
})
const { text, duration } = await speech.transcribe!({ audio: uploadedBytes, filename: 'memo.m4a' })Works with: @molecule/api-ai-speech, @molecule/api-secrets
Secrets: MOLECULE_API_KEY
Reference
molecule.dev hosted speech provider for @molecule/api-ai-speech.
Text-to-speech and transcription run on molecule.dev and are billed to your
molecule project, so the app needs no OpenAI account. It is an ordinary
bond: swap it for @molecule/api-ai-speech-openai (your own key) or
@molecule/api-ai-speech-elevenlabs without changing code that calls the core.
Quick Start
import { setProvider, requireProvider } from '@molecule/api-ai-speech'
import { provider } from '@molecule/api-ai-speech-molecule'
setProvider(provider) // reads MOLECULE_API_KEY from the environment
const speech = requireProvider()
const { audio, contentType } = await speech.synthesize!({
input: 'Your order shipped.',
voice: 'nova',
})
const { text, duration } = await speech.transcribe!({ audio: uploadedBytes, filename: 'memo.m4a' })
Type
provider
Installation
npm install @molecule/api-ai-speech-molecule @molecule/api-ai-speech @molecule/api-secrets
API
Interfaces
MoleculeSpeechConfig
Options for createProvider. Every field falls back to an env var, so
the zero-argument provider export works from .env alone.
interface MoleculeSpeechConfig {
/**
* The molecule project API key (`mk_…`), or the sandbox token molecule.dev
* writes for in-IDE apps (`mbk_…`). Defaults to `MOLECULE_API_KEY`.
*/
apiKey?: string
/**
* The hosted services base URL, without a trailing slash. Defaults to
* `MOLECULE_SERVICES_URL`, then `https://api.molecule.dev/api/v1/services`.
*/
servicesUrl?: string
/** Default text-to-speech voice. Default `alloy`. */
defaultVoice?: string
/** Default text-to-speech model: `tts-1` (default) or `tts-1-hd`. */
defaultTTSModel?: string
/** Per-request timeout in milliseconds. Default 120000. */
timeoutMs?: number
}
ProcessEnv
Environment variables this provider reads.
interface ProcessEnv {
MOLECULE_API_KEY?: string
MOLECULE_SERVICES_URL?: string
}
Classes
MoleculeServiceError
Error thrown for a refused or failed hosted-service call.
MoleculeSpeechProvider
Speech through molecule.dev's hosted service.
Functions
createProvider(config)
Create a hosted speech provider.
function createProvider(config?: MoleculeSpeechConfig): AISpeechProvider
config— Options; each falls back to its env var.
Returns: An AISpeechProvider backed by molecule.dev.
Constants
aiSpeechMoleculeSecretDefinitions
Secret definitions required by the molecule.dev hosted speech bond.
const aiSpeechMoleculeSecretDefinitions: SecretDefinition[]
DEFAULT_SERVICES_URL
Default hosted services base URL.
const DEFAULT_SERVICES_URL: 'https://api.molecule.dev/api/v1/services'
provider
The provider implementation.
const provider: AISpeechProvider
SPEECH_SERVICE_LIMITS
Per-request limits of the hosted service.
const SPEECH_SERVICE_LIMITS: {
readonly maxSynthesizeChars: 4096
readonly maxTranscribeBytes: number
}
Core Interface
Implements @molecule/api-ai-speech interface.
Bond Wiring
Setup function to register this provider with the core interface:
import { setProvider } from '@molecule/api-ai-speech'
import { provider } from '@molecule/api-ai-speech-molecule'
export function setupAiSpeechMolecule(): void {
setProvider(provider)
}
Injection Notes
Requirements
Peer dependencies:
@molecule/api-ai-speech>=1.0.1@molecule/api-secrets^1.0.1
Environment Variables
MOLECULE_API_KEY(required) — molecule.dev project API key- Setup: Create one for your project with
mlcl apikey create --project <id> --name <name>(keys start with mk_). - Get it here: https://www.molecule.dev
- Example:
mk_...
- Setup: Create one for your project with
Runtime Dependencies
-
@molecule/api-ai-speech -
@molecule/api-secrets -
Config:
MOLECULE_API_KEY(SERVER-side only) — a molecule project API key (mk_…) with scopebrokerorbroker:speech. OptionalMOLECULE_SERVICES_URL(defaulthttps://api.molecule.dev/api/v1/services; required — plain-http is refused unless the host is loopback or a private-network endpoint (RFC 1918 / *.docker.internal, e.g. the sandbox gateway host.docker.internal)). -
Implements ONLY
synthesizeandtranscribe.synthesizeSpeech,synthesizeStream,listVoicesandtranslateare absent — check before calling (if (speech.listVoices)), or bond the ElevenLabs provider for them. -
synthesize: modelstts-1(default) andtts-1-hd; voices alloy, ash, coral, echo, fable, nova, onyx, sage, shimmer; formats mp3 (default), opus, aac, flac, wav, pcm; at most 4096 characters per call — split long text at sentence boundaries.instructions(a gpt-4o-mini-tts feature) is not sent. -
transcribe:whisper-1only; always returns text, language, duration and segments. Passfilenamewith the right extension (.m4a,.webm, …) — the service uses it to recognise the format. At most 12 MB per call. -
Errors are
MoleculeServiceErrorwithstatusanderrorKey. 401: bad, revoked, or wrong-scope key. 402: the project owner's included allowance is used up — usage billing must be enabled on molecule.dev. 413: input too large. 429 / 503: slow down / retry later. Nothing is retried for you. -
Never import this from browser code: the key would ship to every visitor. Browser recordings go to YOUR API route, which calls
transcribe.
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip. The wired provider implements a subset (TTS, STT, or both) — feature-detect and run only the direction(s) it actually exposes:
- TTS: each read-aloud / narration / voice-note flow the app defines
returns real PLAYABLE audio — the response carries an audio Content-Type
(e.g.
audio/mpeg) and non-trivial bytes (not a 0-byte file, not a JSON-encoded blob), and the UI's<audio>/player actually plays it. The spoken audio reflects the input text you sent. - TTS: synthesizing two different strings yields two audibly different clips (distinct byte payloads) — proves the text drives synthesis and you are not hearing a cached/placeholder file.
- TTS: any exposed voice/format/speed option takes effect — switching
voice (
voice/voiceId) or output format (responseFormat/outputFormat) changes the returned audio (audible voice, Content-Type, or file extension); an unknown voice/format surfaces a visible error rather than silently falling back. - STT: transcribing a KNOWN spoken clip returns text that MATCHES the words spoken (not empty, not garbage) and renders in the UI; translating a non-English clip returns English text. Any timestamps the UI shows line up with the audio.
- STT: a silent / empty / too-short clip is handled gracefully — an empty or "no speech detected" result shown in the UI, never a crash or a spinner that hangs forever.
- The audio artifact is served from the app's OWN origin (a stored upload or a route the app serves), NOT a raw expiring provider URL — copy the audio src, reload it later, and confirm it still plays.
- A provider error (bad key, quota, unsupported format/voice, or a capability the wired provider lacks) surfaces as a readable message in the UI, not an unhandled crash or a blank/broken player.
- Synthesis/transcription runs SERVER-SIDE and authorized: the provider key never reaches the browser (check the network tab / client bundle), and no unauthenticated or unbounded endpoint lets a caller fire arbitrary synthesize/transcribe calls — each is billed per character / per minute of audio, so an open endpoint is a cost-DoS vector.