feat: extensible OAuth 2.0 PKCE provider connect buttons (OpenRouter + Codex)#26
Open
ProfSynapse wants to merge 5 commits intomainfrom
Open
feat: extensible OAuth 2.0 PKCE provider connect buttons (OpenRouter + Codex)#26ProfSynapse wants to merge 5 commits intomainfrom
ProfSynapse wants to merge 5 commits intomainfrom
Conversation
Adds OAuth connect buttons for OpenRouter (official) and OpenAI Codex (experimental) providers. Architecture supports any future LLM provider via the IOAuthProvider interface. Core OAuth infrastructure: - IOAuthProvider interface + OAuthService singleton registry - PKCEUtils: PKCE helpers using crypto.subtle (S256 challenge) - OAuthCallbackServer: ephemeral 127.0.0.1 server, state validation, 5-min timeout, EADDRINUSE handling Providers: - OpenRouterOAuthProvider: port 3000, JSON token exchange, permanent sk-or-... key - OpenAICodexOAuthProvider: port 1455, form-urlencoded exchange, JWT accountId extraction, proactive token refresh (5-min window before ~10-day TTL) LLM adapter: - OpenAICodexAdapter: SSE stream parsing, Bearer + ChatGPT-Account-Id headers, input as array, stream:true + store:false required parameters - OpenAICodexModels: 6 model variants (gpt-5.3-codex through gpt-5.1-codex-mini) UI: - OAuthConsentModal: experimental provider consent dialog - OAuthPreAuthModal: key_label + optional credit_limit pre-auth form (OpenRouter) - GenericProviderModal: OAuth connect button, connected banner, disconnect, auto-clear - ProvidersTab: attachOAuthConfigs(), Platform.isDesktop guard - styles.css: OAuth button and status badge styles Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comprehensive test suite for all new OAuth modules: - PKCEUtils: verifier/challenge/state generation, base64url encoding (18 tests) - OAuthCallbackServer: state validation, error paths, timeout (port 0), EADDRINUSE (24 tests) - OAuthService: state machine, concurrent flow prevention, provider registry (22 tests) - OpenRouterOAuthProvider: config, auth URL, token exchange, preAuthParams (28 tests) - OpenAICodexOAuthProvider: config, auth URL, token exchange, JWT parsing, refresh (36 tests) - OpenAICodexAdapter: SSE parsing, proactive refresh, header construction (32 tests) - OAuthModals: consent dialog, pre-auth form validation (14 tests) All 525 tests pass (351 baseline + 174 new). fetch mocked throughout. OAuthService.resetInstance() used for test isolation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Security: - Add Cache-Control: no-store to all OAuthCallbackServer responses - Use crypto.timingSafeEqual() for CSRF state comparison - Remove id_token (PII) from stored metadata after accountId extraction - prefers-color-scheme CSS in callback HTML (light/dark, system-ui font) Quality: - Add saveSettings() callback after token refresh via _onSettingsDirty - Remove unnecessary `as any` cast in ProviderManager - Fix provider ID mismatch: ProvidersTab now attaches Codex config to 'openai-codex' entry (not 'openai'); full data flow verified - Call OAuthService.resetInstance() in main.ts onunload() to prevent callback server leak on plugin disable/reload Tests: - OAuthCallbackServer: use randomized ephemeral ports (port 0 requires source refactor; documented) - OAuthService: restore global.window in afterAll to prevent mock leak - OpenAICodexAdapter: add partial SSE chunk boundary test + concurrent refresh deduplication test (confirms refreshInProgress lock works) - OpenAICodexOAuthProvider: update idToken assertion to expect undefined (PII prevention intent now documented) - jest.config.js: add per-file coverage thresholds for 6 OAuth source files (PKCEUtils 100%, others 80/75/80/80) 527/527 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Codex endpoint uses the Responses API SSE format, which supports
tool/function calling. This commit enables it end-to-end:
- getCapabilities(): supportsFunctions: true + 'tool_calling' feature
- generateStreamAsync(): forwards options.tools in Responses API flat
format ({type, name, description, parameters})
- parseCodexSSEStream(): accumulates function_call output items from
response.output_item.done events; all completion paths yield toolCalls
- generateUncached(): extracts tool calls from stream, sets
finishReason: 'tool_calls' when present
Tests: 47/47 pass (8 new tool call tests added)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove explicit `Record<string, unknown>` annotation on the `.map()` callback parameter — TypeScript infers the correct `Tool` type from `options.tools`, making the annotation incorrect and causing a build error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds OAuth 2.0 PKCE connect buttons to the provider settings UI, allowing users to authenticate with LLM providers without manually copying API keys. Architecture is provider-agnostic — any future provider can be added by implementing the
IOAuthProviderinterface.Providers implemented:
sk-or-...key, port 3000Key technical decisions:
stream: true,store: false,inputas array (all mandatory per spike testing)OAuthCallbackServerbinds to127.0.0.1only, single-use, 5-min timeout, state validationPKCEUtilsusescrypto.subtle(Web Crypto API) — neverMath.randomPlatform.isDesktopguardSpike validated: Full Codex OAuth flow tested end-to-end before implementation.
New files
src/services/oauth/—IOAuthProvider,PKCEUtils,OAuthCallbackServer,OAuthService, providerssrc/services/llm/adapters/openai-codex/—OpenAICodexAdapter,OpenAICodexModelssrc/components/llm-provider/providers/OAuthModals.ts— consent + pre-auth dialogstests/unit/— 174 unit tests (525/525 passing)Modified files
GenericProviderModal,LLMProviderModal,ProvidersTab,main.ts,ProviderTypes,AdapterRegistry,ModelRegistry,ProviderManager,styles.cssTest plan
🤖 Generated with Claude Code