import {
CLAUDE_CLI_PROFILE_ID,
type OpenClawConfig,
type ProviderAuthResult,
} from "openclaw/plugin-sdk/provider-auth" ;
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime" ;
import {
readClaudeCliCredentialsForSetup,
readClaudeCliCredentialsForSetupNonInteractive,
} from "./cli-auth-seam.js" ;
import { CLAUDE_CLI_BACKEND_ID, CLAUDE_CLI_DEFAULT_ALLOWLIST_REFS } from "./cli-shared.js" ;
type AgentDefaultsModel = NonNullable<NonNullable<OpenClawConfig["agents" ]>["defaults" ]>["model" ];
type AgentDefaultsModels = NonNullable<NonNullable<OpenClawConfig["agents" ]>["defaults" ]>["models" ];
type AgentDefaultsEmbeddedHarness = NonNullable<
NonNullable<OpenClawConfig["agents" ]>["defaults" ]
>["embeddedHarness" ];
type ClaudeCliCredential = NonNullable<ReturnType<typeof readClaudeCliCredentialsForSetup>>;
function toAnthropicModelRef(raw: string): string | null {
const trimmed = raw.trim();
const lower = normalizeLowercaseStringOrEmpty(trimmed);
const provider = lower.startsWith("anthropic/" )
? "anthropic"
: lower.startsWith(`${CLAUDE_CLI_BACKEND_ID}/`)
? CLAUDE_CLI_BACKEND_ID
: "" ;
if (!provider) {
return null ;
}
const modelId = trimmed.slice(provider.length + 1 ).trim();
if (!normalizeLowercaseStringOrEmpty(modelId).startsWith("claude-" )) {
return null ;
}
return `anthropic/${modelId}`;
}
function rewriteModelSelection(model: AgentDefaultsModel): {
value: AgentDefaultsModel;
primary?: string;
changed: boolean ;
} {
if (typeof model === "string" ) {
const converted = toAnthropicModelRef(model);
return converted
? { value: converted, primary: converted, changed: true }
: { value: model, changed: false };
}
if (!model || typeof model !== "object" || Array.isArray(model)) {
return { value: model, changed: false };
}
const current = model as Record<string, unknown>;
const next: Record<string, unknown> = { ...current };
let changed = false ;
let primary: string | undefined;
if (typeof current.primary === "string" ) {
const converted = toAnthropicModelRef(current.primary);
if (converted) {
next.primary = converted;
primary = converted;
changed = true ;
}
}
const currentFallbacks = current.fallbacks;
if (Array.isArray(currentFallbacks)) {
const nextFallbacks = currentFallbacks.map((entry) =>
typeof entry === "string" ? (toAnthropicModelRef(entry) ?? entry) : entry,
);
if (nextFallbacks.some((entry, index) => entry !== currentFallbacks[index])) {
next.fallbacks = nextFallbacks;
changed = true ;
}
}
return {
value: changed ? next : model,
...(primary ? { primary } : {}),
changed,
};
}
function rewriteModelEntryMap(models: Record<string, unknown> | undefined): {
value: Record<string, unknown> | undefined;
migrated: string[];
} {
if (!models) {
return { value: models, migrated: [] };
}
const next = { ...models };
const migrated: string[] = [];
for (const [rawKey, value] of Object.entries(models)) {
const converted = toAnthropicModelRef(rawKey);
if (!converted) {
continue ;
}
if (converted === rawKey) {
continue ;
}
if (!(converted in next)) {
next[converted] = value;
}
delete next[rawKey];
migrated.push(converted);
}
return {
value: migrated.length > 0 ? next : models,
migrated,
};
}
function seedClaudeCliAllowlist(
models: NonNullable<AgentDefaultsModels>,
): NonNullable<AgentDefaultsModels> {
const next = { ...models };
for (const ref of CLAUDE_CLI_DEFAULT_ALLOWLIST_REFS) {
const canonicalRef = toAnthropicModelRef(ref) ?? ref;
next[canonicalRef] = next[canonicalRef] ?? {};
}
return next;
}
function selectClaudeCliRuntime(
embeddedHarness: AgentDefaultsEmbeddedHarness | undefined,
): AgentDefaultsEmbeddedHarness {
const currentRuntime = embeddedHarness?.runtime?.trim();
if (currentRuntime && currentRuntime !== "auto" ) {
return embeddedHarness;
}
return {
...embeddedHarness,
runtime: CLAUDE_CLI_BACKEND_ID,
};
}
export function hasClaudeCliAuth(options?: { allowKeychainPrompt?: boolean }): boolean {
return Boolean (
options?.allowKeychainPrompt === false
? readClaudeCliCredentialsForSetupNonInteractive()
: readClaudeCliCredentialsForSetup(),
);
}
function buildClaudeCliAuthProfiles(
credential?: ClaudeCliCredential | null ,
): ProviderAuthResult["profiles" ] {
if (!credential) {
return [];
}
if (credential.type === "oauth" ) {
return [
{
profileId: CLAUDE_CLI_PROFILE_ID,
credential: {
type: "oauth" ,
provider: CLAUDE_CLI_BACKEND_ID,
access: credential.access,
refresh: credential.refresh,
expires: credential.expires,
},
},
];
}
return [
{
profileId: CLAUDE_CLI_PROFILE_ID,
credential: {
type: "token" ,
provider: CLAUDE_CLI_BACKEND_ID,
token: credential.token,
expires: credential.expires,
},
},
];
}
export function buildAnthropicCliMigrationResult(
config: OpenClawConfig,
credential?: ClaudeCliCredential | null ,
): ProviderAuthResult {
const defaults = config.agents?.defaults;
const rewrittenModel = rewriteModelSelection(defaults?.model);
const rewrittenModels = rewriteModelEntryMap(defaults?.models);
const existingModels = (rewrittenModels.value ??
defaults?.models ??
{}) as NonNullable<AgentDefaultsModels>;
const nextModels = seedClaudeCliAllowlist(existingModels);
const defaultModel = rewrittenModel.primary ?? "anthropic/claude-opus-4-7" ;
return {
profiles: buildClaudeCliAuthProfiles(credential),
configPatch: {
agents: {
defaults: {
...(rewrittenModel.changed ? { model: rewrittenModel.value } : {}),
embeddedHarness: selectClaudeCliRuntime(defaults?.embeddedHarness),
models: nextModels,
},
},
},
// Rewrites `claude-cli/*` -> `anthropic/*`; merge would keep stale keys.
replaceDefaultModels: true ,
defaultModel,
notes: [
"Claude CLI auth detected; kept Anthropic model refs and selected the local Claude CLI runtime." ,
"Existing Anthropic auth profiles are kept for rollback." ,
...(rewrittenModels.migrated.length > 0
? [`Migrated allowlist entries: ${rewrittenModels.migrated.join(", " )}.`]
: []),
],
};
}
Messung V0.5 in Prozent C=100 H=96 G=97
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland