From f9f44b9b96df2742fa88d7617a0c2de857035192 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 5 Apr 2026 04:38:58 +0900 Subject: [PATCH] fix(models): restore anthropic cli auth login --- src/commands/models/auth.test.ts | 67 ++++++++++++++++++++++++++++++++ src/commands/models/auth.ts | 24 ++++++++++-- 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/commands/models/auth.test.ts b/src/commands/models/auth.test.ts index c1360df10b8..55b46566470 100644 --- a/src/commands/models/auth.test.ts +++ b/src/commands/models/auth.test.ts @@ -14,6 +14,7 @@ const mocks = vi.hoisted(() => ({ resolveAgentWorkspaceDir: vi.fn(), resolveDefaultAgentWorkspaceDir: vi.fn(), upsertAuthProfile: vi.fn(), + resolveOwningPluginIdsForProvider: vi.fn(), resolvePluginProviders: vi.fn(), createClackPrompter: vi.fn(), loadValidConfigOrThrow: vi.fn(), @@ -54,6 +55,10 @@ vi.mock("../../plugins/providers.runtime.js", () => ({ resolvePluginProviders: mocks.resolvePluginProviders, })); +vi.mock("../../plugins/providers.js", () => ({ + resolveOwningPluginIdsForProvider: mocks.resolveOwningPluginIdsForProvider, +})); + vi.mock("../../wizard/clack-prompter.js", () => ({ createClackPrompter: mocks.createClackPrompter, })); @@ -148,6 +153,7 @@ describe("modelsAuthLoginCommand", () => { mocks.resolveAgentWorkspaceDir.mockReturnValue("/tmp/openclaw/workspace"); mocks.resolveDefaultAgentWorkspaceDir.mockReturnValue("/tmp/openclaw/workspace"); mocks.loadValidConfigOrThrow.mockImplementation(async () => currentConfig); + mocks.resolveOwningPluginIdsForProvider.mockReturnValue(undefined); mocks.updateConfig.mockImplementation( async (mutator: (cfg: OpenClawConfig) => OpenClawConfig) => { lastUpdatedConfig = mutator(currentConfig); @@ -278,6 +284,67 @@ describe("modelsAuthLoginCommand", () => { expect(runtime.log).toHaveBeenCalledWith("Default model set to claude-cli/claude-sonnet-4-6"); }); + it("loads the owning plugin for an explicit provider even in a clean config", async () => { + const runtime = createRuntime(); + const runClaudeCliMigration = vi.fn().mockResolvedValue({ + profiles: [], + defaultModel: "claude-cli/claude-sonnet-4-6", + configPatch: { + agents: { + defaults: { + models: { + "claude-cli/claude-sonnet-4-6": {}, + }, + }, + }, + }, + }); + mocks.resolveOwningPluginIdsForProvider.mockReturnValue(["anthropic"]); + mocks.resolvePluginProviders.mockImplementation( + (params: { activate?: boolean; onlyPluginIds?: string[] } | undefined) => + params?.activate === true && params?.onlyPluginIds?.[0] === "anthropic" + ? [ + { + id: "anthropic", + label: "Anthropic", + auth: [ + { + id: "cli", + label: "Claude CLI", + kind: "custom", + run: runClaudeCliMigration, + }, + ], + }, + ] + : [], + ); + + await modelsAuthLoginCommand( + { provider: "anthropic", method: "cli", setDefault: true }, + runtime, + ); + + expect(mocks.resolveOwningPluginIdsForProvider).toHaveBeenCalledWith({ + provider: "anthropic", + config: {}, + workspaceDir: "/tmp/openclaw/workspace", + env: process.env, + }); + expect(mocks.resolvePluginProviders).toHaveBeenCalledWith( + expect.objectContaining({ + config: {}, + workspaceDir: "/tmp/openclaw/workspace", + bundledProviderAllowlistCompat: true, + bundledProviderVitestCompat: true, + onlyPluginIds: ["anthropic"], + activate: true, + }), + ); + expect(runClaudeCliMigration).toHaveBeenCalledOnce(); + expect(runtime.log).toHaveBeenCalledWith("Default model set to claude-cli/claude-sonnet-4-6"); + }); + it("clears stale auth lockouts before attempting openai-codex login", async () => { const runtime = createRuntime(); const fakeStore = { diff --git a/src/commands/models/auth.ts b/src/commands/models/auth.ts index a563d4bddc4..a7c70a2d566 100644 --- a/src/commands/models/auth.ts +++ b/src/commands/models/auth.ts @@ -101,7 +101,9 @@ function listProvidersWithTokenMethods(providers: ProviderPlugin[]): ProviderPlu return providers.filter((provider) => listTokenAuthMethods(provider).length > 0); } -async function resolveModelsAuthContext(): Promise { +async function resolveModelsAuthContext(params?: { + requestedProvider?: string; +}): Promise { const config = await loadValidConfigOrThrow(); const defaultAgentId = resolveDefaultAgentId(config); const agentDir = resolveAgentDir(config, defaultAgentId); @@ -111,8 +113,18 @@ async function resolveModelsAuthContext(): Promise { config, workspaceDir, mode: "setup", + bundledProviderAllowlistCompat: true, + bundledProviderVitestCompat: true, + ...(params?.requestedProvider?.trim() + ? { providerRefs: [params.requestedProvider], activate: true } + : {}), }); - return { config, agentDir, workspaceDir, providers }; + return { + config, + agentDir, + workspaceDir, + providers, + }; } function resolveRequestedProviderOrThrow( @@ -308,7 +320,9 @@ export async function modelsAuthSetupTokenCommand( throw new Error("setup-token requires an interactive TTY."); } - const { config, agentDir, workspaceDir, providers } = await resolveModelsAuthContext(); + const { config, agentDir, workspaceDir, providers } = await resolveModelsAuthContext({ + requestedProvider: opts.provider, + }); const tokenProviders = listProvidersWithTokenMethods(providers); if (tokenProviders.length === 0) { throw new Error( @@ -566,7 +580,9 @@ export async function modelsAuthLoginCommand(opts: LoginOptions, runtime: Runtim throw new Error("models auth login requires an interactive TTY."); } - const { config, agentDir, workspaceDir, providers } = await resolveModelsAuthContext(); + const { config, agentDir, workspaceDir, providers } = await resolveModelsAuthContext({ + requestedProvider: opts.provider, + }); const prompter = createClackPrompter(); const authProviders = listProvidersWithAuthMethods(providers); if (authProviders.length === 0) {