Files
openclaw/extensions/migrate-hermes/secrets.ts
Peter Steinberger c6ee68b751 Reapply "refactor: move runtime state to SQLite"
This reverts commit 694ca50e97.
2026-05-28 00:46:31 +01:00

253 lines
9.6 KiB
TypeScript

import {
loadAuthProfileStoreWithoutExternalProfiles,
resolveAuthProfileStoreLocationForDisplay,
} from "openclaw/plugin-sdk/agent-runtime";
import type { MigrationItem, MigrationProviderContext } from "openclaw/plugin-sdk/plugin-entry";
import { updateAuthProfileStoreWithLock } from "openclaw/plugin-sdk/provider-auth";
import {
applyAuthProfileConfigWithConflictCheck,
hasAuthProfileConfigConflict,
hasCurrentAuthProfileConfigConflict,
type HermesAuthProfileConfig,
} from "./auth-config.js";
import { isRecord, parseEnv, readString, readText } from "./helpers.js";
import {
createHermesSecretItem,
HERMES_REASON_AUTH_PROFILE_EXISTS,
HERMES_REASON_AUTH_PROFILE_WRITE_FAILED,
HERMES_REASON_MISSING_SECRET_METADATA,
HERMES_REASON_SECRET_NO_LONGER_PRESENT,
hermesItemConflict,
hermesItemError,
hermesItemSkipped,
readHermesSecretDetails,
} from "./items.js";
import type { HermesSource } from "./source.js";
import type { PlannedTargets } from "./targets.js";
type SecretCredentialMode = "api_key" | "token";
type SecretMapping = {
envVar: string;
provider: string;
profileId: string;
mode?: SecretCredentialMode;
};
const SECRET_MAPPINGS: readonly SecretMapping[] = [
{ envVar: "OPENAI_API_KEY", provider: "openai", profileId: "openai:hermes-import" },
{ envVar: "ANTHROPIC_API_KEY", provider: "anthropic", profileId: "anthropic:hermes-import" },
{ envVar: "OPENROUTER_API_KEY", provider: "openrouter", profileId: "openrouter:hermes-import" },
{ envVar: "GOOGLE_API_KEY", provider: "google", profileId: "google:hermes-import" },
{ envVar: "GEMINI_API_KEY", provider: "google", profileId: "google:hermes-import" },
{ envVar: "GROQ_API_KEY", provider: "groq", profileId: "groq:hermes-import" },
{ envVar: "XAI_API_KEY", provider: "xai", profileId: "xai:hermes-import" },
{ envVar: "MISTRAL_API_KEY", provider: "mistral", profileId: "mistral:hermes-import" },
{ envVar: "DEEPSEEK_API_KEY", provider: "deepseek", profileId: "deepseek:hermes-import" },
{ envVar: "ZAI_API_KEY", provider: "zai", profileId: "zai:hermes-import" },
{ envVar: "Z_AI_API_KEY", provider: "zai", profileId: "zai:hermes-import" },
{ envVar: "GLM_API_KEY", provider: "zai", profileId: "zai:hermes-import" },
{ envVar: "KIMI_API_KEY", provider: "kimi-coding", profileId: "kimi-coding:hermes-import" },
{ envVar: "KIMICODE_API_KEY", provider: "kimi-coding", profileId: "kimi-coding:hermes-import" },
{ envVar: "MOONSHOT_API_KEY", provider: "moonshot", profileId: "moonshot:hermes-import" },
{ envVar: "MINIMAX_API_KEY", provider: "minimax", profileId: "minimax:hermes-import" },
{
envVar: "MINIMAX_CODING_API_KEY",
provider: "minimax",
profileId: "minimax:hermes-import",
},
{ envVar: "DASHSCOPE_API_KEY", provider: "qwen", profileId: "qwen:hermes-import" },
{ envVar: "QWEN_API_KEY", provider: "qwen", profileId: "qwen:hermes-import" },
{ envVar: "MODELSTUDIO_API_KEY", provider: "qwen", profileId: "qwen:hermes-import" },
{ envVar: "KILOCODE_API_KEY", provider: "kilocode", profileId: "kilocode:hermes-import" },
{
envVar: "AI_GATEWAY_API_KEY",
provider: "vercel-ai-gateway",
profileId: "vercel-ai-gateway:hermes-import",
},
{ envVar: "HF_TOKEN", provider: "huggingface", profileId: "huggingface:hermes-import" },
{
envVar: "HUGGINGFACE_HUB_TOKEN",
provider: "huggingface",
profileId: "huggingface:hermes-import",
},
{ envVar: "TOGETHER_API_KEY", provider: "together", profileId: "together:hermes-import" },
{ envVar: "FIREWORKS_API_KEY", provider: "fireworks", profileId: "fireworks:hermes-import" },
{ envVar: "DEEPINFRA_API_KEY", provider: "deepinfra", profileId: "deepinfra:hermes-import" },
{ envVar: "CEREBRAS_API_KEY", provider: "cerebras", profileId: "cerebras:hermes-import" },
{ envVar: "NVIDIA_API_KEY", provider: "nvidia", profileId: "nvidia:hermes-import" },
{ envVar: "VENICE_API_KEY", provider: "venice", profileId: "venice:hermes-import" },
{ envVar: "XIAOMI_API_KEY", provider: "xiaomi", profileId: "xiaomi:hermes-import" },
{ envVar: "ALIBABA_API_KEY", provider: "alibaba", profileId: "alibaba:hermes-import" },
{ envVar: "ARCEEAI_API_KEY", provider: "arcee", profileId: "arcee:hermes-import" },
{ envVar: "CHUTES_API_KEY", provider: "chutes", profileId: "chutes:hermes-import" },
{
envVar: "CLOUDFLARE_AI_GATEWAY_API_KEY",
provider: "cloudflare-ai-gateway",
profileId: "cloudflare-ai-gateway:hermes-import",
},
{ envVar: "QIANFAN_API_KEY", provider: "qianfan", profileId: "qianfan:hermes-import" },
{ envVar: "OPENCODE_API_KEY", provider: "opencode", profileId: "opencode:hermes-import" },
{ envVar: "OPENCODE_API_KEY", provider: "opencode-go", profileId: "opencode-go:hermes-import" },
{ envVar: "OPENCODE_ZEN_API_KEY", provider: "opencode", profileId: "opencode:hermes-import" },
{
envVar: "OPENCODE_ZEN_API_KEY",
provider: "opencode-go",
profileId: "opencode-go:hermes-import",
},
{
envVar: "OPENCODE_GO_API_KEY",
provider: "opencode-go",
profileId: "opencode-go:hermes-import",
},
{
envVar: "COPILOT_GITHUB_TOKEN",
provider: "github-copilot",
profileId: "github-copilot:github",
mode: "token",
},
{
envVar: "GH_TOKEN",
provider: "github-copilot",
profileId: "github-copilot:github",
mode: "token",
},
{
envVar: "GITHUB_TOKEN",
provider: "github-copilot",
profileId: "github-copilot:github",
mode: "token",
},
] as const;
function buildStateEnv(ctx: MigrationProviderContext): NodeJS.ProcessEnv {
return { ...process.env, OPENCLAW_STATE_DIR: ctx.stateDir };
}
export async function buildSecretItems(params: {
ctx: MigrationProviderContext;
source: HermesSource;
targets: PlannedTargets;
}): Promise<MigrationItem[]> {
const env = parseEnv(await readText(params.source.envPath));
const stateEnv = buildStateEnv(params.ctx);
const store = loadAuthProfileStoreWithoutExternalProfiles(params.targets.agentDir, {
env: stateEnv,
});
const seenProfiles = new Set<string>();
const items: MigrationItem[] = [];
const candidates = [
...buildEnvSecretCandidates({ env, envPath: params.source.envPath }),
...(await buildOpenCodeSecretCandidates(params.source.opencodeAuthPath)),
];
for (const candidate of candidates) {
if (seenProfiles.has(candidate.profileId)) {
continue;
}
seenProfiles.add(candidate.profileId);
const existsAlready = Boolean(store.profiles[candidate.profileId]);
const configConflict = hasAuthProfileConfigConflict(
params.ctx.config,
secretAuthProfileConfig(candidate),
Boolean(params.ctx.overwrite),
);
items.push(
createHermesSecretItem({
id: `secret:${mapping.provider}`,
source: params.source.envPath,
target: `${resolveAuthProfileStoreLocationForDisplay(
params.targets.agentDir,
stateEnv,
)}/${mapping.profileId}`,
includeSecrets: params.ctx.includeSecrets,
existsAlready: (existsAlready && !params.ctx.overwrite) || configConflict,
details: {
...(candidate.envVar ? { envVar: candidate.envVar } : {}),
provider: candidate.provider,
profileId: candidate.profileId,
...(candidate.mode === "token" ? { mode: candidate.mode } : {}),
...(candidate.sourceKind ? { sourceKind: candidate.sourceKind } : {}),
...(candidate.sourceProvider ? { sourceProvider: candidate.sourceProvider } : {}),
...(candidate.secretField ? { secretField: candidate.secretField } : {}),
},
}),
);
}
return items;
}
export async function applySecretItem(
ctx: MigrationProviderContext,
item: MigrationItem,
targets: PlannedTargets,
): Promise<MigrationItem> {
if (item.status !== "planned") {
return item;
}
const details = readHermesSecretDetails(item);
const source = item.source;
if (!details || !source) {
return hermesItemError(item, HERMES_REASON_MISSING_SECRET_METADATA);
}
const key = await readSecretCandidateValue(details, source);
if (!key) {
return hermesItemSkipped(item, HERMES_REASON_SECRET_NO_LONGER_PRESENT);
}
const configProfile = secretAuthProfileConfig(details);
if (hasCurrentAuthProfileConfigConflict(ctx, configProfile)) {
return hermesItemConflict(item, HERMES_REASON_AUTH_PROFILE_EXISTS);
}
let conflicted = false;
let wrote = false;
const store = await updateAuthProfileStoreWithLock({
agentDir: targets.agentDir,
env: buildStateEnv(ctx),
updater: (freshStore) => {
if (!ctx.overwrite && freshStore.profiles[details.profileId]) {
conflicted = true;
return false;
}
freshStore.profiles[details.profileId] =
details.mode === "token"
? {
type: "token",
provider: details.provider,
token: key,
displayName: "Hermes import",
}
: {
type: "api_key",
provider: details.provider,
key,
displayName: "Hermes import",
};
wrote = true;
return true;
},
});
if (conflicted) {
return hermesItemConflict(item, HERMES_REASON_AUTH_PROFILE_EXISTS);
}
if (!store?.profiles[details.profileId]) {
return hermesItemError(item, HERMES_REASON_AUTH_PROFILE_WRITE_FAILED);
}
if (!wrote && !ctx.overwrite) {
return hermesItemConflict(item, HERMES_REASON_AUTH_PROFILE_EXISTS);
}
const configResult = await applyAuthProfileConfigWithConflictCheck({
ctx,
profile: configProfile,
});
if (configResult === "conflict") {
return hermesItemConflict(item, HERMES_REASON_AUTH_PROFILE_EXISTS);
}
return {
...item,
status: "migrated",
details: {
...item.details,
configUpdated: configResult === "configured",
},
};
}