diff --git a/src/extension.ts b/src/extension.ts index 8fa37fb..0095ef1 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -14,6 +14,9 @@ let OPENCREDITS_API_URL = 'https://ccc.api.opencredits.ai'; let OPENCREDITS_WEB_URL = 'https://ccc.opencredits.ai'; let OPENCREDITS_PUBLISHABLE_KEY = 'oc_pk_c43da4f9a9484ae484ad29bc97cc354f'; +// Base URL substrings that identify a known first-party endpoint (OpenCredits/router) +const KNOWN_ENDPOINT_MARKERS = ['opencredits.ai', 'localhost:8787']; + const exec = util.promisify(cp.exec); // Storage for diff content (used by DiffContentProvider) @@ -289,8 +292,8 @@ class ClaudeChatProvider { return false; } const envVars = config.get>('environment.variables', {}); - const baseUrl = envVars['ANTHROPIC_BASE_URL'] || ''; - return baseUrl.includes('opencredits.ai') || baseUrl.includes('localhost:8787'); + const baseUrl = (envVars['ANTHROPIC_BASE_URL'] || '').toLowerCase(); + return KNOWN_ENDPOINT_MARKERS.some(marker => baseUrl.includes(marker)); } private async _setEnvsDisabled(disabled: boolean): Promise { @@ -1679,15 +1682,31 @@ class ClaudeChatProvider { return text.includes('Failed to authenticate. API Error: 401 Invalid authentication credentials'); } + // A custom (non-Anthropic, non-OpenCredits/router) endpoint may return its + // own error text that happens to contain one of the broad login patterns + // below (e.g. a relay pointing users to its own '/login' page). In that + // case only the strict, exact-match check should be trusted. + private _hasCustomAnthropicEndpoint(): boolean { + const config = vscode.workspace.getConfiguration('claudeCodeChat'); + const envsDisabled = config.get('environment.disabled', false); + const envVars = envsDisabled ? {} : config.get>('environment.variables', {}); + const baseUrl = (envVars['ANTHROPIC_BASE_URL'] || process.env.ANTHROPIC_BASE_URL || '').trim().toLowerCase(); + if (!baseUrl) { return false; } + if (baseUrl.includes('api.anthropic.com')) { return false; } + if (KNOWN_ENDPOINT_MARKERS.some(marker => baseUrl.includes(marker))) { return false; } + return true; + } + // Broader login-required signals — only trusted when they arrive on an // error result, since these phrases can appear in benign explanations. private _isLoginErrorResult(text: unknown): boolean { if (typeof text !== 'string' || !text) { return false; } if (this._isLoginError(text)) { return true; } + if (this._hasCustomAnthropicEndpoint()) { return false; } const patterns = [ 'Invalid API key', 'Not logged in', - '/login', + 'Please run /login', 'not authenticated' ]; return patterns.some(pattern => text.includes(pattern)); diff --git a/src/script.ts b/src/script.ts index 4c949e2..57c1be1 100644 --- a/src/script.ts +++ b/src/script.ts @@ -2119,6 +2119,7 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt let hasOpenCreditsKey = false; // Whether OpenCredits key exists in env vars let openCreditsBalance = null; // OpenCredits account balance let envsDisabled = false; // Whether custom env vars are disabled + const OPENCREDITS_BASE_URL_MARKERS = ['opencredits', 'localhost:8787']; // Base URL substrings identifying an OpenCredits-style endpoint let opencreditsEnabled = false; // Feature flag: whether OpenCredits is available in this region let hasSavedOpenCreditsKey = false; // Whether a key exists in encrypted storage @@ -4871,9 +4872,9 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt document.getElementById('wslOptions').style.display = wslEnabled ? 'block' : 'none'; // Update OpenCredits state from current env vars - const baseUrl = envVariables['ANTHROPIC_BASE_URL'] || ''; + const baseUrl = (envVariables['ANTHROPIC_BASE_URL'] || '').toLowerCase(); const wasOpenCredits = hasOpenCreditsKey; - hasOpenCreditsKey = !!(baseUrl && (baseUrl.includes('opencredits') || baseUrl.includes('localhost:8787'))); + hasOpenCreditsKey = !!(baseUrl && OPENCREDITS_BASE_URL_MARKERS.some(function(marker) { return baseUrl.includes(marker); })); if (!hasOpenCreditsKey) { openCreditsBalance = null; // If a OpenCredits model was selected, revert to default @@ -5199,7 +5200,8 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt envsDisabled = !!(message.data['environment.disabled']); const envVars = message.data['environment.variables'] || {}; const wasOpenCreditsSettings = hasOpenCreditsKey; - hasOpenCreditsKey = !!(message.data['isOpenCredits'] || (!envsDisabled && envVars['ANTHROPIC_BASE_URL'] && (envVars['ANTHROPIC_BASE_URL'].includes('opencredits') || envVars['ANTHROPIC_BASE_URL'].includes('localhost:8787')))); + const settingsBaseUrl = (envVars['ANTHROPIC_BASE_URL'] || '').toLowerCase(); + hasOpenCreditsKey = !!(message.data['isOpenCredits'] || (!envsDisabled && settingsBaseUrl && OPENCREDITS_BASE_URL_MARKERS.some(function(marker) { return settingsBaseUrl.includes(marker); }))); // Show/hide provider exclusion based on OpenCredits var providerSection = document.getElementById('providerExclusionSection');