Skip to content

Commit 6ce6c0b

Browse files
committed
Reviewer fixes 2
1 parent d95b16d commit 6ce6c0b

File tree

6 files changed

+377
-331
lines changed

6 files changed

+377
-331
lines changed

cli/src/components/codex-connect-banner.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const CodexConnectBanner = () => {
2424
const theme = useTheme()
2525
const [flowState, setFlowState] = useState<FlowState>('checking')
2626
const [error, setError] = useState<string | null>(null)
27+
const [manualUrl, setManualUrl] = useState<string | null>(null)
2728
const [isDisconnectHovered, setIsDisconnectHovered] = useState(false)
2829
const [isConnectHovered, setIsConnectHovered] = useState(false)
2930

@@ -41,6 +42,8 @@ export const CodexConnectBanner = () => {
4142
} else if (callbackStatus === 'error') {
4243
setError(message ?? 'Authorization failed')
4344
setFlowState('error')
45+
} else if (callbackStatus === 'waiting' && message) {
46+
setManualUrl(message)
4447
}
4548
}).catch((err) => {
4649
setError(err instanceof Error ? err.message : 'Failed to start OAuth flow')
@@ -57,12 +60,15 @@ export const CodexConnectBanner = () => {
5760
const handleConnect = async () => {
5861
try {
5962
setFlowState('waiting-for-code')
63+
setManualUrl(null)
6064
await startOAuthFlowWithCallback((callbackStatus, message) => {
6165
if (callbackStatus === 'success') {
6266
setFlowState('connected')
6367
} else if (callbackStatus === 'error') {
6468
setError(message ?? 'Authorization failed')
6569
setFlowState('error')
70+
} else if (callbackStatus === 'waiting' && message) {
71+
setManualUrl(message)
6672
}
6773
})
6874
} catch (err) {
@@ -128,10 +134,17 @@ export const CodexConnectBanner = () => {
128134
<BottomBanner borderColorKey="info" onClose={handleClose}>
129135
<box style={{ flexDirection: 'column', gap: 0, flexGrow: 1 }}>
130136
<text style={{ fg: theme.info }}>Waiting for authorization</text>
131-
<text style={{ fg: theme.muted, marginTop: 1 }}>
132-
Sign in with your OpenAI account in the browser. The authorization
133-
will complete automatically.
134-
</text>
137+
{manualUrl ? (
138+
<text style={{ fg: theme.muted, marginTop: 1 }}>
139+
Could not open browser. Open this URL manually:{' '}
140+
{manualUrl}
141+
</text>
142+
) : (
143+
<text style={{ fg: theme.muted, marginTop: 1 }}>
144+
Sign in with your OpenAI account in the browser. The authorization
145+
will complete automatically.
146+
</text>
147+
)}
135148
</box>
136149
</BottomBanner>
137150
)

cli/src/utils/codex-oauth.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ function getErrorPage(errorMessage: string): string {
188188
<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
189189
</div>
190190
<h1>Authorization Failed</h1>
191-
<p class="error-message">${errorMessage}</p>
191+
<p class="error-message">${escapeHtml(errorMessage)}</p>
192192
<div class="hint">
193193
<p>You can close this window and try again from the terminal.</p>
194194
</div>
@@ -197,6 +197,15 @@ function getErrorPage(errorMessage: string): string {
197197
</html>`
198198
}
199199

200+
function escapeHtml(str: string): string {
201+
return str
202+
.replace(/&/g, '&amp;')
203+
.replace(/</g, '&lt;')
204+
.replace(/>/g, '&gt;')
205+
.replace(/"/g, '&quot;')
206+
.replace(/'/g, '&#39;')
207+
}
208+
200209
// PKCE code verifier and challenge generation
201210
function generateCodeVerifier(): string {
202211
// Generate 32 random bytes and encode as base64url
@@ -361,7 +370,8 @@ export function startOAuthFlowWithCallback(
361370
try {
362371
await open(authUrl)
363372
} catch {
364-
// Browser open failed, but server is running - user can manually open the URL
373+
// Browser open failed - surface the URL so the user can open it manually
374+
onStatusChange?.('waiting', authUrl)
365375
}
366376
})
367377
})
@@ -391,8 +401,17 @@ export async function exchangeCodeForTokens(
391401
)
392402
}
393403

394-
// The authorization code might come with a state parameter
395-
const code = authorizationCode.trim().split('#')[0]
404+
// The authorization code might be a full callback URL or just the code
405+
let code: string
406+
const trimmed = authorizationCode.trim()
407+
try {
408+
const parsed = new URL(trimmed)
409+
const extractedCode = parsed.searchParams.get('code')
410+
code = extractedCode ?? trimmed.split('#')[0]
411+
} catch {
412+
// Not a URL - treat as a raw authorization code
413+
code = trimmed.split('#')[0]
414+
}
396415

397416
// Exchange the code for tokens
398417
// IMPORTANT: Use application/x-www-form-urlencoded, NOT application/json

cli/src/utils/input-modes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export const INPUT_MODE_CONFIGS: Record<InputMode, InputModeConfig> = {
110110
'connect:codex': {
111111
icon: '🔗',
112112
color: 'info',
113-
placeholder: 'paste authorization code here...',
113+
placeholder: 'waiting for browser authorization...',
114114
widthAdjustment: 3, // emoji width + padding
115115
showAgentModeToggle: false,
116116
disableSlashSuggestions: true,

sdk/src/__tests__/codex-message-transform.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
type ChatMessage,
88
type ToolCallState,
99
type ImageUrlContentPart,
10-
} from '../impl/model-provider'
10+
} from '../impl/codex-transform'
1111

1212
/**
1313
* Unit tests for Codex OAuth message transformation functions.

0 commit comments

Comments
 (0)