mirror of https://github.com/openclaw/openclaw.git
fix(anthropic): remove setup-token onboarding path
This commit is contained in:
parent
15bee338e9
commit
1b4bb5be19
|
|
@ -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 <key>",
|
||||
|
|
|
|||
|
|
@ -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<ProviderAuthResult> {
|
||||
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<ProviderAuthContext["config"] | null> {
|
||||
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<ProviderAuthResult> {
|
||||
if (!hasClaudeCliAuth()) {
|
||||
throw new Error(
|
||||
|
|
@ -385,7 +239,7 @@ export async function registerAnthropicPlugin(api: OpenClawPluginApi): Promise<v
|
|||
assistantPriority: -20,
|
||||
groupId: "anthropic",
|
||||
groupLabel: "Anthropic",
|
||||
groupHint: "Claude CLI + setup-token + API key",
|
||||
groupHint: "Claude CLI + API key",
|
||||
modelAllowlist: {
|
||||
allowedKeys: [...ANTHROPIC_OAUTH_ALLOWLIST].map((model) =>
|
||||
model.replace(/^anthropic\//, "claude-cli/"),
|
||||
|
|
@ -401,34 +255,6 @@ export async function registerAnthropicPlugin(api: OpenClawPluginApi): Promise<v
|
|||
runtime: ctx.runtime,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: "setup-token",
|
||||
label: "setup-token (claude)",
|
||||
hint: "Paste a setup-token from `claude setup-token`",
|
||||
kind: "token",
|
||||
wizard: {
|
||||
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",
|
||||
modelAllowlist: {
|
||||
allowedKeys: [...ANTHROPIC_OAUTH_ALLOWLIST],
|
||||
initialSelections: ["anthropic/claude-sonnet-4-6"],
|
||||
message: "Anthropic OAuth models",
|
||||
},
|
||||
},
|
||||
run: async (ctx: ProviderAuthContext) => 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<v
|
|||
choiceLabel: "Anthropic API key",
|
||||
groupId: "anthropic",
|
||||
groupLabel: "Anthropic",
|
||||
groupHint: "Claude CLI + setup-token + API key",
|
||||
groupHint: "Claude CLI + API key",
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
|
@ -481,7 +307,8 @@ export async function registerAnthropicPlugin(api: OpenClawPluginApi): Promise<v
|
|||
? "adaptive"
|
||||
: undefined,
|
||||
resolveUsageAuth: async (ctx) => 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({
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <name>", "Provider id (default: anthropic)")
|
||||
.option("--provider <name>", "Provider id")
|
||||
.option("--yes", "Skip confirmation", false)
|
||||
.action(async (opts) => {
|
||||
await runModelsCommand(async () => {
|
||||
|
|
|
|||
|
|
@ -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"]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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<string, ChoiceHandler>([
|
||||
[
|
||||
"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<ProviderAuthConfigSnapshot>(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();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue