fix(coding-agent): warn on Anthropic subscription auth

This commit is contained in:
Mario Zechner
2026-04-08 18:46:40 +02:00
parent d0a4baf8a1
commit 96916f2cad
4 changed files with 120 additions and 4 deletions

View File

@@ -129,6 +129,13 @@ type CompactionQueuedMessage = {
mode: "steer" | "followUp";
};
const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING =
"Anthropic subscription auth is active. Third-party usage now draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage.";
function isAnthropicSubscriptionAuthKey(apiKey: string | undefined): boolean {
return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
}
/**
* Options for InteractiveMode initialization.
*/
@@ -175,6 +182,7 @@ export class InteractiveMode {
private lastEscapeTime = 0;
private changelogMarkdown: string | undefined = undefined;
private startupNoticesShown = false;
private anthropicSubscriptionWarningShown = false;
// Status line tracking (for mutating immediately-sequential status updates)
private lastStatusSpacer: Spacer | undefined = undefined;
@@ -431,7 +439,7 @@ export class InteractiveMode {
private shouldShowEarendilAnnouncement(): boolean {
const now = new Date();
return now.getFullYear() === 2026 && now.getMonth() === 3 && now.getDate() === 8;
return now.getFullYear() === 2026 && now.getMonth() === 3 && (now.getDate() === 8 || now.getDate() === 9);
}
private showStartupNoticesIfNeeded(): void {
@@ -631,6 +639,8 @@ export class InteractiveMode {
this.showWarning(modelFallbackMessage);
}
void this.maybeWarnAboutAnthropicSubscriptionAuth();
// Process initial messages
if (initialMessage) {
try {
@@ -2980,6 +2990,7 @@ export class InteractiveMode {
const thinkingStr =
result.model.reasoning && result.thinkingLevel !== "off" ? ` (thinking: ${result.thinkingLevel})` : "";
this.showStatus(`Switched to ${result.model.name || result.model.id}${thinkingStr}`);
void this.maybeWarnAboutAnthropicSubscriptionAuth(result.model);
}
} catch (error) {
this.showError(error instanceof Error ? error.message : String(error));
@@ -3476,6 +3487,7 @@ export class InteractiveMode {
this.footer.invalidate();
this.updateEditorBorderColor();
this.showStatus(`Model: ${model.id}`);
void this.maybeWarnAboutAnthropicSubscriptionAuth(model);
this.checkDaxnutsEasterEgg(model);
} catch (error) {
this.showError(error instanceof Error ? error.message : String(error));
@@ -3511,6 +3523,35 @@ export class InteractiveMode {
this.footerDataProvider.setAvailableProviderCount(uniqueProviders.size);
}
private async maybeWarnAboutAnthropicSubscriptionAuth(
model: Model<any> | undefined = this.session.model,
): Promise<void> {
if (this.anthropicSubscriptionWarningShown) {
return;
}
if (!model || model.provider !== "anthropic") {
return;
}
const storedCredential = this.session.modelRegistry.authStorage.get("anthropic");
if (storedCredential?.type === "oauth") {
this.anthropicSubscriptionWarningShown = true;
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
return;
}
try {
const apiKey = await this.session.modelRegistry.getApiKeyForProvider(model.provider);
if (!isAnthropicSubscriptionAuthKey(apiKey)) {
return;
}
this.anthropicSubscriptionWarningShown = true;
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
} catch {
// Ignore auth lookup failures for warning-only checks.
}
}
private showModelSelector(initialSearchInput?: string): void {
this.showSelector((done) => {
const selector = new ModelSelectorComponent(
@@ -3526,6 +3567,7 @@ export class InteractiveMode {
this.updateEditorBorderColor();
done();
this.showStatus(`Model: ${model.id}`);
void this.maybeWarnAboutAnthropicSubscriptionAuth(model);
this.checkDaxnutsEasterEgg(model);
} catch (error) {
done();