feat(voice): add Codex subscription transcription - #5647

Closed
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription
Closed

feat(voice): add Codex subscription transcription#5647
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription

Conversation

@fbal98

@fbal98fbal98 commented Aug 7, 2026

Copy link
Copy Markdown

Problem

The voice dictation beta in #5213 supports OpenAI and Groq API keys, but users already signed in to Codex with ChatGPT cannot reuse that subscription for transcription.

What changed

  • add Codex subscription to the existing transcription provider interface
  • resolve the first configured Codex home, including shadow-home layouts, and read its file-based ChatGPT credentials on the server
  • send recorded audio to the Codex Desktop transcription endpoint with credentials kept entirely host-side
  • show Codex authentication availability in Beta settings without exposing token material to the client
  • preserve the existing 25 MB limit, authenticated server route, recording flow, and OpenAI/Groq adapters
  • document that the Codex endpoint is private and experimental

This is intentionally stacked on #5213 so the change stays at its existing transcription seam instead of duplicating the composer, recording, settings, and HTTP work in that PR.

User impact

Users with file-based Codex ChatGPT credentials can select Codex subscription for dictation without configuring a separate speech-to-text API key. A missing or rejected login produces a bounded error and suggests signing in with codex login.

Checks

Focused server, web, and contract tests were added. They were not run on this limited VPS per its workspace instructions; CI should run validation.

UI

Adds a Codex subscription provider option and a host authentication status row in Settings → Beta features → Voice dictation. Screenshots are pending because browser/computer-use validation was not authorized for this run.

Built with GPT-5.6 Sol via Codex in T3 Code.

Note

Add Codex subscription as a voice transcription provider

  • Adds 'codex' as a valid VoiceTranscriptionProvider, routed through a new forwardCodexVoiceTranscription effect that posts audio to the Codex Desktop transcription endpoint using file-based ChatGPT OAuth credentials from the server.
  • Reads credentials via a new resolveCodexVoiceCredentials effect that locates the Codex provider config, reads auth.json, and extracts access_token and account_id.
  • Exposes a codexTranscriptionAuthStatus boolean on GET /api/transcription and surfaces it in the settings UI, which shows a server-side auth status row instead of API key/model inputs when Codex is selected.
  • Model listing returns an empty list for Codex; no API key or model is sent in transcription requests.
📊 Macroscope summarized 6ddfeec. 6 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

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 Plus

Run ID: 56ef973e-1d1e-4e0a-a446-377e9d7c6d5f

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

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 7, 2026
@fbal98fbal98 closed this Aug 7, 2026

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review: 3 findings in apps/server/src/transcription.ts (error modeling and catchTag usage).

Posted via Macroscope — Effect Service Conventions

Comment on lines +213 to +215
export const codexTranscriptionAuthStatus = resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError", () => Effect.succeed(false)),

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.

Effect.catchTag should be Effect.catchTags({ ... }), even when handling a single known tag.

Suggested change
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError",()=>Effect.succeed(false)),
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTags({TranscriptionCodexAuthError:()=>Effect.succeed(false)}),

Posted via Macroscope — Effect Service Conventions

Comment on lines +94 to +101
export class TranscriptionCodexAuthError extends Schema.TaggedErrorClass<TranscriptionCodexAuthError>()(
"TranscriptionCodexAuthError",
{},
) {
override get message(): string {
return "Voice transcription requires file-based Codex credentials signed in with ChatGPT.";
}
}

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.

TranscriptionCodexAuthError carries no structural attributes and no cause, yet resolveCodexVoiceCredentials (lines 183-205) collapses five distinct failures into it — settings read failure, no Codex instance configured, CodexSettings decode failure, auth.json read failure, and auth.json JSON/schema decode failure — discarding both the underlying error and the known authPath.

Consider giving the error a multi-value operation/stage field plus the resolved path, and an optional cause: Schema.Defect() (the "no Codex instance" case legitimately has no underlying failure), then mapping each step with the context known at that site. CodexShadowHomeFileSystemError in provider/Drivers/CodexHomeLayout.ts is the existing shape to follow (operation, path, cause). The message getter can stay derived from those attributes so the caller-visible HTTP message remains stable.

Posted via Macroscope — Effect Service Conventions

Comment on lines +247 to +249
if (response.status === 401 || response.status === 403) {
return yield* new TranscriptionCodexAuthError();
}

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.

