diff --git a/extensions/anthropic/cli-auth-seam.ts b/extensions/anthropic/cli-auth-seam.ts new file mode 100644 index 00000000000..e00ad540b99 --- /dev/null +++ b/extensions/anthropic/cli-auth-seam.ts @@ -0,0 +1,5 @@ +import { readClaudeCliCredentialsCached } from "openclaw/plugin-sdk/provider-auth"; + +export function readClaudeCliCredentialsForRuntime() { + return readClaudeCliCredentialsCached(); +} diff --git a/extensions/anthropic/index.test.ts b/extensions/anthropic/index.test.ts index 7d4736e48e8..16afcdd0241 100644 --- a/extensions/anthropic/index.test.ts +++ b/extensions/anthropic/index.test.ts @@ -1,15 +1,13 @@ import { describe, expect, it, vi } from "vitest"; import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js"; -const { readClaudeCliCredentialsCachedMock } = vi.hoisted(() => ({ - readClaudeCliCredentialsCachedMock: vi.fn(), +const { readClaudeCliCredentialsForRuntimeMock } = vi.hoisted(() => ({ + readClaudeCliCredentialsForRuntimeMock: vi.fn(), })); -vi.mock("openclaw/plugin-sdk/provider-auth", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("./cli-auth-seam.js", () => { return { - ...actual, - readClaudeCliCredentialsCached: readClaudeCliCredentialsCachedMock, + readClaudeCliCredentialsForRuntime: readClaudeCliCredentialsForRuntimeMock, }; }); @@ -97,9 +95,9 @@ describe("anthropic provider replay hooks", () => { ).toBe("short"); }); - it("resolves claude-cli synthetic auth without allowing keychain prompts", async () => { - readClaudeCliCredentialsCachedMock.mockReset(); - readClaudeCliCredentialsCachedMock.mockReturnValue({ + it("resolves claude-cli synthetic oauth auth", async () => { + readClaudeCliCredentialsForRuntimeMock.mockReset(); + readClaudeCliCredentialsForRuntimeMock.mockReturnValue({ type: "oauth", provider: "anthropic", access: "access-token", @@ -118,8 +116,28 @@ describe("anthropic provider replay hooks", () => { source: "Claude CLI native auth", mode: "oauth", }); - expect(readClaudeCliCredentialsCachedMock).toHaveBeenCalledWith({ - allowKeychainPrompt: false, + expect(readClaudeCliCredentialsForRuntimeMock).toHaveBeenCalledTimes(1); + }); + + it("resolves claude-cli synthetic token auth", async () => { + readClaudeCliCredentialsForRuntimeMock.mockReset(); + readClaudeCliCredentialsForRuntimeMock.mockReturnValue({ + type: "token", + provider: "anthropic", + token: "bearer-token", + expires: 123, + }); + + const provider = await registerSingleProviderPlugin(anthropicPlugin); + + expect( + provider.resolveSyntheticAuth?.({ + provider: "claude-cli", + } as never), + ).toEqual({ + apiKey: "bearer-token", + source: "Claude CLI native auth", + mode: "token", }); }); }); diff --git a/extensions/anthropic/register.runtime.ts b/extensions/anthropic/register.runtime.ts index 0433e7c0060..d778c4786dd 100644 --- a/extensions/anthropic/register.runtime.ts +++ b/extensions/anthropic/register.runtime.ts @@ -13,7 +13,6 @@ import { ensureApiKeyFromOptionEnvOrPrompt, listProfilesForProvider, normalizeApiKeyInput, - readClaudeCliCredentialsCached, type OpenClawConfig as ProviderAuthConfig, suggestOAuthProfileIdForLegacyDefault, type AuthProfileStore, @@ -24,6 +23,7 @@ import { } from "openclaw/plugin-sdk/provider-auth"; import { cloneFirstTemplateModel } from "openclaw/plugin-sdk/provider-model-shared"; import { fetchClaudeUsage } from "openclaw/plugin-sdk/provider-usage"; +import { readClaudeCliCredentialsForRuntime } from "./cli-auth-seam.js"; import { buildAnthropicCliBackend } from "./cli-backend.js"; import { buildAnthropicCliMigrationResult, hasClaudeCliAuth } from "./cli-migration.js"; import { CLAUDE_CLI_BACKEND_ID } from "./cli-shared.js"; @@ -285,7 +285,7 @@ function buildAnthropicAuthDoctorHint(params: { } function resolveClaudeCliSyntheticAuth() { - const credential = readClaudeCliCredentialsCached({ allowKeychainPrompt: false }); + const credential = readClaudeCliCredentialsForRuntime(); if (!credential) { return undefined; }