Skip to content

feat: add Codex OAuth voice-to-text input#4174

Closed
maria-rcks wants to merge 1 commit into
pingdotgg:mainfrom
maria-rcks:voice-to-text
Closed

feat: add Codex OAuth voice-to-text input#4174
maria-rcks wants to merge 1 commit into
pingdotgg:mainfrom
maria-rcks:voice-to-text

Conversation

@maria-rcks

@maria-rcks maria-rcks commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

What Changed

  • Added a microphone control to the chat composer for recording and transcribing voice input.
  • Added an authenticated server endpoint that uses the selected Codex provider instance’s existing ChatGPT OAuth credentials.
  • Sends recordings to the same private transcription endpoint used by Codex Desktop.
  • Disables voice input when Codex ChatGPT OAuth is unavailable.
  • Added recording lifecycle guards and focused server tests.

Why

This brings Codex-style dictation to the web composer without requiring a separate API key or client-selected transcription model. Authentication stays server-side and reuses the user’s existing Codex OAuth session.

UI Changes

Adds a microphone button beside the composer’s send action, with recording, transcribing, disabled, and error states.

image image image

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Add voice-to-text transcription via Codex OAuth to the chat composer

  • Adds a POST /api/voice/transcribe endpoint to the environment HTTP API, defined in environmentHttp.ts and served by http.ts, gated by AuthOrchestrationOperateScope.
  • Adds VoiceTranscription.ts which reads ChatGPT OAuth tokens from the Codex shadow home's auth.json, validates audio (max 25 MB), and posts to https://chatgpt.com/backend-api/transcribe.
  • Adds ComposerVoiceInput.tsx, a mic button in the chat composer that records audio via MediaRecorder, sends it to the transcription endpoint, and inserts the result at the cursor.
  • Only Codex provider instances with valid ChatGPT OAuth credentials can use transcription; API-key auth returns EnvironmentHttpForbiddenError.
📊 Macroscope summarized efb05ad. 5 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@github-actions github-actions Bot added the size:L 100-499 changed lines (additions + deletions). label Jul 20, 2026
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ff48167e-f70c-49d0-8330-13497089ab34

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

setState("requesting");
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
if (operation !== operationRef.current) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium chat/ComposerVoiceInput.tsx:109

startRecording only checks operationRef after getUserMedia resolves, so a pending microphone permission request is not cancelled when voice input becomes unavailable mid-prompt. If the composer becomes disabled or Codex OAuth becomes unavailable while the browser permission dialog is open, granting permission still starts recording and can later insert a transcription even though voice input is no longer available. The getUserMedia call is awaited without a cancellation path, so the stale operation check on line 109 only fires after the promise resolves — by then the user has already interacted with the permission prompt. Consider checking unavailableReason (or props.disabled / props.hasCodexOauth) again after getUserMedia resolves, before creating and starting the MediaRecorder.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/chat/ComposerVoiceInput.tsx around line 109:

`startRecording` only checks `operationRef` after `getUserMedia` resolves, so a pending microphone permission request is not cancelled when voice input becomes unavailable mid-prompt. If the composer becomes disabled or Codex OAuth becomes unavailable while the browser permission dialog is open, granting permission still starts recording and can later insert a transcription even though voice input is no longer available. The `getUserMedia` call is awaited without a cancellation path, so the stale operation check on line 109 only fires after the promise resolves — by then the user has already interacted with the permission prompt. Consider checking `unavailableReason` (or `props.disabled` / `props.hasCodexOauth`) again after `getUserMedia` resolves, before creating and starting the `MediaRecorder`.

headers: OptionalBearerHeaders,
payload: Schema.Struct({
providerInstanceId: ProviderInstanceId,
audio: Schema.Uint8ArrayFromBase64,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High src/environmentHttp.ts:559

The /api/voice/transcribe endpoint accepts audio: Schema.Uint8ArrayFromBase64 with no size bound before decoding. An authenticated caller can submit an arbitrarily large base64 string, forcing the server to buffer the entire request body and allocate the decoded Uint8Array before the 25 MB check in transcribeCodexAudio runs. Sufficiently large requests exhaust server memory and crash the process. Consider enforcing a maximum request body size or a base64-string length constraint (e.g. Schema.isMaxLength) before the Uint8Array decode step.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/contracts/src/environmentHttp.ts around line 559:

The `/api/voice/transcribe` endpoint accepts `audio: Schema.Uint8ArrayFromBase64` with no size bound before decoding. An authenticated caller can submit an arbitrarily large base64 string, forcing the server to buffer the entire request body and allocate the decoded `Uint8Array` before the 25 MB check in `transcribeCodexAudio` runs. Sufficiently large requests exhaust server memory and crash the process. Consider enforcing a maximum request body size or a base64-string length constraint (e.g. `Schema.isMaxLength`) before the `Uint8Array` decode step.

@macroscopeapp

macroscopeapp Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

2 blocking correctness issues found. This PR introduces a new voice input feature with new API endpoints and UI components. An unresolved HIGH severity comment identifies a potential DoS vulnerability in the transcribe endpoint where unbounded request bodies are decoded before size validation. New feature scope plus security concern warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@maria-rcks maria-rcks closed this Jul 20, 2026
@maria-rcks
maria-rcks deleted the voice-to-text branch July 20, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant