From e0c165e70796bd68a0193ae34348d8c68a067697 Mon Sep 17 00:00:00 2001 From: anurag Date: Sun, 17 May 2026 09:51:41 +0530 Subject: [PATCH] feat: integrate OpenAI Codex mode for reasoning tasks --- README.md | 12 ++ app/components/chat/BaseChat.tsx | 108 +++++++++++++++++- app/components/chat/CodexModeToggle.tsx | 32 ++++++ app/components/chat/CodexProgress.tsx | 92 ++++++++++++++++ app/components/settings/SettingsWindow.tsx | 91 +++++++++++++++ app/components/sidebar/Menu.client.tsx | 12 +- app/lib/stores/codex.ts | 17 +++ app/lib/stores/workbench.ts | 28 ++++- app/routes/api.codex.ts | 122 +++++++++++++++++++++ worker-configuration.d.ts | 1 + wrangler.toml | 3 + 11 files changed, 513 insertions(+), 5 deletions(-) create mode 100644 app/components/chat/CodexModeToggle.tsx create mode 100644 app/components/chat/CodexProgress.tsx create mode 100644 app/components/settings/SettingsWindow.tsx create mode 100644 app/lib/stores/codex.ts create mode 100644 app/routes/api.codex.ts diff --git a/README.md b/README.md index d3745298fff..4f696e14c67 100644 --- a/README.md +++ b/README.md @@ -52,3 +52,15 @@ Bolt.new supports most popular JavaScript frameworks and libraries. If it runs o **How can I add make sure my framework/project works well in bolt?** We are excited to work with the JavaScript ecosystem to improve functionality in Bolt. Reach out to us via [hello@stackblitz.com](mailto:hello@stackblitz.com) to discuss how we can partner! + +## Codex Mode + +Bolt includes an integration with [OpenAI Codex](https://openai.com/index/introducing-codex/) — a cloud-based software engineering agent powered by `codex-mini-latest`. + +### Enabling Codex Mode +1. Add your OpenAI API key in **Settings → Providers → OpenAI** +2. Toggle **Codex mode** in the chat toolbar (robot icon) +3. Describe a complex task and hit send + +Codex will reason through your codebase, plan changes, and apply them directly to your project — the same way the AI normally works, just powered by a more deliberate reasoning model. Best for: large refactors, adding authentication, database schema migrations, test suites. + diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index c4f90f43a1f..672e75d76d4 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -7,6 +7,11 @@ import { Workbench } from '~/components/workbench/Workbench.client'; import { classNames } from '~/utils/classNames'; import { Messages } from './Messages.client'; import { SendButton } from './SendButton.client'; +import { CodexModeToggle } from './CodexModeToggle'; +import { CodexProgress } from './CodexProgress'; +import { useStore } from '@nanostores/react'; +import { codexModeEnabled, codexStatus, codexCurrentTask, codexElapsedSeconds } from '~/lib/stores/codex'; +import { chatStore } from '~/lib/stores/chat'; import styles from './BaseChat.module.scss'; @@ -58,6 +63,93 @@ export const BaseChat = React.forwardRef( ref, ) => { const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200; + const isCodexMode = useStore(codexModeEnabled); + + React.useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key.toLowerCase() === 'c' && e.shiftKey && (e.ctrlKey || e.metaKey)) { + e.preventDefault(); + codexModeEnabled.set(!codexModeEnabled.get()); + } + }; + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, []); + + const handleCodexSubmit = async () => { + const currentInput = input; + if (!currentInput) return; + handleInputChange?.({ target: { value: '' } } as any); + + const { workbenchStore } = await import('~/lib/stores/workbench'); + const files = workbenchStore.files.get(); + const serializedFiles = Object.fromEntries( + Object.entries(files) + .filter(([, f]) => f?.type === 'file') + .map(([p, f]) => [p, f?.content ?? '']) + ); + + codexStatus.set('running'); + codexCurrentTask.set({ + id: `codex-${Date.now()}`, + task: currentInput, + status: 'running', + startedAt: Date.now() + }); + codexElapsedSeconds.set(0); + + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 120000); + + try { + const response = await fetch('/api/codex', { + method: 'POST', + body: JSON.stringify({ task: currentInput, files: serializedFiles }), + headers: { 'Content-Type': 'application/json' }, + signal: controller.signal + }); + + clearTimeout(timeout); + + if (!response.ok) { + const err = await response.json().catch(() => ({ error: 'Unknown error' })); + codexStatus.set('error'); + codexCurrentTask.set({ ...codexCurrentTask.get()!, status: 'error', error: err.error }); + return; + } + + const reader = response.body?.getReader(); + if (!reader) throw new Error('No stream available'); + + const decoder = new TextDecoder(); + let accumulated = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + accumulated += decoder.decode(value, { stream: true }); + } + + chatStore.setKey('messages', [ + ...chatStore.get().messages, + { id: Date.now().toString(), role: 'user', content: currentInput }, + { id: (Date.now() + 1).toString(), role: 'assistant', content: accumulated } + ]); + + workbenchStore.applyCodexArtifact(accumulated); + codexStatus.set('complete'); + codexCurrentTask.set({ ...codexCurrentTask.get()!, status: 'complete', completedAt: Date.now() }); + + } catch (error: any) { + clearTimeout(timeout); + codexStatus.set('error'); + codexCurrentTask.set({ + ...codexCurrentTask.get()!, + status: 'error', + error: error.name === 'AbortError' ? 'Codex timed out after 2 minutes. Try a smaller task.' : error.message + }); + } + }; return (
( 'shadow-sm border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden', )} > +