This branch is only reachable for openai/groq (codex returns early on line 236), so a 401/403 from the OpenAI or Groq models endpoint is reported with the Codex-specific message "Voice transcription requires file-based Codex credentials…". The error tag no longer identifies the failure structurally, and the models route in http.ts does not handle TranscriptionCodexAuthError, so the failure escapes its catchTags. Suggest dropping the branch and letting TranscriptionProviderError carry the status.

 if (response.status < 200 || response.status >= 300) {
- if (response.status === 401 || response.status === 403) {- return yield* new TranscriptionCodexAuthError();- }

Posted via Macroscope — Effect Service Conventions

@@ -81,6 +86,7 @@ export function BetaSettingsPanel() {
);
const voiceTranscriptionModel = useClientSettings((settings) => settings.voiceTranscriptionModel);
const [environmentApiKeys, setEnvironmentApiKeys] = useState({

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.

🟡 Mediumsettings/BetaSettingsPanel.tsx:88

The Codex authentication row shows Unavailable whenever environmentApiKeys.codex is false, but readVoiceTranscriptionEnvironmentStatus failures are silently swallowed by .catch(() => undefined), leaving all environment keys at their initial false values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/settings/BetaSettingsPanel.tsx around line 88:
The Codex authentication row shows `Unavailable` whenever `environmentApiKeys.codex` is `false`, but `readVoiceTranscriptionEnvironmentStatus` failures are silently swallowed by `.catch(() => undefined)`, leaving all environment keys at their initial `false` values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

httpClient.execute,
Effect.mapError((cause) => new TranscriptionRequestError({ provider: "codex", cause })),
Effect.flatMap((response) =>
Effect.gen(function* () {

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.

🟠 Highsrc/transcription.ts:329

When the Codex transcription endpoint returns HTTP 401 or 403, forwardCodexVoiceTranscription throws TranscriptionProviderError with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The listVoiceTranscriptionModels function already maps 401/403 to TranscriptionCodexAuthError; forwardCodexVoiceTranscription should do the same instead of falling through to the generic TranscriptionProviderError.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/transcription.ts around line 329:
When the Codex transcription endpoint returns HTTP 401 or 403, `forwardCodexVoiceTranscription` throws `TranscriptionProviderError` with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The `listVoiceTranscriptionModels` function already maps 401/403 to `TranscriptionCodexAuthError`; `forwardCodexVoiceTranscription` should do the same instead of falling through to the generic `TranscriptionProviderError`.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@fbal98
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(voice): add Codex subscription transcription - #5647

Closed
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription
Closed

feat(voice): add Codex subscription transcription#5647
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription

Conversation

@fbal98

@fbal98fbal98 commented Aug 7, 2026

Copy link
Copy Markdown

Problem

The voice dictation beta in #5213 supports OpenAI and Groq API keys, but users already signed in to Codex with ChatGPT cannot reuse that subscription for transcription.

What changed

  • add Codex subscription to the existing transcription provider interface
  • resolve the first configured Codex home, including shadow-home layouts, and read its file-based ChatGPT credentials on the server
  • send recorded audio to the Codex Desktop transcription endpoint with credentials kept entirely host-side
  • show Codex authentication availability in Beta settings without exposing token material to the client
  • preserve the existing 25 MB limit, authenticated server route, recording flow, and OpenAI/Groq adapters
  • document that the Codex endpoint is private and experimental

This is intentionally stacked on #5213 so the change stays at its existing transcription seam instead of duplicating the composer, recording, settings, and HTTP work in that PR.

User impact

Users with file-based Codex ChatGPT credentials can select Codex subscription for dictation without configuring a separate speech-to-text API key. A missing or rejected login produces a bounded error and suggests signing in with codex login.

Checks

Focused server, web, and contract tests were added. They were not run on this limited VPS per its workspace instructions; CI should run validation.

UI

Adds a Codex subscription provider option and a host authentication status row in Settings → Beta features → Voice dictation. Screenshots are pending because browser/computer-use validation was not authorized for this run.

Built with GPT-5.6 Sol via Codex in T3 Code.

Note

Add Codex subscription as a voice transcription provider

  • Adds 'codex' as a valid VoiceTranscriptionProvider, routed through a new forwardCodexVoiceTranscription effect that posts audio to the Codex Desktop transcription endpoint using file-based ChatGPT OAuth credentials from the server.
  • Reads credentials via a new resolveCodexVoiceCredentials effect that locates the Codex provider config, reads auth.json, and extracts access_token and account_id.
  • Exposes a codexTranscriptionAuthStatus boolean on GET /api/transcription and surfaces it in the settings UI, which shows a server-side auth status row instead of API key/model inputs when Codex is selected.
  • Model listing returns an empty list for Codex; no API key or model is sent in transcription requests.
📊 Macroscope summarized 6ddfeec. 6 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

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 Plus

Run ID: 56ef973e-1d1e-4e0a-a446-377e9d7c6d5f

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

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 7, 2026
@fbal98fbal98 closed this Aug 7, 2026

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review: 3 findings in apps/server/src/transcription.ts (error modeling and catchTag usage).

Posted via Macroscope — Effect Service Conventions

Comment on lines +213 to +215
export const codexTranscriptionAuthStatus = resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError", () => Effect.succeed(false)),

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.

Effect.catchTag should be Effect.catchTags({ ... }), even when handling a single known tag.

Suggested change
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError",()=>Effect.succeed(false)),
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTags({TranscriptionCodexAuthError:()=>Effect.succeed(false)}),

Posted via Macroscope — Effect Service Conventions

Comment on lines +94 to +101
export class TranscriptionCodexAuthError extends Schema.TaggedErrorClass<TranscriptionCodexAuthError>()(
"TranscriptionCodexAuthError",
{},
) {
override get message(): string {
return "Voice transcription requires file-based Codex credentials signed in with ChatGPT.";
}
}

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.

TranscriptionCodexAuthError carries no structural attributes and no cause, yet resolveCodexVoiceCredentials (lines 183-205) collapses five distinct failures into it — settings read failure, no Codex instance configured, CodexSettings decode failure, auth.json read failure, and auth.json JSON/schema decode failure — discarding both the underlying error and the known authPath.

Consider giving the error a multi-value operation/stage field plus the resolved path, and an optional cause: Schema.Defect() (the "no Codex instance" case legitimately has no underlying failure), then mapping each step with the context known at that site. CodexShadowHomeFileSystemError in provider/Drivers/CodexHomeLayout.ts is the existing shape to follow (operation, path, cause). The message getter can stay derived from those attributes so the caller-visible HTTP message remains stable.

Posted via Macroscope — Effect Service Conventions

Comment on lines +247 to +249
if (response.status === 401 || response.status === 403) {
return yield* new TranscriptionCodexAuthError();
}

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.

This branch is only reachable for openai/groq (codex returns early on line 236), so a 401/403 from the OpenAI or Groq models endpoint is reported with the Codex-specific message "Voice transcription requires file-based Codex credentials…". The error tag no longer identifies the failure structurally, and the models route in http.ts does not handle TranscriptionCodexAuthError, so the failure escapes its catchTags. Suggest dropping the branch and letting TranscriptionProviderError carry the status.

 if (response.status < 200 || response.status >= 300) {
- if (response.status === 401 || response.status === 403) {- return yield* new TranscriptionCodexAuthError();- }

Posted via Macroscope — Effect Service Conventions

@@ -81,6 +86,7 @@ export function BetaSettingsPanel() {
);
const voiceTranscriptionModel = useClientSettings((settings) => settings.voiceTranscriptionModel);
const [environmentApiKeys, setEnvironmentApiKeys] = useState({

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.

🟡 Mediumsettings/BetaSettingsPanel.tsx:88

The Codex authentication row shows Unavailable whenever environmentApiKeys.codex is false, but readVoiceTranscriptionEnvironmentStatus failures are silently swallowed by .catch(() => undefined), leaving all environment keys at their initial false values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/settings/BetaSettingsPanel.tsx around line 88:
The Codex authentication row shows `Unavailable` whenever `environmentApiKeys.codex` is `false`, but `readVoiceTranscriptionEnvironmentStatus` failures are silently swallowed by `.catch(() => undefined)`, leaving all environment keys at their initial `false` values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

httpClient.execute,
Effect.mapError((cause) => new TranscriptionRequestError({ provider: "codex", cause })),
Effect.flatMap((response) =>
Effect.gen(function* () {

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.

🟠 Highsrc/transcription.ts:329

When the Codex transcription endpoint returns HTTP 401 or 403, forwardCodexVoiceTranscription throws TranscriptionProviderError with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The listVoiceTranscriptionModels function already maps 401/403 to TranscriptionCodexAuthError; forwardCodexVoiceTranscription should do the same instead of falling through to the generic TranscriptionProviderError.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/transcription.ts around line 329:
When the Codex transcription endpoint returns HTTP 401 or 403, `forwardCodexVoiceTranscription` throws `TranscriptionProviderError` with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The `listVoiceTranscriptionModels` function already maps 401/403 to `TranscriptionCodexAuthError`; `forwardCodexVoiceTranscription` should do the same instead of falling through to the generic `TranscriptionProviderError`.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@fbal98
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(voice): add Codex subscription transcription - #5647

Closed
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription
Closed

feat(voice): add Codex subscription transcription#5647
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription

Conversation

@fbal98

@fbal98fbal98 commented Aug 7, 2026

Copy link
Copy Markdown

Problem

The voice dictation beta in #5213 supports OpenAI and Groq API keys, but users already signed in to Codex with ChatGPT cannot reuse that subscription for transcription.

What changed

  • add Codex subscription to the existing transcription provider interface
  • resolve the first configured Codex home, including shadow-home layouts, and read its file-based ChatGPT credentials on the server
  • send recorded audio to the Codex Desktop transcription endpoint with credentials kept entirely host-side
  • show Codex authentication availability in Beta settings without exposing token material to the client
  • preserve the existing 25 MB limit, authenticated server route, recording flow, and OpenAI/Groq adapters
  • document that the Codex endpoint is private and experimental

This is intentionally stacked on #5213 so the change stays at its existing transcription seam instead of duplicating the composer, recording, settings, and HTTP work in that PR.

User impact

Users with file-based Codex ChatGPT credentials can select Codex subscription for dictation without configuring a separate speech-to-text API key. A missing or rejected login produces a bounded error and suggests signing in with codex login.

Checks

Focused server, web, and contract tests were added. They were not run on this limited VPS per its workspace instructions; CI should run validation.

UI

Adds a Codex subscription provider option and a host authentication status row in Settings → Beta features → Voice dictation. Screenshots are pending because browser/computer-use validation was not authorized for this run.

Built with GPT-5.6 Sol via Codex in T3 Code.

Note

Add Codex subscription as a voice transcription provider

  • Adds 'codex' as a valid VoiceTranscriptionProvider, routed through a new forwardCodexVoiceTranscription effect that posts audio to the Codex Desktop transcription endpoint using file-based ChatGPT OAuth credentials from the server.
  • Reads credentials via a new resolveCodexVoiceCredentials effect that locates the Codex provider config, reads auth.json, and extracts access_token and account_id.
  • Exposes a codexTranscriptionAuthStatus boolean on GET /api/transcription and surfaces it in the settings UI, which shows a server-side auth status row instead of API key/model inputs when Codex is selected.
  • Model listing returns an empty list for Codex; no API key or model is sent in transcription requests.
📊 Macroscope summarized 6ddfeec. 6 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

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 Plus

Run ID: 56ef973e-1d1e-4e0a-a446-377e9d7c6d5f

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

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 7, 2026
@fbal98fbal98 closed this Aug 7, 2026

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review: 3 findings in apps/server/src/transcription.ts (error modeling and catchTag usage).

Posted via Macroscope — Effect Service Conventions

Comment on lines +213 to +215
export const codexTranscriptionAuthStatus = resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError", () => Effect.succeed(false)),

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.

Effect.catchTag should be Effect.catchTags({ ... }), even when handling a single known tag.

Suggested change
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError",()=>Effect.succeed(false)),
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTags({TranscriptionCodexAuthError:()=>Effect.succeed(false)}),

Posted via Macroscope — Effect Service Conventions

Comment on lines +94 to +101
export class TranscriptionCodexAuthError extends Schema.TaggedErrorClass<TranscriptionCodexAuthError>()(
"TranscriptionCodexAuthError",
{},
) {
override get message(): string {
return "Voice transcription requires file-based Codex credentials signed in with ChatGPT.";
}
}

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.

TranscriptionCodexAuthError carries no structural attributes and no cause, yet resolveCodexVoiceCredentials (lines 183-205) collapses five distinct failures into it — settings read failure, no Codex instance configured, CodexSettings decode failure, auth.json read failure, and auth.json JSON/schema decode failure — discarding both the underlying error and the known authPath.

Consider giving the error a multi-value operation/stage field plus the resolved path, and an optional cause: Schema.Defect() (the "no Codex instance" case legitimately has no underlying failure), then mapping each step with the context known at that site. CodexShadowHomeFileSystemError in provider/Drivers/CodexHomeLayout.ts is the existing shape to follow (operation, path, cause). The message getter can stay derived from those attributes so the caller-visible HTTP message remains stable.

Posted via Macroscope — Effect Service Conventions

Comment on lines +247 to +249
if (response.status === 401 || response.status === 403) {
return yield* new TranscriptionCodexAuthError();
}

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.

This branch is only reachable for openai/groq (codex returns early on line 236), so a 401/403 from the OpenAI or Groq models endpoint is reported with the Codex-specific message "Voice transcription requires file-based Codex credentials…". The error tag no longer identifies the failure structurally, and the models route in http.ts does not handle TranscriptionCodexAuthError, so the failure escapes its catchTags. Suggest dropping the branch and letting TranscriptionProviderError carry the status.

 if (response.status < 200 || response.status >= 300) {
- if (response.status === 401 || response.status === 403) {- return yield* new TranscriptionCodexAuthError();- }

Posted via Macroscope — Effect Service Conventions

@@ -81,6 +86,7 @@ export function BetaSettingsPanel() {
);
const voiceTranscriptionModel = useClientSettings((settings) => settings.voiceTranscriptionModel);
const [environmentApiKeys, setEnvironmentApiKeys] = useState({

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.

🟡 Mediumsettings/BetaSettingsPanel.tsx:88

The Codex authentication row shows Unavailable whenever environmentApiKeys.codex is false, but readVoiceTranscriptionEnvironmentStatus failures are silently swallowed by .catch(() => undefined), leaving all environment keys at their initial false values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/settings/BetaSettingsPanel.tsx around line 88:
The Codex authentication row shows `Unavailable` whenever `environmentApiKeys.codex` is `false`, but `readVoiceTranscriptionEnvironmentStatus` failures are silently swallowed by `.catch(() => undefined)`, leaving all environment keys at their initial `false` values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

httpClient.execute,
Effect.mapError((cause) => new TranscriptionRequestError({ provider: "codex", cause })),
Effect.flatMap((response) =>
Effect.gen(function* () {

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.

🟠 Highsrc/transcription.ts:329

When the Codex transcription endpoint returns HTTP 401 or 403, forwardCodexVoiceTranscription throws TranscriptionProviderError with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The listVoiceTranscriptionModels function already maps 401/403 to TranscriptionCodexAuthError; forwardCodexVoiceTranscription should do the same instead of falling through to the generic TranscriptionProviderError.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/transcription.ts around line 329:
When the Codex transcription endpoint returns HTTP 401 or 403, `forwardCodexVoiceTranscription` throws `TranscriptionProviderError` with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The `listVoiceTranscriptionModels` function already maps 401/403 to `TranscriptionCodexAuthError`; `forwardCodexVoiceTranscription` should do the same instead of falling through to the generic `TranscriptionProviderError`.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@fbal98
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(voice): add Codex subscription transcription - #5647

Closed
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription
Closed

feat(voice): add Codex subscription transcription#5647
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription

Conversation

@fbal98

@fbal98fbal98 commented Aug 7, 2026

Copy link
Copy Markdown

Problem

The voice dictation beta in #5213 supports OpenAI and Groq API keys, but users already signed in to Codex with ChatGPT cannot reuse that subscription for transcription.

What changed

  • add Codex subscription to the existing transcription provider interface
  • resolve the first configured Codex home, including shadow-home layouts, and read its file-based ChatGPT credentials on the server
  • send recorded audio to the Codex Desktop transcription endpoint with credentials kept entirely host-side
  • show Codex authentication availability in Beta settings without exposing token material to the client
  • preserve the existing 25 MB limit, authenticated server route, recording flow, and OpenAI/Groq adapters
  • document that the Codex endpoint is private and experimental

This is intentionally stacked on #5213 so the change stays at its existing transcription seam instead of duplicating the composer, recording, settings, and HTTP work in that PR.

User impact

Users with file-based Codex ChatGPT credentials can select Codex subscription for dictation without configuring a separate speech-to-text API key. A missing or rejected login produces a bounded error and suggests signing in with codex login.

Checks

Focused server, web, and contract tests were added. They were not run on this limited VPS per its workspace instructions; CI should run validation.

UI

Adds a Codex subscription provider option and a host authentication status row in Settings → Beta features → Voice dictation. Screenshots are pending because browser/computer-use validation was not authorized for this run.

Built with GPT-5.6 Sol via Codex in T3 Code.

Note

Add Codex subscription as a voice transcription provider

  • Adds 'codex' as a valid VoiceTranscriptionProvider, routed through a new forwardCodexVoiceTranscription effect that posts audio to the Codex Desktop transcription endpoint using file-based ChatGPT OAuth credentials from the server.
  • Reads credentials via a new resolveCodexVoiceCredentials effect that locates the Codex provider config, reads auth.json, and extracts access_token and account_id.
  • Exposes a codexTranscriptionAuthStatus boolean on GET /api/transcription and surfaces it in the settings UI, which shows a server-side auth status row instead of API key/model inputs when Codex is selected.
  • Model listing returns an empty list for Codex; no API key or model is sent in transcription requests.
📊 Macroscope summarized 6ddfeec. 6 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

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 Plus

Run ID: 56ef973e-1d1e-4e0a-a446-377e9d7c6d5f

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

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 7, 2026
@fbal98fbal98 closed this Aug 7, 2026

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review: 3 findings in apps/server/src/transcription.ts (error modeling and catchTag usage).

Posted via Macroscope — Effect Service Conventions

Comment on lines +213 to +215
export const codexTranscriptionAuthStatus = resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError", () => Effect.succeed(false)),

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.

Effect.catchTag should be Effect.catchTags({ ... }), even when handling a single known tag.

Suggested change
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError",()=>Effect.succeed(false)),
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTags({TranscriptionCodexAuthError:()=>Effect.succeed(false)}),

Posted via Macroscope — Effect Service Conventions

Comment on lines +94 to +101
export class TranscriptionCodexAuthError extends Schema.TaggedErrorClass<TranscriptionCodexAuthError>()(
"TranscriptionCodexAuthError",
{},
) {
override get message(): string {
return "Voice transcription requires file-based Codex credentials signed in with ChatGPT.";
}
}

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.

TranscriptionCodexAuthError carries no structural attributes and no cause, yet resolveCodexVoiceCredentials (lines 183-205) collapses five distinct failures into it — settings read failure, no Codex instance configured, CodexSettings decode failure, auth.json read failure, and auth.json JSON/schema decode failure — discarding both the underlying error and the known authPath.

Consider giving the error a multi-value operation/stage field plus the resolved path, and an optional cause: Schema.Defect() (the "no Codex instance" case legitimately has no underlying failure), then mapping each step with the context known at that site. CodexShadowHomeFileSystemError in provider/Drivers/CodexHomeLayout.ts is the existing shape to follow (operation, path, cause). The message getter can stay derived from those attributes so the caller-visible HTTP message remains stable.

Posted via Macroscope — Effect Service Conventions

Comment on lines +247 to +249
if (response.status === 401 || response.status === 403) {
return yield* new TranscriptionCodexAuthError();
}

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.

This branch is only reachable for openai/groq (codex returns early on line 236), so a 401/403 from the OpenAI or Groq models endpoint is reported with the Codex-specific message "Voice transcription requires file-based Codex credentials…". The error tag no longer identifies the failure structurally, and the models route in http.ts does not handle TranscriptionCodexAuthError, so the failure escapes its catchTags. Suggest dropping the branch and letting TranscriptionProviderError carry the status.

 if (response.status < 200 || response.status >= 300) {
- if (response.status === 401 || response.status === 403) {- return yield* new TranscriptionCodexAuthError();- }

Posted via Macroscope — Effect Service Conventions

@@ -81,6 +86,7 @@ export function BetaSettingsPanel() {
);
const voiceTranscriptionModel = useClientSettings((settings) => settings.voiceTranscriptionModel);
const [environmentApiKeys, setEnvironmentApiKeys] = useState({

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.

🟡 Mediumsettings/BetaSettingsPanel.tsx:88

The Codex authentication row shows Unavailable whenever environmentApiKeys.codex is false, but readVoiceTranscriptionEnvironmentStatus failures are silently swallowed by .catch(() => undefined), leaving all environment keys at their initial false values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/settings/BetaSettingsPanel.tsx around line 88:
The Codex authentication row shows `Unavailable` whenever `environmentApiKeys.codex` is `false`, but `readVoiceTranscriptionEnvironmentStatus` failures are silently swallowed by `.catch(() => undefined)`, leaving all environment keys at their initial `false` values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

httpClient.execute,
Effect.mapError((cause) => new TranscriptionRequestError({ provider: "codex", cause })),
Effect.flatMap((response) =>
Effect.gen(function* () {

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.

🟠 Highsrc/transcription.ts:329

When the Codex transcription endpoint returns HTTP 401 or 403, forwardCodexVoiceTranscription throws TranscriptionProviderError with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The listVoiceTranscriptionModels function already maps 401/403 to TranscriptionCodexAuthError; forwardCodexVoiceTranscription should do the same instead of falling through to the generic TranscriptionProviderError.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/transcription.ts around line 329:
When the Codex transcription endpoint returns HTTP 401 or 403, `forwardCodexVoiceTranscription` throws `TranscriptionProviderError` with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The `listVoiceTranscriptionModels` function already maps 401/403 to `TranscriptionCodexAuthError`; `forwardCodexVoiceTranscription` should do the same instead of falling through to the generic `TranscriptionProviderError`.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@fbal98
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(voice): add Codex subscription transcription - #5647

Closed
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription
Closed

feat(voice): add Codex subscription transcription#5647
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription

Conversation

@fbal98

@fbal98fbal98 commented Aug 7, 2026

Copy link
Copy Markdown

Problem

The voice dictation beta in #5213 supports OpenAI and Groq API keys, but users already signed in to Codex with ChatGPT cannot reuse that subscription for transcription.

What changed

  • add Codex subscription to the existing transcription provider interface
  • resolve the first configured Codex home, including shadow-home layouts, and read its file-based ChatGPT credentials on the server
  • send recorded audio to the Codex Desktop transcription endpoint with credentials kept entirely host-side
  • show Codex authentication availability in Beta settings without exposing token material to the client
  • preserve the existing 25 MB limit, authenticated server route, recording flow, and OpenAI/Groq adapters
  • document that the Codex endpoint is private and experimental

This is intentionally stacked on #5213 so the change stays at its existing transcription seam instead of duplicating the composer, recording, settings, and HTTP work in that PR.

User impact

Users with file-based Codex ChatGPT credentials can select Codex subscription for dictation without configuring a separate speech-to-text API key. A missing or rejected login produces a bounded error and suggests signing in with codex login.

Checks

Focused server, web, and contract tests were added. They were not run on this limited VPS per its workspace instructions; CI should run validation.

UI

Adds a Codex subscription provider option and a host authentication status row in Settings → Beta features → Voice dictation. Screenshots are pending because browser/computer-use validation was not authorized for this run.

Built with GPT-5.6 Sol via Codex in T3 Code.

Note

Add Codex subscription as a voice transcription provider

  • Adds 'codex' as a valid VoiceTranscriptionProvider, routed through a new forwardCodexVoiceTranscription effect that posts audio to the Codex Desktop transcription endpoint using file-based ChatGPT OAuth credentials from the server.
  • Reads credentials via a new resolveCodexVoiceCredentials effect that locates the Codex provider config, reads auth.json, and extracts access_token and account_id.
  • Exposes a codexTranscriptionAuthStatus boolean on GET /api/transcription and surfaces it in the settings UI, which shows a server-side auth status row instead of API key/model inputs when Codex is selected.
  • Model listing returns an empty list for Codex; no API key or model is sent in transcription requests.
📊 Macroscope summarized 6ddfeec. 6 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

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 Plus

Run ID: 56ef973e-1d1e-4e0a-a446-377e9d7c6d5f

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

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 7, 2026
@fbal98fbal98 closed this Aug 7, 2026

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review: 3 findings in apps/server/src/transcription.ts (error modeling and catchTag usage).

Posted via Macroscope — Effect Service Conventions

Comment on lines +213 to +215
export const codexTranscriptionAuthStatus = resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError", () => Effect.succeed(false)),

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.

Effect.catchTag should be Effect.catchTags({ ... }), even when handling a single known tag.

Suggested change
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError",()=>Effect.succeed(false)),
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTags({TranscriptionCodexAuthError:()=>Effect.succeed(false)}),

Posted via Macroscope — Effect Service Conventions

Comment on lines +94 to +101
export class TranscriptionCodexAuthError extends Schema.TaggedErrorClass<TranscriptionCodexAuthError>()(
"TranscriptionCodexAuthError",
{},
) {
override get message(): string {
return "Voice transcription requires file-based Codex credentials signed in with ChatGPT.";
}
}

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.

TranscriptionCodexAuthError carries no structural attributes and no cause, yet resolveCodexVoiceCredentials (lines 183-205) collapses five distinct failures into it — settings read failure, no Codex instance configured, CodexSettings decode failure, auth.json read failure, and auth.json JSON/schema decode failure — discarding both the underlying error and the known authPath.

Consider giving the error a multi-value operation/stage field plus the resolved path, and an optional cause: Schema.Defect() (the "no Codex instance" case legitimately has no underlying failure), then mapping each step with the context known at that site. CodexShadowHomeFileSystemError in provider/Drivers/CodexHomeLayout.ts is the existing shape to follow (operation, path, cause). The message getter can stay derived from those attributes so the caller-visible HTTP message remains stable.

Posted via Macroscope — Effect Service Conventions

Comment on lines +247 to +249
if (response.status === 401 || response.status === 403) {
return yield* new TranscriptionCodexAuthError();
}

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.

This branch is only reachable for openai/groq (codex returns early on line 236), so a 401/403 from the OpenAI or Groq models endpoint is reported with the Codex-specific message "Voice transcription requires file-based Codex credentials…". The error tag no longer identifies the failure structurally, and the models route in http.ts does not handle TranscriptionCodexAuthError, so the failure escapes its catchTags. Suggest dropping the branch and letting TranscriptionProviderError carry the status.

 if (response.status < 200 || response.status >= 300) {
- if (response.status === 401 || response.status === 403) {- return yield* new TranscriptionCodexAuthError();- }

Posted via Macroscope — Effect Service Conventions

@@ -81,6 +86,7 @@ export function BetaSettingsPanel() {
);
const voiceTranscriptionModel = useClientSettings((settings) => settings.voiceTranscriptionModel);
const [environmentApiKeys, setEnvironmentApiKeys] = useState({

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.

🟡 Mediumsettings/BetaSettingsPanel.tsx:88

The Codex authentication row shows Unavailable whenever environmentApiKeys.codex is false, but readVoiceTranscriptionEnvironmentStatus failures are silently swallowed by .catch(() => undefined), leaving all environment keys at their initial false values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/settings/BetaSettingsPanel.tsx around line 88:
The Codex authentication row shows `Unavailable` whenever `environmentApiKeys.codex` is `false`, but `readVoiceTranscriptionEnvironmentStatus` failures are silently swallowed by `.catch(() => undefined)`, leaving all environment keys at their initial `false` values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

httpClient.execute,
Effect.mapError((cause) => new TranscriptionRequestError({ provider: "codex", cause })),
Effect.flatMap((response) =>
Effect.gen(function* () {

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.

🟠 Highsrc/transcription.ts:329

When the Codex transcription endpoint returns HTTP 401 or 403, forwardCodexVoiceTranscription throws TranscriptionProviderError with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The listVoiceTranscriptionModels function already maps 401/403 to TranscriptionCodexAuthError; forwardCodexVoiceTranscription should do the same instead of falling through to the generic TranscriptionProviderError.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/transcription.ts around line 329:
When the Codex transcription endpoint returns HTTP 401 or 403, `forwardCodexVoiceTranscription` throws `TranscriptionProviderError` with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The `listVoiceTranscriptionModels` function already maps 401/403 to `TranscriptionCodexAuthError`; `forwardCodexVoiceTranscription` should do the same instead of falling through to the generic `TranscriptionProviderError`.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@fbal98
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(voice): add Codex subscription transcription - #5647

Closed
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription
Closed

feat(voice): add Codex subscription transcription#5647
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription

Conversation

@fbal98

@fbal98fbal98 commented Aug 7, 2026

Copy link
Copy Markdown

Problem

The voice dictation beta in #5213 supports OpenAI and Groq API keys, but users already signed in to Codex with ChatGPT cannot reuse that subscription for transcription.

What changed

  • add Codex subscription to the existing transcription provider interface
  • resolve the first configured Codex home, including shadow-home layouts, and read its file-based ChatGPT credentials on the server
  • send recorded audio to the Codex Desktop transcription endpoint with credentials kept entirely host-side
  • show Codex authentication availability in Beta settings without exposing token material to the client
  • preserve the existing 25 MB limit, authenticated server route, recording flow, and OpenAI/Groq adapters
  • document that the Codex endpoint is private and experimental

This is intentionally stacked on #5213 so the change stays at its existing transcription seam instead of duplicating the composer, recording, settings, and HTTP work in that PR.

User impact

Users with file-based Codex ChatGPT credentials can select Codex subscription for dictation without configuring a separate speech-to-text API key. A missing or rejected login produces a bounded error and suggests signing in with codex login.

Checks

Focused server, web, and contract tests were added. They were not run on this limited VPS per its workspace instructions; CI should run validation.

UI

Adds a Codex subscription provider option and a host authentication status row in Settings → Beta features → Voice dictation. Screenshots are pending because browser/computer-use validation was not authorized for this run.

Built with GPT-5.6 Sol via Codex in T3 Code.

Note

Add Codex subscription as a voice transcription provider

  • Adds 'codex' as a valid VoiceTranscriptionProvider, routed through a new forwardCodexVoiceTranscription effect that posts audio to the Codex Desktop transcription endpoint using file-based ChatGPT OAuth credentials from the server.
  • Reads credentials via a new resolveCodexVoiceCredentials effect that locates the Codex provider config, reads auth.json, and extracts access_token and account_id.
  • Exposes a codexTranscriptionAuthStatus boolean on GET /api/transcription and surfaces it in the settings UI, which shows a server-side auth status row instead of API key/model inputs when Codex is selected.
  • Model listing returns an empty list for Codex; no API key or model is sent in transcription requests.
📊 Macroscope summarized 6ddfeec. 6 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

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 Plus

Run ID: 56ef973e-1d1e-4e0a-a446-377e9d7c6d5f

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

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 7, 2026
@fbal98fbal98 closed this Aug 7, 2026

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review: 3 findings in apps/server/src/transcription.ts (error modeling and catchTag usage).

Posted via Macroscope — Effect Service Conventions

Comment on lines +213 to +215
export const codexTranscriptionAuthStatus = resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError", () => Effect.succeed(false)),

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.

Effect.catchTag should be Effect.catchTags({ ... }), even when handling a single known tag.

Suggested change
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError",()=>Effect.succeed(false)),
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTags({TranscriptionCodexAuthError:()=>Effect.succeed(false)}),

Posted via Macroscope — Effect Service Conventions

Comment on lines +94 to +101
export class TranscriptionCodexAuthError extends Schema.TaggedErrorClass<TranscriptionCodexAuthError>()(
"TranscriptionCodexAuthError",
{},
) {
override get message(): string {
return "Voice transcription requires file-based Codex credentials signed in with ChatGPT.";
}
}

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.

TranscriptionCodexAuthError carries no structural attributes and no cause, yet resolveCodexVoiceCredentials (lines 183-205) collapses five distinct failures into it — settings read failure, no Codex instance configured, CodexSettings decode failure, auth.json read failure, and auth.json JSON/schema decode failure — discarding both the underlying error and the known authPath.

Consider giving the error a multi-value operation/stage field plus the resolved path, and an optional cause: Schema.Defect() (the "no Codex instance" case legitimately has no underlying failure), then mapping each step with the context known at that site. CodexShadowHomeFileSystemError in provider/Drivers/CodexHomeLayout.ts is the existing shape to follow (operation, path, cause). The message getter can stay derived from those attributes so the caller-visible HTTP message remains stable.

Posted via Macroscope — Effect Service Conventions

Comment on lines +247 to +249
if (response.status === 401 || response.status === 403) {
return yield* new TranscriptionCodexAuthError();
}

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.

This branch is only reachable for openai/groq (codex returns early on line 236), so a 401/403 from the OpenAI or Groq models endpoint is reported with the Codex-specific message "Voice transcription requires file-based Codex credentials…". The error tag no longer identifies the failure structurally, and the models route in http.ts does not handle TranscriptionCodexAuthError, so the failure escapes its catchTags. Suggest dropping the branch and letting TranscriptionProviderError carry the status.

 if (response.status < 200 || response.status >= 300) {
- if (response.status === 401 || response.status === 403) {- return yield* new TranscriptionCodexAuthError();- }

Posted via Macroscope — Effect Service Conventions

@@ -81,6 +86,7 @@ export function BetaSettingsPanel() {
);
const voiceTranscriptionModel = useClientSettings((settings) => settings.voiceTranscriptionModel);
const [environmentApiKeys, setEnvironmentApiKeys] = useState({

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.

🟡 Mediumsettings/BetaSettingsPanel.tsx:88

The Codex authentication row shows Unavailable whenever environmentApiKeys.codex is false, but readVoiceTranscriptionEnvironmentStatus failures are silently swallowed by .catch(() => undefined), leaving all environment keys at their initial false values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/settings/BetaSettingsPanel.tsx around line 88:
The Codex authentication row shows `Unavailable` whenever `environmentApiKeys.codex` is `false`, but `readVoiceTranscriptionEnvironmentStatus` failures are silently swallowed by `.catch(() => undefined)`, leaving all environment keys at their initial `false` values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

httpClient.execute,
Effect.mapError((cause) => new TranscriptionRequestError({ provider: "codex", cause })),
Effect.flatMap((response) =>
Effect.gen(function* () {

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.

🟠 Highsrc/transcription.ts:329

When the Codex transcription endpoint returns HTTP 401 or 403, forwardCodexVoiceTranscription throws TranscriptionProviderError with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The listVoiceTranscriptionModels function already maps 401/403 to TranscriptionCodexAuthError; forwardCodexVoiceTranscription should do the same instead of falling through to the generic TranscriptionProviderError.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/transcription.ts around line 329:
When the Codex transcription endpoint returns HTTP 401 or 403, `forwardCodexVoiceTranscription` throws `TranscriptionProviderError` with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The `listVoiceTranscriptionModels` function already maps 401/403 to `TranscriptionCodexAuthError`; `forwardCodexVoiceTranscription` should do the same instead of falling through to the generic `TranscriptionProviderError`.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@fbal98
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(voice): add Codex subscription transcription - #5647

Closed
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription
Closed

feat(voice): add Codex subscription transcription#5647
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription

Conversation

@fbal98

@fbal98fbal98 commented Aug 7, 2026

Copy link
Copy Markdown

Problem

The voice dictation beta in #5213 supports OpenAI and Groq API keys, but users already signed in to Codex with ChatGPT cannot reuse that subscription for transcription.

What changed

  • add Codex subscription to the existing transcription provider interface
  • resolve the first configured Codex home, including shadow-home layouts, and read its file-based ChatGPT credentials on the server
  • send recorded audio to the Codex Desktop transcription endpoint with credentials kept entirely host-side
  • show Codex authentication availability in Beta settings without exposing token material to the client
  • preserve the existing 25 MB limit, authenticated server route, recording flow, and OpenAI/Groq adapters
  • document that the Codex endpoint is private and experimental

This is intentionally stacked on #5213 so the change stays at its existing transcription seam instead of duplicating the composer, recording, settings, and HTTP work in that PR.

User impact

Users with file-based Codex ChatGPT credentials can select Codex subscription for dictation without configuring a separate speech-to-text API key. A missing or rejected login produces a bounded error and suggests signing in with codex login.

Checks

Focused server, web, and contract tests were added. They were not run on this limited VPS per its workspace instructions; CI should run validation.

UI

Adds a Codex subscription provider option and a host authentication status row in Settings → Beta features → Voice dictation. Screenshots are pending because browser/computer-use validation was not authorized for this run.

Built with GPT-5.6 Sol via Codex in T3 Code.

Note

Add Codex subscription as a voice transcription provider

  • Adds 'codex' as a valid VoiceTranscriptionProvider, routed through a new forwardCodexVoiceTranscription effect that posts audio to the Codex Desktop transcription endpoint using file-based ChatGPT OAuth credentials from the server.
  • Reads credentials via a new resolveCodexVoiceCredentials effect that locates the Codex provider config, reads auth.json, and extracts access_token and account_id.
  • Exposes a codexTranscriptionAuthStatus boolean on GET /api/transcription and surfaces it in the settings UI, which shows a server-side auth status row instead of API key/model inputs when Codex is selected.
  • Model listing returns an empty list for Codex; no API key or model is sent in transcription requests.
📊 Macroscope summarized 6ddfeec. 6 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

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 Plus

Run ID: 56ef973e-1d1e-4e0a-a446-377e9d7c6d5f

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

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 7, 2026
@fbal98fbal98 closed this Aug 7, 2026

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review: 3 findings in apps/server/src/transcription.ts (error modeling and catchTag usage).

Posted via Macroscope — Effect Service Conventions

Comment on lines +213 to +215
export const codexTranscriptionAuthStatus = resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError", () => Effect.succeed(false)),

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.

Effect.catchTag should be Effect.catchTags({ ... }), even when handling a single known tag.

Suggested change
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError",()=>Effect.succeed(false)),
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTags({TranscriptionCodexAuthError:()=>Effect.succeed(false)}),

Posted via Macroscope — Effect Service Conventions

Comment on lines +94 to +101
export class TranscriptionCodexAuthError extends Schema.TaggedErrorClass<TranscriptionCodexAuthError>()(
"TranscriptionCodexAuthError",
{},
) {
override get message(): string {
return "Voice transcription requires file-based Codex credentials signed in with ChatGPT.";
}
}

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.

TranscriptionCodexAuthError carries no structural attributes and no cause, yet resolveCodexVoiceCredentials (lines 183-205) collapses five distinct failures into it — settings read failure, no Codex instance configured, CodexSettings decode failure, auth.json read failure, and auth.json JSON/schema decode failure — discarding both the underlying error and the known authPath.

Consider giving the error a multi-value operation/stage field plus the resolved path, and an optional cause: Schema.Defect() (the "no Codex instance" case legitimately has no underlying failure), then mapping each step with the context known at that site. CodexShadowHomeFileSystemError in provider/Drivers/CodexHomeLayout.ts is the existing shape to follow (operation, path, cause). The message getter can stay derived from those attributes so the caller-visible HTTP message remains stable.

Posted via Macroscope — Effect Service Conventions

Comment on lines +247 to +249
if (response.status === 401 || response.status === 403) {
return yield* new TranscriptionCodexAuthError();
}

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.

This branch is only reachable for openai/groq (codex returns early on line 236), so a 401/403 from the OpenAI or Groq models endpoint is reported with the Codex-specific message "Voice transcription requires file-based Codex credentials…". The error tag no longer identifies the failure structurally, and the models route in http.ts does not handle TranscriptionCodexAuthError, so the failure escapes its catchTags. Suggest dropping the branch and letting TranscriptionProviderError carry the status.

 if (response.status < 200 || response.status >= 300) {
- if (response.status === 401 || response.status === 403) {- return yield* new TranscriptionCodexAuthError();- }

Posted via Macroscope — Effect Service Conventions

@@ -81,6 +86,7 @@ export function BetaSettingsPanel() {
);
const voiceTranscriptionModel = useClientSettings((settings) => settings.voiceTranscriptionModel);
const [environmentApiKeys, setEnvironmentApiKeys] = useState({

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.

🟡 Mediumsettings/BetaSettingsPanel.tsx:88

The Codex authentication row shows Unavailable whenever environmentApiKeys.codex is false, but readVoiceTranscriptionEnvironmentStatus failures are silently swallowed by .catch(() => undefined), leaving all environment keys at their initial false values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/settings/BetaSettingsPanel.tsx around line 88:
The Codex authentication row shows `Unavailable` whenever `environmentApiKeys.codex` is `false`, but `readVoiceTranscriptionEnvironmentStatus` failures are silently swallowed by `.catch(() => undefined)`, leaving all environment keys at their initial `false` values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

httpClient.execute,
Effect.mapError((cause) => new TranscriptionRequestError({ provider: "codex", cause })),
Effect.flatMap((response) =>
Effect.gen(function* () {

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.

🟠 Highsrc/transcription.ts:329

When the Codex transcription endpoint returns HTTP 401 or 403, forwardCodexVoiceTranscription throws TranscriptionProviderError with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The listVoiceTranscriptionModels function already maps 401/403 to TranscriptionCodexAuthError; forwardCodexVoiceTranscription should do the same instead of falling through to the generic TranscriptionProviderError.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/transcription.ts around line 329:
When the Codex transcription endpoint returns HTTP 401 or 403, `forwardCodexVoiceTranscription` throws `TranscriptionProviderError` with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The `listVoiceTranscriptionModels` function already maps 401/403 to `TranscriptionCodexAuthError`; `forwardCodexVoiceTranscription` should do the same instead of falling through to the generic `TranscriptionProviderError`.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@fbal98
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(voice): add Codex subscription transcription - #5647

Closed
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription
Closed

feat(voice): add Codex subscription transcription#5647
fbal98 wants to merge 1 commit into
pingdotgg:feat/voice-dictation-betafrom
fbal98:agent/codex-oauth-voice-transcription

Conversation

@fbal98

@fbal98fbal98 commented Aug 7, 2026

Copy link
Copy Markdown

Problem

The voice dictation beta in #5213 supports OpenAI and Groq API keys, but users already signed in to Codex with ChatGPT cannot reuse that subscription for transcription.

What changed

  • add Codex subscription to the existing transcription provider interface
  • resolve the first configured Codex home, including shadow-home layouts, and read its file-based ChatGPT credentials on the server
  • send recorded audio to the Codex Desktop transcription endpoint with credentials kept entirely host-side
  • show Codex authentication availability in Beta settings without exposing token material to the client
  • preserve the existing 25 MB limit, authenticated server route, recording flow, and OpenAI/Groq adapters
  • document that the Codex endpoint is private and experimental

This is intentionally stacked on #5213 so the change stays at its existing transcription seam instead of duplicating the composer, recording, settings, and HTTP work in that PR.

User impact

Users with file-based Codex ChatGPT credentials can select Codex subscription for dictation without configuring a separate speech-to-text API key. A missing or rejected login produces a bounded error and suggests signing in with codex login.

Checks

Focused server, web, and contract tests were added. They were not run on this limited VPS per its workspace instructions; CI should run validation.

UI

Adds a Codex subscription provider option and a host authentication status row in Settings → Beta features → Voice dictation. Screenshots are pending because browser/computer-use validation was not authorized for this run.

Built with GPT-5.6 Sol via Codex in T3 Code.

Note

Add Codex subscription as a voice transcription provider

  • Adds 'codex' as a valid VoiceTranscriptionProvider, routed through a new forwardCodexVoiceTranscription effect that posts audio to the Codex Desktop transcription endpoint using file-based ChatGPT OAuth credentials from the server.
  • Reads credentials via a new resolveCodexVoiceCredentials effect that locates the Codex provider config, reads auth.json, and extracts access_token and account_id.
  • Exposes a codexTranscriptionAuthStatus boolean on GET /api/transcription and surfaces it in the settings UI, which shows a server-side auth status row instead of API key/model inputs when Codex is selected.
  • Model listing returns an empty list for Codex; no API key or model is sent in transcription requests.
📊 Macroscope summarized 6ddfeec. 6 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

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 Plus

Run ID: 56ef973e-1d1e-4e0a-a446-377e9d7c6d5f

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

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 7, 2026
@fbal98fbal98 closed this Aug 7, 2026

@macroscopeappmacroscopeappBot left a comment

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.

Effect service conventions review: 3 findings in apps/server/src/transcription.ts (error modeling and catchTag usage).

Posted via Macroscope — Effect Service Conventions

Comment on lines +213 to +215
export const codexTranscriptionAuthStatus = resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError", () => Effect.succeed(false)),

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.

Effect.catchTag should be Effect.catchTags({ ... }), even when handling a single known tag.

Suggested change
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTag("TranscriptionCodexAuthError",()=>Effect.succeed(false)),
exportconstcodexTranscriptionAuthStatus=resolveCodexVoiceCredentials().pipe(
Effect.as(true),
Effect.catchTags({TranscriptionCodexAuthError:()=>Effect.succeed(false)}),

Posted via Macroscope — Effect Service Conventions

Comment on lines +94 to +101
export class TranscriptionCodexAuthError extends Schema.TaggedErrorClass<TranscriptionCodexAuthError>()(
"TranscriptionCodexAuthError",
{},
) {
override get message(): string {
return "Voice transcription requires file-based Codex credentials signed in with ChatGPT.";
}
}

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.

TranscriptionCodexAuthError carries no structural attributes and no cause, yet resolveCodexVoiceCredentials (lines 183-205) collapses five distinct failures into it — settings read failure, no Codex instance configured, CodexSettings decode failure, auth.json read failure, and auth.json JSON/schema decode failure — discarding both the underlying error and the known authPath.

Consider giving the error a multi-value operation/stage field plus the resolved path, and an optional cause: Schema.Defect() (the "no Codex instance" case legitimately has no underlying failure), then mapping each step with the context known at that site. CodexShadowHomeFileSystemError in provider/Drivers/CodexHomeLayout.ts is the existing shape to follow (operation, path, cause). The message getter can stay derived from those attributes so the caller-visible HTTP message remains stable.

Posted via Macroscope — Effect Service Conventions

Comment on lines +247 to +249
if (response.status === 401 || response.status === 403) {
return yield* new TranscriptionCodexAuthError();
}

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.

This branch is only reachable for openai/groq (codex returns early on line 236), so a 401/403 from the OpenAI or Groq models endpoint is reported with the Codex-specific message "Voice transcription requires file-based Codex credentials…". The error tag no longer identifies the failure structurally, and the models route in http.ts does not handle TranscriptionCodexAuthError, so the failure escapes its catchTags. Suggest dropping the branch and letting TranscriptionProviderError carry the status.

 if (response.status < 200 || response.status >= 300) {
- if (response.status === 401 || response.status === 403) {- return yield* new TranscriptionCodexAuthError();- }

Posted via Macroscope — Effect Service Conventions

@@ -81,6 +86,7 @@ export function BetaSettingsPanel() {
);
const voiceTranscriptionModel = useClientSettings((settings) => settings.voiceTranscriptionModel);
const [environmentApiKeys, setEnvironmentApiKeys] = useState({

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.

🟡 Mediumsettings/BetaSettingsPanel.tsx:88

The Codex authentication row shows Unavailable whenever environmentApiKeys.codex is false, but readVoiceTranscriptionEnvironmentStatus failures are silently swallowed by .catch(() => undefined), leaving all environment keys at their initial false values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/settings/BetaSettingsPanel.tsx around line 88:
The Codex authentication row shows `Unavailable` whenever `environmentApiKeys.codex` is `false`, but `readVoiceTranscriptionEnvironmentStatus` failures are silently swallowed by `.catch(() => undefined)`, leaving all environment keys at their initial `false` values. So a transient network error or server-side failure on the status request causes the row to incorrectly report that no Codex login was found, even when credentials are configured. Consider tracking a loading/error state for the environment status request so the row can distinguish "not configured" from "unknown."

httpClient.execute,
Effect.mapError((cause) => new TranscriptionRequestError({ provider: "codex", cause })),
Effect.flatMap((response) =>
Effect.gen(function* () {

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.

🟠 Highsrc/transcription.ts:329

When the Codex transcription endpoint returns HTTP 401 or 403, forwardCodexVoiceTranscription throws TranscriptionProviderError with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The listVoiceTranscriptionModels function already maps 401/403 to TranscriptionCodexAuthError; forwardCodexVoiceTranscription should do the same instead of falling through to the generic TranscriptionProviderError.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/transcription.ts around line 329:
When the Codex transcription endpoint returns HTTP 401 or 403, `forwardCodexVoiceTranscription` throws `TranscriptionProviderError` with the message "The transcription provider rejected the request. Check the provider and API key." This is wrong because Codex transcription doesn't use an API key — those statuses mean the Codex credentials are expired or rejected, so the user gets a misleading 502 telling them to check an API key instead of the actionable sign-in prompt. The `listVoiceTranscriptionModels` function already maps 401/403 to `TranscriptionCodexAuthError`; `forwardCodexVoiceTranscription` should do the same instead of falling through to the generic `TranscriptionProviderError`.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@fbal98