For maintainers. Using T3 Code? See voice input on iPhone.
Voice input produces editable composer text. The current implementation records on the client and
transcribes locally with Apple's SpeechAnalyzer and SpeechTranscriber on supported iOS 26+
devices. Environment-provided transcription and transcription on web and desktop are not implemented.
The shared VoiceInputController in packages/client-runtime owns preparation,
recording, transcription, cancellation, temporary-file cleanup, and insertion into the captured
draft selection. Applications import it through the voice-input entry point as
@t3tools/client-runtime/voice-input. Its dependencies separate capture from transcription; the
controller imports neither React Native nor an Apple transcription API.
The shared transcription contract defines VoiceTranscriber,
PreparedVoiceTranscription, and transcription errors. The controller calls getTranscriber() once
at the start of an operation, before asking for microphone permission. Preparation returns a resolved
locale and a bound transcribe function. The controller retains that result for the recording, so a
selection change cannot prepare with one implementation and transcribe with another.
useVoiceInputController supplies Expo audio capture, microphone permissions, audio-session
management, waveform samples, and app and navigation lifecycle handling. It normalizes Expo's
mediaServicesDidReset into a generic recorder error. voiceTranscription.ios.ts adapts
@react-native-ai/apple through getLocalVoiceTranscriber(), capturing the requested device locale
and binding the prepared transcriber to Apple's resolved locale. The other-platform binding returns
no local transcriber. That result describes the local implementation, not whether a client could use
an environment's transcription service.
Mobile's voiceInputPresentation.ts maps shared state to toolbar labels and actions.
Waveform and toolbar rendering stay in mobile. The composer edits draft text without selecting a
speech vendor.
Recording captures the draft owner, revision, text, and selection. A late transcript cannot overwrite
a different or edited draft. Only normal message submission sends the resulting text to an agent.
Each operation passes one AbortSignal through preparation and transcription. Cancellation
invalidates the operation and aborts that signal immediately. Implementations settle their promises
only after their underlying work stops. The Apple binding checks cancellation between asynchronous
steps but cannot interrupt an in-flight native request. The controller retains its session until
that work settles, ignores its result, and cleans up the recording.
The extension boundary distinguishes transcription on the client device from transcription through the composer's environment. These constraints apply when adding selectable transcription services:
providerInstances;
selecting OpenAI for transcription does not select Codex for the thread.environmentId. Its choices are
supported local recognition and the services exposed by the composer's environment. A service ID
is meaningful only within that environment. Different clients can make different choices.ServerSettingsService and ServerSecretStore provide environment-owned
configuration and secret persistence. Existing settings redaction handles coding-provider environment
variables only. Any transcription credential fields need their own explicit separation and redaction
before settings responses or subscriptions reach client caches.
ExecutionEnvironmentCapabilities handles version skew. Remote transcription must be
opt-in: a missing transcription capability means unsupported. The authenticated server-config
subscription and shared environment state already distribute configuration per
environment. A transcription service catalog belongs behind that capability and authenticated
boundary. Older servers expose no remote transcription choices.
The attachment upload contracts and shared upload operations provide a pattern for authorized binary uploads through an environment, including remote connections. Their existing chat-attachment retention is not a transcription cleanup policy.
Future service selection and environment requests belong alongside the controller in
packages/client-runtime, with wire contracts in packages/contracts. Capture and native local
recognition remain client-specific. An environment-backed transcriber implements the same shared
contract, with its environment and service bound when selected. The controller does not own service
credentials, provider SDKs, or transport selection.