@@ -26,16 +26,6 @@ import { nodeIdFor, edgeIdFor, ideaIdFor } from './graph.js';
2626import { deriveCapabilities } from './taxonomy.js' ;
2727import { combineCandidates } from './combinator.js' ;
2828import { buildCombinatorPrompt , parseCombinator } from './combinator-prompt.js' ;
29- import {
30- ANTHROPIC_OAUTH_ERROR_KEY ,
31- ANTHROPIC_OAUTH_STATE_KEY ,
32- ANTHROPIC_OAUTH_VERIFIER_KEY ,
33- clearAnthropicCredentials ,
34- exchangeAnthropicCode ,
35- getAnthropicCredentials ,
36- isAnthropicOAuthCallbackUrl ,
37- refreshAnthropicToken ,
38- } from './oauth-anthropic.js' ;
3929import { refreshXaiToken , XAI_CHAT_PROXY } from './oauth-xai.js' ;
4030import {
4131 OPENAI_OAUTH_ERROR_KEY ,
@@ -218,80 +208,10 @@ function resolveCompetitor(input) {
218208 return { platform : 'github' , repoId } ;
219209}
220210
221- // ─── Anthropic OAuth callback handling (robust intercept for claude.ai → console.anthropic.com) ───
222-
223- // One redirect can fire BOTH webNavigation.onCompleted and tabs.onUpdated, and the
224- // auth code is single-use — only the first handler may run the exchange.
211+ // One redirect can fire BOTH a navigation event and tabs.onUpdated, and the auth
212+ // code is single-use — this de-dups so only the first handler runs the exchange.
225213const _handledOAuthCodes = new Set ( ) ;
226214
227- async function handleAnthropicOAuthCallback ( rawUrl , tabId ) {
228- if ( ! rawUrl || ! isAnthropicOAuthCallbackUrl ( rawUrl ) ) return ;
229-
230- console . log ( '[RepoLens OAuth] Callback detected:' , rawUrl . split ( '?' ) [ 0 ] ) ; // strip the single-use ?code=…
231-
232- let url ;
233- try {
234- url = new URL ( rawUrl ) ;
235- } catch {
236- return ;
237- }
238-
239- const code = url . searchParams . get ( 'code' ) ;
240- const state = url . searchParams . get ( 'state' ) ;
241- const error = url . searchParams . get ( 'error' ) ;
242- const errorDesc = url . searchParams . get ( 'error_description' ) ;
243-
244- // Clean verifier/state (the "pending flow" markers). Leave ERROR_KEY for the waiting UI to consume.
245- const cleanupFlowMarkers = async ( ) => {
246- await chrome . storage . local . remove ( [
247- ANTHROPIC_OAUTH_VERIFIER_KEY ,
248- ANTHROPIC_OAUTH_STATE_KEY ,
249- ] ) . catch ( ( ) => { } ) ;
250- } ;
251-
252- if ( error ) {
253- const msg = errorDesc || error ;
254- console . warn ( '[RepoLens OAuth] Provider returned error:' , msg ) ;
255- await chrome . storage . local . set ( { [ ANTHROPIC_OAUTH_ERROR_KEY ] : `Claude OAuth error: ${ msg } ` } ) ;
256- await cleanupFlowMarkers ( ) ;
257- if ( tabId ) chrome . tabs . remove ( tabId ) . catch ( ( ) => { } ) ;
258- return ;
259- }
260-
261- if ( ! code ) {
262- console . warn ( '[RepoLens OAuth] No code in callback URL' ) ;
263- await cleanupFlowMarkers ( ) ;
264- if ( tabId ) chrome . tabs . remove ( tabId ) . catch ( ( ) => { } ) ;
265- return ;
266- }
267-
268- if ( _handledOAuthCodes . has ( code ) ) return ; // the other listener got here first
269- _handledOAuthCodes . add ( code ) ;
270-
271- const stored = await chrome . storage . local . get ( [ ANTHROPIC_OAUTH_VERIFIER_KEY , ANTHROPIC_OAUTH_STATE_KEY ] ) ;
272- const verifier = stored [ ANTHROPIC_OAUTH_VERIFIER_KEY ] ;
273- const storedState = stored [ ANTHROPIC_OAUTH_STATE_KEY ] ;
274-
275- if ( ! verifier ) {
276- console . warn ( '[RepoLens OAuth] No stored verifier — flow may have been interrupted or was for another extension' ) ;
277- await cleanupFlowMarkers ( ) ;
278- if ( tabId ) chrome . tabs . remove ( tabId ) . catch ( ( ) => { } ) ;
279- return ;
280- }
281-
282- try {
283- await exchangeAnthropicCode ( { code, state, verifier, storedState } ) ;
284- console . log ( '[RepoLens OAuth] Success — tokens stored' ) ;
285- await cleanupFlowMarkers ( ) ;
286- if ( tabId ) chrome . tabs . remove ( tabId ) . catch ( ( ) => { } ) ;
287- } catch ( err ) {
288- console . error ( '[RepoLens OAuth] Exchange error:' , err . message ) ;
289- await chrome . storage . local . set ( { [ ANTHROPIC_OAUTH_ERROR_KEY ] : err . message } ) ;
290- await cleanupFlowMarkers ( ) ;
291- if ( tabId ) chrome . tabs . remove ( tabId ) . catch ( ( ) => { } ) ;
292- }
293- }
294-
295215// ─── OpenAI OAuth callback handling ("Sign in with ChatGPT", Codex CLI flow) ───
296216//
297217// The redirect lands on http://localhost:1455/auth/callback — the loopback server
@@ -366,11 +286,6 @@ async function handleOpenAIOAuthCallback(rawUrl, tabId) {
366286 }
367287}
368288
369- chrome . webNavigation . onCompleted . addListener ( ( details ) => {
370- if ( details . frameId !== 0 ) return ;
371- handleAnthropicOAuthCallback ( details . url , details . tabId ) ;
372- } ) ;
373-
374289// The OpenAI loopback redirect can't load (no local server), so onCompleted never
375290// fires for it — onBeforeNavigate runs first and still carries the ?code=.
376291chrome . webNavigation . onBeforeNavigate . addListener ( ( details ) => {
@@ -380,7 +295,6 @@ chrome.webNavigation.onBeforeNavigate.addListener((details) => {
380295
381296chrome . tabs . onUpdated . addListener ( ( tabId , changeInfo ) => {
382297 if ( changeInfo . url ) {
383- handleAnthropicOAuthCallback ( changeInfo . url , tabId ) ;
384298 handleOpenAIOAuthCallback ( changeInfo . url , tabId ) ;
385299 }
386300} ) ;
@@ -978,44 +892,22 @@ async function callAIInner(keys, prompt, part) {
978892 throw new Error ( rankErrors ( failures ) . userMessage ) ;
979893}
980894
895+ // Anthropic Messages API with a standard Console API key (sk-ant-api…) via x-api-key.
896+ // Subscription/OAuth sign-in was removed: Anthropic binds Claude-subscription tokens to
897+ // the Claude Code client (server-side identity checks) and, as of 2026, prohibits using
898+ // subscription auth in third-party apps — so the only supported path is a Console key.
981899async function callAnthropic ( model = 'claude-sonnet-4-6' , prompt ) {
982- // Two credential styles share the `anthropicKey` slot:
983- // • a standard API key (sk-ant-api…) → sent via x-api-key ← supported, reliable
984- // • an OAuth access token (has a refresh token) → exchanged for an API key, or
985- // sent raw with the OAuth beta flags as a fallback
986- //
987- // Uses Hermes-hardened getAnthropicCredentials + refreshAnthropicToken (60s skew + inflight dedup)
988- const { anthropicKey, anthropicRefresh } = await chrome . storage . local . get ( [ 'anthropicKey' , 'anthropicRefresh' ] ) ;
989- const creds = await getAnthropicCredentials ( ) . catch ( ( ) => null ) ;
990-
991- const isOAuth = ! ! ( anthropicRefresh || creds ?. refresh_token ) ;
992- let token = isOAuth ? await refreshAnthropicToken ( ) : anthropicKey ;
993- if ( ! token ) throw new Error ( 'No Anthropic credential — add a key in Settings' ) ;
994-
995- // OAuth tokens need exchange via create_api_key to get a usable API key.
996- // Direct x-api-key with OAuth tokens may fail (401 "invalid x-api-key").
997- // The Claude Code SDK does this exchange internally.
998- let usingRawOAuthToken = false ;
999- if ( isOAuth ) {
1000- const exchanged = await exchangeAnthropicOAuthForApiKey ( token ) ;
1001- usingRawOAuthToken = exchanged === token ;
1002- token = exchanged ;
1003- }
1004-
1005- const headers = {
1006- 'anthropic-version' : '2023-06-01' ,
1007- 'anthropic-dangerous-direct-browser-access' : 'true' ,
1008- 'Content-Type' : 'application/json' ,
1009- 'x-api-key' : token ,
1010- } ;
1011- // A raw OAuth access token (exchange unavailable) only works with the beta flags.
1012- if ( usingRawOAuthToken ) {
1013- headers [ 'anthropic-beta' ] = 'oauth-2025-04-20,claude-code-20250219' ;
1014- }
900+ const { anthropicKey } = await chrome . storage . local . get ( 'anthropicKey' ) ;
901+ if ( ! anthropicKey ) throw new Error ( 'No Anthropic API key — add one in Settings' ) ;
1015902
1016903 const res = await fetch ( 'https://api.anthropic.com/v1/messages' , {
1017904 method : 'POST' ,
1018- headers,
905+ headers : {
906+ 'anthropic-version' : '2023-06-01' ,
907+ 'anthropic-dangerous-direct-browser-access' : 'true' ,
908+ 'Content-Type' : 'application/json' ,
909+ 'x-api-key' : anthropicKey ,
910+ } ,
1019911 body : JSON . stringify ( {
1020912 model,
1021913 max_tokens : 4096 ,
@@ -1024,20 +916,6 @@ async function callAnthropic(model = 'claude-sonnet-4-6', prompt) {
1024916 } ) ;
1025917 if ( ! res . ok ) {
1026918 const err = await res . json ( ) . catch ( ( ) => ( { } ) ) ;
1027- // Only clear stored OAuth tokens on a real auth failure — never on transient errors.
1028- if ( res . status === 401 && isOAuth ) {
1029- // The cached exchanged key may be the stale credential being rejected — drop it
1030- // so a reconnect takes effect immediately instead of after the SW restarts.
1031- _anthropicExchangedKey = null ;
1032- _anthropicExchangedKeyExpiry = 0 ;
1033- // Hermes-hardened clear (structured + legacy flat keys)
1034- if ( typeof clearAnthropicCredentials === 'function' ) {
1035- await clearAnthropicCredentials ( ) ;
1036- } else {
1037- await chrome . storage . local . remove ( [ 'anthropicKey' , 'anthropicRefresh' , 'anthropicExpiry' , 'anthropicCredentials' ] ) ;
1038- }
1039- throw new Error ( 'Anthropic session expired — please reconnect in Settings' ) ;
1040- }
1041919 throw new Error ( err . error ?. message ?? `Anthropic API error ${ res . status } ` ) ;
1042920 }
1043921 const data = await res . json ( ) ;
@@ -1046,44 +924,6 @@ async function callAnthropic(model = 'claude-sonnet-4-6', prompt) {
1046924 return text ;
1047925}
1048926
1049- // Cache the exchanged API key (short-lived, ~8h) to avoid hammering the endpoint.
1050- let _anthropicExchangedKey = null ;
1051- let _anthropicExchangedKeyExpiry = 0 ;
1052-
1053- async function exchangeAnthropicOAuthForApiKey ( oauthToken ) {
1054- // Return cached key if still valid (with 5min skew)
1055- if ( _anthropicExchangedKey && Date . now ( ) < _anthropicExchangedKeyExpiry - 300_000 ) {
1056- return _anthropicExchangedKey ;
1057- }
1058-
1059- try {
1060- const res = await fetch ( 'https://api.anthropic.com/api/oauth/claude_cli/create_api_key' , {
1061- method : 'POST' ,
1062- headers : {
1063- 'Content-Type' : 'application/json' ,
1064- 'Authorization' : `Bearer ${ oauthToken } ` ,
1065- } ,
1066- body : JSON . stringify ( { } ) ,
1067- } ) ;
1068-
1069- if ( res . ok ) {
1070- const data = await res . json ( ) ;
1071- if ( data . api_key ) {
1072- _anthropicExchangedKey = data . api_key ;
1073- _anthropicExchangedKeyExpiry = Date . now ( ) + ( data . expires_in || 28800 ) * 1000 ;
1074- return data . api_key ;
1075- }
1076- }
1077- // If exchange fails (endpoint might not exist or token type not supported),
1078- // fall back to using the raw OAuth token (callAnthropic adds the beta flags).
1079- console . warn ( '[RepoLens] create_api_key exchange failed, falling back to raw OAuth token' ) ;
1080- } catch ( err ) {
1081- console . warn ( '[RepoLens] create_api_key exchange error:' , err . message ) ;
1082- }
1083-
1084- return oauthToken ;
1085- }
1086-
1087927async function callGemini ( key , model = 'gemini-2.5-flash' , prompt ) {
1088928 const url = 'https://generativelanguage.googleapis.com/v1beta/models/' + encodeURIComponent ( model ) + ':generateContent?key=' + encodeURIComponent ( key ) ;
1089929 const res = await fetch ( url , {
0 commit comments