From 1b4bb5be19c088fe820ed9367d6b533ed56e222d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 4 Apr 2026 15:45:57 +0900 Subject: [PATCH] fix(anthropic): remove setup-token onboarding path --- extensions/anthropic/openclaw.plugin.json | 15 +- extensions/anthropic/register.runtime.ts | 183 +----------------- extensions/anthropic/stream-wrappers.test.ts | 2 +- extensions/anthropic/stream-wrappers.ts | 2 +- src/cli/models-cli.ts | 4 +- src/commands/auth-choice-legacy.test.ts | 7 +- src/commands/auth-choice-legacy.ts | 4 +- src/commands/auth-choice-options.test.ts | 24 +-- src/commands/auth-choice.apply.ts | 18 +- src/commands/auth-choice.test.ts | 33 +++- src/commands/models/auth.test.ts | 38 ++++ src/commands/models/auth.ts | 36 ++-- ...oard-non-interactive.provider-auth.test.ts | 65 ++----- .../local/auth-choice.ts | 31 ++- src/plugins/provider-wizard.test.ts | 20 +- 15 files changed, 175 insertions(+), 307 deletions(-) diff --git a/extensions/anthropic/openclaw.plugin.json b/extensions/anthropic/openclaw.plugin.json index 85b91998692..ec0e3cacd23 100644 --- a/extensions/anthropic/openclaw.plugin.json +++ b/extensions/anthropic/openclaw.plugin.json @@ -20,18 +20,7 @@ "assistantPriority": -20, "groupId": "anthropic", "groupLabel": "Anthropic", - "groupHint": "Claude CLI + setup-token + API key" - }, - { - "provider": "anthropic", - "method": "setup-token", - "choiceId": "token", - "choiceLabel": "Anthropic token (paste setup-token)", - "choiceHint": "Run `claude setup-token` elsewhere, then paste the token here", - "assistantVisibility": "manual-only", - "groupId": "anthropic", - "groupLabel": "Anthropic", - "groupHint": "Claude CLI + setup-token + API key" + "groupHint": "Claude CLI + API key" }, { "provider": "anthropic", @@ -40,7 +29,7 @@ "choiceLabel": "Anthropic API key", "groupId": "anthropic", "groupLabel": "Anthropic", - "groupHint": "Claude CLI + setup-token + API key", + "groupHint": "Claude CLI + API key", "optionKey": "anthropicApiKey", "cliFlag": "--anthropic-api-key", "cliOption": "--anthropic-api-key ", diff --git a/extensions/anthropic/register.runtime.ts b/extensions/anthropic/register.runtime.ts index c9f0f380d0a..66e8a6c6422 100644 --- a/extensions/anthropic/register.runtime.ts +++ b/extensions/anthropic/register.runtime.ts @@ -1,27 +1,19 @@ -import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime"; -import { parseDurationMs } from "openclaw/plugin-sdk/cli-runtime"; import type { OpenClawPluginApi, ProviderAuthContext, ProviderResolveDynamicModelContext, ProviderRuntimeModel, } from "openclaw/plugin-sdk/plugin-entry"; +import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime"; import { CLAUDE_CLI_PROFILE_ID, applyAuthProfileConfig, - buildTokenProfileId, ensureApiKeyFromOptionEnvOrPrompt, listProfilesForProvider, normalizeApiKeyInput, - normalizeSecretInput, - normalizeSecretInputModeInput, - promptSecretRefForSetup, - resolveSecretInputModeForEnvSelection, suggestOAuthProfileIdForLegacyDefault, type AuthProfileStore, type ProviderAuthResult, - upsertAuthProfile, - validateAnthropicSetupToken, validateApiKeyInput, } from "openclaw/plugin-sdk/provider-auth"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; @@ -167,144 +159,6 @@ function buildAnthropicAuthDoctorHint(params: { ].join("\n"); } -async function runAnthropicSetupToken(ctx: ProviderAuthContext): Promise { - await ctx.prompter.note( - ["Run `claude setup-token` in your terminal.", "Then paste the generated token below."].join( - "\n", - ), - "Anthropic setup-token", - ); - - const requestedSecretInputMode = normalizeSecretInputModeInput(ctx.secretInputMode); - const selectedMode = ctx.allowSecretRefPrompt - ? await resolveSecretInputModeForEnvSelection({ - prompter: ctx.prompter, - explicitMode: requestedSecretInputMode, - copy: { - modeMessage: "How do you want to provide this setup token?", - plaintextLabel: "Paste setup token now", - plaintextHint: "Stores the token directly in the auth profile", - }, - }) - : "plaintext"; - - let token = ""; - let tokenRef: { source: "env" | "file" | "exec"; provider: string; id: string } | undefined; - if (selectedMode === "ref") { - const resolved = await promptSecretRefForSetup({ - provider: "anthropic-setup-token", - config: ctx.config, - prompter: ctx.prompter, - preferredEnvVar: "ANTHROPIC_SETUP_TOKEN", - copy: { - sourceMessage: "Where is this Anthropic setup token stored?", - envVarPlaceholder: "ANTHROPIC_SETUP_TOKEN", - }, - }); - token = resolved.resolvedValue.trim(); - tokenRef = resolved.ref; - } else { - const tokenRaw = await ctx.prompter.text({ - message: "Paste Anthropic setup-token", - validate: (value) => validateAnthropicSetupToken(String(value ?? "")), - }); - token = String(tokenRaw ?? "").trim(); - } - const tokenError = validateAnthropicSetupToken(token); - if (tokenError) { - throw new Error(tokenError); - } - - const profileNameRaw = await ctx.prompter.text({ - message: "Token name (blank = default)", - placeholder: "default", - }); - - return { - profiles: [ - { - profileId: buildTokenProfileId({ - provider: PROVIDER_ID, - name: String(profileNameRaw ?? ""), - }), - credential: { - type: "token", - provider: PROVIDER_ID, - token, - ...(tokenRef ? { tokenRef } : {}), - }, - }, - ], - }; -} - -async function runAnthropicSetupTokenNonInteractive(ctx: { - config: ProviderAuthContext["config"]; - opts: { - tokenProvider?: string; - token?: string; - tokenExpiresIn?: string; - tokenProfileId?: string; - }; - runtime: ProviderAuthContext["runtime"]; - agentDir?: string; -}): Promise { - const provider = ctx.opts.tokenProvider?.trim().toLowerCase(); - if (!provider) { - ctx.runtime.error("Missing --token-provider for --auth-choice token."); - ctx.runtime.exit(1); - return null; - } - if (provider !== PROVIDER_ID) { - ctx.runtime.error("Only --token-provider anthropic is supported for --auth-choice token."); - ctx.runtime.exit(1); - return null; - } - - const token = normalizeSecretInput(ctx.opts.token); - if (!token) { - ctx.runtime.error("Missing --token for --auth-choice token."); - ctx.runtime.exit(1); - return null; - } - const tokenError = validateAnthropicSetupToken(token); - if (tokenError) { - ctx.runtime.error(tokenError); - ctx.runtime.exit(1); - return null; - } - - let expires: number | undefined; - const expiresInRaw = ctx.opts.tokenExpiresIn?.trim(); - if (expiresInRaw) { - try { - expires = Date.now() + parseDurationMs(expiresInRaw, { defaultUnit: "d" }); - } catch (err) { - ctx.runtime.error(`Invalid --token-expires-in: ${String(err)}`); - ctx.runtime.exit(1); - return null; - } - } - - const profileId = - ctx.opts.tokenProfileId?.trim() || buildTokenProfileId({ provider: PROVIDER_ID, name: "" }); - upsertAuthProfile({ - profileId, - agentDir: ctx.agentDir, - credential: { - type: "token", - provider: PROVIDER_ID, - token, - ...(expires ? { expires } : {}), - }, - }); - return applyAuthProfileConfig(ctx.config, { - profileId, - provider: PROVIDER_ID, - mode: "token", - }); -} - async function runAnthropicCliMigration(ctx: ProviderAuthContext): Promise { if (!hasClaudeCliAuth()) { throw new Error( @@ -385,7 +239,7 @@ export async function registerAnthropicPlugin(api: OpenClawPluginApi): Promise model.replace(/^anthropic\//, "claude-cli/"), @@ -401,34 +255,6 @@ export async function registerAnthropicPlugin(api: OpenClawPluginApi): Promise await runAnthropicSetupToken(ctx), - runNonInteractive: async (ctx) => - await runAnthropicSetupTokenNonInteractive({ - config: ctx.config, - opts: ctx.opts, - runtime: ctx.runtime, - agentDir: ctx.agentDir, - }), - }, createProviderApiKeyAuthMethod({ providerId: PROVIDER_ID, methodId: "api-key", @@ -445,7 +271,7 @@ export async function registerAnthropicPlugin(api: OpenClawPluginApi): Promise await ctx.resolveOAuthToken(), - fetchUsageSnapshot: async (ctx) => await fetchClaudeUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn), + fetchUsageSnapshot: async (ctx) => + await fetchClaudeUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn), isCacheTtlEligible: () => true, buildAuthDoctorHint: (ctx) => buildAnthropicAuthDoctorHint({ diff --git a/extensions/anthropic/stream-wrappers.test.ts b/extensions/anthropic/stream-wrappers.test.ts index 43baa43b60b..4cde2e2bd23 100644 --- a/extensions/anthropic/stream-wrappers.test.ts +++ b/extensions/anthropic/stream-wrappers.test.ts @@ -25,7 +25,7 @@ describe("anthropic stream wrappers", () => { vi.restoreAllMocks(); }); - it("strips context-1m for subscription setup-token auth and warns", () => { + it("strips context-1m for Claude CLI or legacy token auth and warns", () => { const warn = vi.spyOn(__testing.log, "warn").mockImplementation(() => undefined); const headers = runWrapper("sk-ant-oat01-123"); expect(headers?.["anthropic-beta"]).toBeDefined(); diff --git a/extensions/anthropic/stream-wrappers.ts b/extensions/anthropic/stream-wrappers.ts index f60b9b77d9c..9e929986577 100644 --- a/extensions/anthropic/stream-wrappers.ts +++ b/extensions/anthropic/stream-wrappers.ts @@ -128,7 +128,7 @@ export function createAnthropicBetaHeadersWrapper( : betas; if (isOauth && requestedContext1m) { log.warn( - `ignoring context1m for Anthropic subscription (OAuth setup-token) auth on ${model.provider}/${model.id}; falling back to the standard context window because Anthropic rejects context-1m beta with OAuth auth`, + `ignoring context1m for Anthropic Claude CLI or legacy token auth on ${model.provider}/${model.id}; falling back to the standard context window because Anthropic rejects context-1m beta with non-API-key auth`, ); } diff --git a/src/cli/models-cli.ts b/src/cli/models-cli.ts index f3391c2796e..4337fa75d68 100644 --- a/src/cli/models-cli.ts +++ b/src/cli/models-cli.ts @@ -295,7 +295,7 @@ export function registerModelsCli(program: Command) { auth .command("add") - .description("Interactive auth helper (setup-token or paste token)") + .description("Interactive auth helper (provider auth or paste token)") .action(async () => { await runModelsCommand(async () => { await modelsAuthAddCommand({}, defaultRuntime); @@ -324,7 +324,7 @@ export function registerModelsCli(program: Command) { auth .command("setup-token") .description("Run a provider CLI to create/sync a token (TTY required)") - .option("--provider ", "Provider id (default: anthropic)") + .option("--provider ", "Provider id") .option("--yes", "Skip confirmation", false) .action(async (opts) => { await runModelsCommand(async () => { diff --git a/src/commands/auth-choice-legacy.test.ts b/src/commands/auth-choice-legacy.test.ts index 7914a316ca1..ea838bd939c 100644 --- a/src/commands/auth-choice-legacy.test.ts +++ b/src/commands/auth-choice-legacy.test.ts @@ -19,11 +19,6 @@ describe("auth choice legacy aliases", () => { }); it("sources deprecated cli aliases from plugin manifests", () => { - expect(resolveLegacyAuthChoiceAliasesForCli()).toEqual([ - "setup-token", - "oauth", - "claude-cli", - "codex-cli", - ]); + expect(resolveLegacyAuthChoiceAliasesForCli()).toEqual(["claude-cli", "codex-cli"]); }); }); diff --git a/src/commands/auth-choice-legacy.ts b/src/commands/auth-choice-legacy.ts index a4cd510fb1c..82bce76ffa7 100644 --- a/src/commands/auth-choice-legacy.ts +++ b/src/commands/auth-choice-legacy.ts @@ -1,9 +1,9 @@ import type { OpenClawConfig } from "../config/config.js"; +import type { AuthChoice } from "./onboard-types.js"; import { resolveManifestDeprecatedProviderAuthChoice, resolveManifestProviderAuthChoices, } from "../plugins/provider-auth-choices.js"; -import type { AuthChoice } from "./onboard-types.js"; function resolveLegacyCliBackendChoice( choice: string, @@ -32,7 +32,7 @@ export function resolveLegacyAuthChoiceAliasesForCli(params?: { .flatMap((choice) => choice.deprecatedChoiceIds ?? []) .filter((choice): choice is AuthChoice => choice.endsWith("-cli")) .toSorted((left, right) => left.localeCompare(right)); - return ["setup-token", "oauth", ...manifestCliAliases]; + return manifestCliAliases; } export function normalizeLegacyOnboardAuthChoice( diff --git a/src/commands/auth-choice-options.test.ts b/src/commands/auth-choice-options.test.ts index 453344cccf2..e8c595a443c 100644 --- a/src/commands/auth-choice-options.test.ts +++ b/src/commands/auth-choice-options.test.ts @@ -57,15 +57,6 @@ describe("buildAuthChoiceOptions", () => { groupId: "copilot", groupLabel: "Copilot", }, - { - pluginId: "anthropic", - providerId: "anthropic", - methodId: "setup-token", - choiceId: "token", - choiceLabel: "Anthropic token (paste setup-token)", - groupId: "anthropic", - groupLabel: "Anthropic", - }, { pluginId: "openai", providerId: "openai", @@ -202,7 +193,6 @@ describe("buildAuthChoiceOptions", () => { for (const value of [ "github-copilot", - "token", "zai-api-key", "xiaomi-api-key", "minimax-global-api", @@ -295,8 +285,6 @@ describe("buildAuthChoiceOptions", () => { includeSkip: true, }).split("|"); - expect(cliChoices).toContain("setup-token"); - expect(cliChoices).toContain("oauth"); expect(cliChoices).toContain("claude-cli"); expect(cliChoices).toContain("codex-cli"); }); @@ -378,7 +366,7 @@ describe("buildAuthChoiceOptions", () => { expect(litellmGroup?.options.some((opt) => opt.value === "litellm-api-key")).toBe(true); }); - it("prefers Anthropic Claude CLI over API key and hides manual-only setup-token in grouped selection", () => { + it("prefers Anthropic Claude CLI over API key in grouped selection", () => { resolveManifestProviderAuthChoices.mockReturnValue([ { pluginId: "anthropic", @@ -399,16 +387,6 @@ describe("buildAuthChoiceOptions", () => { groupId: "anthropic", groupLabel: "Anthropic", }, - { - pluginId: "anthropic", - providerId: "anthropic", - methodId: "setup-token", - choiceId: "token", - choiceLabel: "Anthropic token (paste setup-token)", - assistantVisibility: "manual-only", - groupId: "anthropic", - groupLabel: "Anthropic", - }, ]); const { groups } = buildAuthChoiceGroups({ store: EMPTY_STORE, diff --git a/src/commands/auth-choice.apply.ts b/src/commands/auth-choice.apply.ts index 92908a3546b..8a22b62832a 100644 --- a/src/commands/auth-choice.apply.ts +++ b/src/commands/auth-choice.apply.ts @@ -1,12 +1,12 @@ import type { OpenClawConfig } from "../config/config.js"; -import { applyAuthChoiceLoadedPluginProvider } from "../plugins/provider-auth-choice.js"; import type { RuntimeEnv } from "../runtime.js"; import type { WizardPrompter } from "../wizard/prompts.js"; +import type { AuthChoice, OnboardOptions } from "./onboard-types.js"; +import { applyAuthChoiceLoadedPluginProvider } from "../plugins/provider-auth-choice.js"; import { normalizeLegacyOnboardAuthChoice } from "./auth-choice-legacy.js"; import { applyAuthChoiceApiProviders } from "./auth-choice.apply.api-providers.js"; import { normalizeApiKeyTokenProviderAuthChoice } from "./auth-choice.apply.api-providers.js"; import { applyAuthChoiceOAuth } from "./auth-choice.apply.oauth.js"; -import type { AuthChoice, OnboardOptions } from "./onboard-types.js"; export type ApplyAuthChoiceParams = { authChoice: AuthChoice; @@ -56,5 +56,19 @@ export async function applyAuthChoice( } } + if ( + normalizedParams.authChoice === "token" || + normalizedParams.authChoice === "setup-token" || + normalizedParams.authChoice === "oauth" + ) { + throw new Error( + [ + `Auth choice "${normalizedParams.authChoice}" is no longer supported for Anthropic setup in OpenClaw.`, + "Existing Anthropic token profiles still run if they are already configured.", + 'Use "anthropic-cli" or "apiKey" instead.', + ].join("\n"), + ); + } + return { config: normalizedParams.config }; } diff --git a/src/commands/auth-choice.test.ts b/src/commands/auth-choice.test.ts index b4a0cb17e22..cffb9543b5e 100644 --- a/src/commands/auth-choice.test.ts +++ b/src/commands/auth-choice.test.ts @@ -1,19 +1,19 @@ -import fs from "node:fs/promises"; import type { OAuthCredentials } from "@mariozechner/pi-ai"; +import fs from "node:fs/promises"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { resolveAgentDir } from "../agents/agent-scope.js"; import type { OpenClawConfig } from "../config/config.js"; -import { resolveAgentModelPrimaryValue } from "../config/model-input.js"; import type { ModelProviderConfig } from "../config/types.models.js"; +import type { ProviderAuthMethod, ProviderPlugin } from "../plugins/types.js"; +import type { WizardPrompter } from "../wizard/prompts.js"; +import type { AuthChoice } from "./onboard-types.js"; +import { resolveAgentDir } from "../agents/agent-scope.js"; +import { resolveAgentModelPrimaryValue } from "../config/model-input.js"; import { GOOGLE_GEMINI_DEFAULT_MODEL } from "../plugin-sdk/google.js"; import { MINIMAX_CN_API_BASE_URL } from "../plugin-sdk/minimax.js"; import { ZAI_CODING_CN_BASE_URL, ZAI_CODING_GLOBAL_BASE_URL } from "../plugin-sdk/zai.js"; import { createProviderApiKeyAuthMethod } from "../plugins/provider-api-key-auth.js"; import { providerApiKeyAuthRuntime } from "../plugins/provider-api-key-auth.runtime.js"; -import type { ProviderAuthMethod, ProviderPlugin } from "../plugins/types.js"; -import type { WizardPrompter } from "../wizard/prompts.js"; import { applyAuthChoice, resolvePreferredProviderForAuthChoice } from "./auth-choice.js"; -import type { AuthChoice } from "./onboard-types.js"; import { authProfilePathForAgent, createAuthTestLifecycle, @@ -651,6 +651,27 @@ describe("applyAuthChoice", () => { resolvePluginProviders.mockReturnValue(createDefaultProviderPlugins()); + it("rejects legacy Anthropic token setup aliases", async () => { + await setupTempState(); + + await expect( + applyAuthChoice({ + authChoice: "token", + config: {} as OpenClawConfig, + prompter: createPrompter({}), + runtime: createExitThrowingRuntime(), + setDefaultModel: true, + opts: { tokenProvider: "anthropic" }, + }), + ).rejects.toThrow( + [ + 'Auth choice "token" is no longer supported for Anthropic setup in OpenClaw.', + "Existing Anthropic token profiles still run if they are already configured.", + 'Use "anthropic-cli" or "apiKey" instead.', + ].join("\n"), + ); + }); + it("does not throw when openai-codex oauth fails", async () => { await setupTempState(); diff --git a/src/commands/models/auth.test.ts b/src/commands/models/auth.test.ts index 0195415e175..b398bb7a276 100644 --- a/src/commands/models/auth.test.ts +++ b/src/commands/models/auth.test.ts @@ -381,6 +381,16 @@ describe("modelsAuthLoginCommand", () => { }); }); + it("rejects pasted Anthropic token setup", async () => { + const runtime = createRuntime(); + + await expect(modelsAuthPasteTokenCommand({ provider: "anthropic" }, runtime)).rejects.toThrow( + "Anthropic setup-token auth is no longer available for new setup in OpenClaw.", + ); + + expect(mocks.upsertAuthProfile).not.toHaveBeenCalled(); + }); + it("runs token auth for any token-capable provider plugin", async () => { const runtime = createRuntime(); const runTokenAuth = vi.fn().mockResolvedValue({ @@ -423,4 +433,32 @@ describe("modelsAuthLoginCommand", () => { agentDir: "/tmp/openclaw/agents/main", }); }); + + it("rejects setup-token for Anthropic even when explicitly requested", async () => { + const runtime = createRuntime(); + const runTokenAuth = vi.fn(); + mocks.resolvePluginProviders.mockReturnValue([ + { + id: "anthropic", + label: "Anthropic", + auth: [ + { + id: "setup-token", + label: "setup-token", + kind: "token", + run: runTokenAuth, + }, + ], + }, + ]); + + await expect( + modelsAuthSetupTokenCommand({ provider: "anthropic", yes: true }, runtime), + ).rejects.toThrow( + "Anthropic setup-token auth is no longer available for new setup in OpenClaw.", + ); + + expect(runTokenAuth).not.toHaveBeenCalled(); + expect(mocks.upsertAuthProfile).not.toHaveBeenCalled(); + }); }); diff --git a/src/commands/models/auth.ts b/src/commands/models/auth.ts index d4f0ee31405..32be3465f84 100644 --- a/src/commands/models/auth.ts +++ b/src/commands/models/auth.ts @@ -5,6 +5,14 @@ import { select as clackSelect, text as clackText, } from "@clack/prompts"; +import type { AuthProfileCredential } from "../../agents/auth-profiles/types.js"; +import type { OpenClawConfig } from "../../config/config.js"; +import type { + ProviderAuthMethod, + ProviderAuthResult, + ProviderPlugin, +} from "../../plugins/types.js"; +import type { RuntimeEnv } from "../../runtime.js"; import { resolveAgentDir, resolveAgentWorkspaceDir, @@ -16,21 +24,13 @@ import { loadAuthProfileStoreForRuntime, upsertAuthProfile, } from "../../agents/auth-profiles.js"; -import type { AuthProfileCredential } from "../../agents/auth-profiles/types.js"; import { normalizeProviderId } from "../../agents/model-selection.js"; import { resolveDefaultAgentWorkspaceDir } from "../../agents/workspace.js"; import { formatCliCommand } from "../../cli/command-format.js"; import { parseDurationMs } from "../../cli/parse-duration.js"; -import type { OpenClawConfig } from "../../config/config.js"; import { logConfigUpdated } from "../../config/logging.js"; import { applyAuthProfileConfig } from "../../plugins/provider-auth-helpers.js"; import { resolvePluginProviders } from "../../plugins/providers.runtime.js"; -import type { - ProviderAuthMethod, - ProviderAuthResult, - ProviderPlugin, -} from "../../plugins/types.js"; -import type { RuntimeEnv } from "../../runtime.js"; import { stylePromptHint, stylePromptMessage } from "../../terminal/prompt-style.js"; import { createClackPrompter } from "../../wizard/clack-prompter.js"; import { isRemoteEnvironment } from "../oauth-env.js"; @@ -81,6 +81,19 @@ function resolveDefaultTokenProfileId(provider: string): string { return `${normalizeProviderId(provider)}:manual`; } +function throwIfAnthropicTokenSetupDisabled(provider: string): void { + if (normalizeProviderId(provider) !== "anthropic") { + return; + } + throw new Error( + [ + "Anthropic setup-token auth is no longer available for new setup in OpenClaw.", + "Existing Anthropic token profiles still run if they are already configured.", + `Use ${formatCliCommand("openclaw models auth login --provider anthropic --method cli --set-default")} or an Anthropic API key instead.`, + ].join("\n"), + ); +} + type ResolvedModelsAuthContext = { config: OpenClawConfig; agentDir: string; @@ -317,13 +330,11 @@ export async function modelsAuthSetupTokenCommand( } const provider = - resolveRequestedProviderOrThrow(tokenProviders, opts.provider ?? "anthropic") ?? - tokenProviders.find((candidate) => normalizeProviderId(candidate.id) === "anthropic") ?? - tokenProviders[0] ?? - null; + resolveRequestedProviderOrThrow(tokenProviders, opts.provider) ?? tokenProviders[0] ?? null; if (!provider) { throw new Error("No token-capable provider is available."); } + throwIfAnthropicTokenSetupDisabled(provider.id); if (!opts.yes) { const proceed = await confirm({ @@ -366,6 +377,7 @@ export async function modelsAuthPasteTokenCommand( throw new Error("Missing --provider."); } const provider = normalizeProviderId(rawProvider); + throwIfAnthropicTokenSetupDisabled(provider); const profileId = opts.profileId?.trim() || resolveDefaultTokenProfileId(provider); const tokenInput = await text({ diff --git a/src/commands/onboard-non-interactive.provider-auth.test.ts b/src/commands/onboard-non-interactive.provider-auth.test.ts index b33eb732f7b..fbf137a1da9 100644 --- a/src/commands/onboard-non-interactive.provider-auth.test.ts +++ b/src/commands/onboard-non-interactive.provider-auth.test.ts @@ -330,33 +330,6 @@ vi.mock("./onboard-non-interactive/local/auth-choice.plugin-providers.js", async }, }; - const anthropicTokenChoice: ChoiceHandler = { - providerId: "anthropic", - label: "Anthropic", - runNonInteractive: async (ctx) => { - const token = normalizeText(ctx.opts.token); - if (!token) { - ctx.runtime.error("Missing --token for --auth-choice token."); - ctx.runtime.exit(1); - return null; - } - upsertAuthProfile({ - profileId: "anthropic:default", - credential: { - type: "token", - provider: "anthropic", - token, - }, - agentDir: ctx.agentDir, - }); - return providerApiKeyAuthRuntime.applyAuthProfileConfig(ctx.config as never, { - profileId: "anthropic:default", - provider: "anthropic", - mode: "token", - }); - }, - }; - const choiceMap = new Map([ [ "apiKey", @@ -581,7 +554,6 @@ vi.mock("./onboard-non-interactive/local/auth-choice.plugin-providers.js", async }), }), ], - ["token", anthropicTokenChoice], ]); return { @@ -1087,30 +1059,31 @@ describe("onboard (non-interactive): provider auth", () => { }); }); - it("stores token auth profile", async () => { + it("rejects legacy Anthropic token onboarding", async () => { await withOnboardEnv("openclaw-onboard-token-", async ({ configPath, runtime }) => { const cleanToken = `sk-ant-oat01-${"a".repeat(80)}`; const token = `${cleanToken.slice(0, 30)}\r${cleanToken.slice(30)}`; - await runNonInteractiveSetupWithDefaults(runtime, { - authChoice: "token", - tokenProvider: "anthropic", - token, - tokenProfileId: "anthropic:default", - }); + await expect( + runNonInteractiveSetupWithDefaults(runtime, { + authChoice: "token", + tokenProvider: "anthropic", + token, + tokenProfileId: "anthropic:default", + }), + ).rejects.toThrow("Process exited with code 1"); + + expect(runtime.error).toHaveBeenCalledWith( + [ + 'Auth choice "token" is no longer supported for Anthropic onboarding.', + "Existing Anthropic token profiles still run if they are already configured.", + 'Use "--auth-choice anthropic-cli" or "--auth-choice apiKey" instead.', + ].join("\n"), + ); const cfg = await readJsonFile(configPath); - - expect(cfg.auth?.profiles?.["anthropic:default"]?.provider).toBe("anthropic"); - expect(cfg.auth?.profiles?.["anthropic:default"]?.mode).toBe("token"); - - const store = ensureAuthProfileStore(); - const profile = store.profiles["anthropic:default"]; - expect(profile?.type).toBe("token"); - if (profile?.type === "token") { - expect(profile.provider).toBe("anthropic"); - expect(profile.token).toBe(cleanToken); - } + expect(cfg.auth?.profiles?.["anthropic:default"]).toBeUndefined(); + expect(ensureAuthProfileStore().profiles["anthropic:default"]).toBeUndefined(); }); }); diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index 80bc1d4f422..51f5e796307 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -1,8 +1,9 @@ import type { ApiKeyCredential } from "../../../agents/auth-profiles/types.js"; import type { OpenClawConfig } from "../../../config/config.js"; import type { SecretInput } from "../../../config/types.secrets.js"; -import { resolveManifestDeprecatedProviderAuthChoice } from "../../../plugins/provider-auth-choices.js"; import type { RuntimeEnv } from "../../../runtime.js"; +import type { AuthChoice, OnboardOptions } from "../../onboard-types.js"; +import { resolveManifestDeprecatedProviderAuthChoice } from "../../../plugins/provider-auth-choices.js"; import { resolveDefaultSecretProviderAlias } from "../../../secrets/ref-contract.js"; import { formatDeprecatedNonInteractiveAuthChoiceError, @@ -16,7 +17,6 @@ import { parseNonInteractiveCustomApiFlags, resolveCustomProviderId, } from "../../onboard-custom.js"; -import type { AuthChoice, OnboardOptions } from "../../onboard-types.js"; import { resolveNonInteractiveApiKey } from "../api-keys.js"; import { applyNonInteractivePluginProviderChoice } from "./auth-choice.plugin-providers.js"; @@ -130,8 +130,21 @@ export async function applyNonInteractiveAuthChoice(params: { if (authChoice === "setup-token") { runtime.error( [ - 'Auth choice "setup-token" requires interactive mode.', - 'Use "--auth-choice token" with --token and --token-provider anthropic.', + 'Auth choice "setup-token" is no longer supported for Anthropic onboarding.', + "Existing Anthropic token profiles still run if they are already configured.", + 'Use "--auth-choice anthropic-cli" or "--auth-choice apiKey" instead.', + ].join("\n"), + ); + runtime.exit(1); + return null; + } + + if (authChoice === "token") { + runtime.error( + [ + 'Auth choice "token" is no longer supported for Anthropic onboarding.', + "Existing Anthropic token profiles still run if they are already configured.", + 'Use "--auth-choice anthropic-cli" or "--auth-choice apiKey" instead.', ].join("\n"), ); runtime.exit(1); @@ -246,7 +259,15 @@ export async function applyNonInteractiveAuthChoice(params: { authChoice === "minimax-global-oauth" || authChoice === "minimax-cn-oauth" ) { - runtime.error("OAuth requires interactive mode."); + runtime.error( + authChoice === "oauth" + ? [ + 'Auth choice "oauth" is no longer supported for Anthropic onboarding.', + "Existing Anthropic token profiles still run if they are already configured.", + 'Use "--auth-choice anthropic-cli" or "--auth-choice apiKey" instead.', + ].join("\n") + : "OAuth requires interactive mode.", + ); runtime.exit(1); return null; } diff --git a/src/plugins/provider-wizard.test.ts b/src/plugins/provider-wizard.test.ts index 3b406965a8a..d61825e2e1b 100644 --- a/src/plugins/provider-wizard.test.ts +++ b/src/plugins/provider-wizard.test.ts @@ -1,4 +1,5 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { ProviderPlugin } from "./types.js"; import { buildProviderPluginMethodChoice, resolveProviderModelPickerEntries, @@ -6,7 +7,6 @@ import { resolveProviderWizardOptions, runProviderModelSelectedHook, } from "./provider-wizard.js"; -import type { ProviderPlugin } from "./types.js"; const resolvePluginProviders = vi.hoisted(() => vi.fn<() => ProviderPlugin[]>(() => [])); vi.mock("./providers.runtime.js", () => ({ @@ -291,24 +291,24 @@ describe("provider wizard boundaries", () => { label: "Anthropic", auth: [ { - id: "setup-token", - label: "setup-token", - kind: "token", + id: "cli", + label: "Claude CLI", + kind: "custom", wizard: { - choiceId: "token", + choiceId: "anthropic-cli", modelAllowlist: { - allowedKeys: ["anthropic/claude-sonnet-4-6"], - initialSelections: ["anthropic/claude-sonnet-4-6"], - message: "Anthropic OAuth models", + allowedKeys: ["claude-cli/claude-sonnet-4-6"], + initialSelections: ["claude-cli/claude-sonnet-4-6"], + message: "Claude CLI models", }, }, run: vi.fn(), }, ], }), - choice: "token", + choice: "anthropic-cli", expectedOption: { - value: "token", + value: "anthropic-cli", label: "Anthropic", groupId: "anthropic", groupLabel: "Anthropic",