Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -289,8 +292,8 @@ class ClaudeChatProvider {
return false;
}
const envVars = config.get<Record<string, string>>('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<void> {
Expand Down Expand Up @@ -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<boolean>('environment.disabled', false);
const envVars = envsDisabled ? {} : config.get<Record<string, string>>('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));
Expand Down
8 changes: 5 additions & 3 deletions src/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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');
Expand Down