Agents and models
Voice
Transcribe speech and speak text through voice providers, with explicit prices, a per-call cost bound and voice:<id> permissions.
mayura/voice turns speech into text and text into speech through provider packages such as
@mayurajs/voice-openai. A voice registry gives every model an id, <provider>/<model>, a price and a per-call cost
bound. Every call is checked against that bound before anything is sent: speech is priced from its text, and
transcription from the audio's duration.
npm install mayura @mayurajs/voice-openaiimport { createVoices } from 'mayura/voice';
import { openaiVoice } from '@mayurajs/voice-openai';
const voices = createVoices({
providers: [openaiVoice({ apiKey: process.env.OPENAI_API_KEY ?? '' })],
prices: 'catalog',
maxCallCostMicros: 50_000,
});
const speech = await voices.speaker('openai/tts-1').speak({ text: 'Your order ships tomorrow.', voice: 'alloy' });
const transcript = await voices.transcriber('openai/whisper-1').transcribe({ audio: speech.audio, durationMs: 2_000 });
console.log(transcript.text, transcript.usage.costMicros);Prices and bounds
Prices are micros (millionths of a US dollar): microsPerMinute of audio for transcription, charged by the
millisecond, and microsPerMillionCharacters of text for speech, counting Unicode code points. Give them in prices
by voice id, or use prices: 'catalog' for the list prices a provider package ships, as of its catalog's date. A model
without a price is refused.
| Option | Notes |
|---|---|
providers |
The voice providers models may come from. |
maxCallCostMicros |
The most one call may cost, unless a model sets its own maxCostMicros. Required. |
prices |
A map from voice id to its price, or 'catalog'. |
timeoutMs |
How long one call may take; each provider has its own default. |
maxAudioBytes |
The largest audio a transcription accepts; 25 MiB by default. |
maxTextCharacters |
The longest text a speech call accepts; 20,000 characters by default. |
Speech that would cost more than its model's bound, or audio whose duration would, fails with BUDGET_EXCEEDED before
the provider is called. Mayura measures WAV audio itself. For compressed audio (MP3, WebM, Ogg, M4A, FLAC) give
durationMs: Mayura does not guess a duration, since a guess could let a call cost more than its bound. The cost a
call reports is what the provider billed: the audio duration it reports, or the duration measured or given.
In agents and workflows
transcriptionTool(transcriber) and speechTool(speaker, { voice }) make tools that take audio or text as data and
return the transcript or the audio as base64. Each requires the permission voice:<id> besides tool:<tool id>,
reserves the model's per-call bound from the run's budget, and reports what the call cost:
import { createVoices, transcriptionTool } from 'mayura/voice';
import { openaiVoice } from '@mayurajs/voice-openai';
const voices = createVoices({ providers: [openaiVoice({ apiKey: process.env.OPENAI_API_KEY ?? '' })], prices: 'catalog', maxCallCostMicros: 50_000 });
const transcribe = transcriptionTool(voices.transcriber('openai/whisper-1'));
// Grant: 'tool:voice.transcribe' and 'voice:openai/whisper-1'.
console.log(transcribe.capabilities);Errors
A failed call raises VoiceProviderError with a reason, as model calls do: authentication, rate_limited,
unavailable, timeout, rejected, invalid_response, refused or configuration (a format, voice or language the
provider does not take). Messages are fixed text: nothing the provider wrote reaches them. A call cancelled through its
signal fails with CANCELLED.
Providers
| Package | Transcription | Speech |
|---|---|---|
@mayurajs/voice-openai |
whisper-1 in the catalog; gpt-4o-transcribe, gpt-4o-mini-transcribe and gpt-transcribe with your own price |
tts-1, tts-1-hd in the catalog; gpt-4o-mini-tts with your own price |
@mayurajs/voice-elevenlabs |
scribe_v2 in the catalog |
eleven_v4, eleven_v4_turbo, eleven_v3, eleven_v3_conversational, eleven_multilingual_v2 and eleven_flash_v2_5 in the catalog; the voice is an ElevenLabs voice id |
@mayurajs/voice-deepgram |
nova-3 (at its multilingual rate) and whisper-large in the catalog; without a language, Deepgram detects it |
aura-2 and aura in the catalog; the voice completes the model, as in aura-2 with voice thalia-en |
@mayurajs/voice-assemblyai |
universal-3-5-pro and universal-2 in the catalog; transcripts are deleted from AssemblyAI once read, unless retainTranscripts: true |
None: AssemblyAI has no speech API |
@mayurajs/voice-google |
chirp_3, chirp_2 and telephony in the catalog, up to a minute of audio per call; without a language, Chirp 3 detects it |
chirp3-hd, studio, neural2, polyglot, wavenet and standard in the catalog; the voice must belong to the family, as in chirp3-hd with en-US-Chirp3-HD-Charon |
@mayurajs/voice-azure |
fast (fast transcription) in the catalog; mai-transcribe-2 with your own price; without a language, Azure detects it |
neural, neural-hd and neural-hd-flash in the catalog; the voice must belong to the family, as in neural-hd with en-US-Ava:DragonHDLatestNeural, and Azure OpenAI voices are refused |
@mayurajs/voice-cartesia |
ink-whisper in the catalog; without a language, Cartesia assumes English |
sonic-3.6, sonic-3.5, sonic-3 and sonic-latest in the catalog; the voice is a Cartesia voice id |
OpenAI bills some audio models per token rather than per minute or character. Their cost cannot be known from the audio or text before a call, so they are left out of the catalog: give a per-minute or per-character price you are comfortable bounding calls with.
Testing a provider
voiceAdapterConformance in mayura/testing is the voice adapter contract as test cases: result shapes, costs,
error reasons without the provider's text, cancellation and timeouts. A provider package runs it against a fake
transport, as modelAdapterConformance does for model providers.