address migrate auth review comments

This commit is contained in:
FullerStackDev
2026-05-23 14:53:48 -06:00
committed by Peter Steinberger
parent 17edec75e4
commit 0a98c2d626
7 changed files with 157 additions and 24 deletions

View File

@@ -236,12 +236,53 @@ The bundled Hermes provider detects state at `~/.hermes` by default. Use `--from
- Memory config defaults for OpenClaw file memory, plus archive or manual-review items for external memory providers such as Honcho.
- Skills that include a `SKILL.md` file under `skills/<name>/`.
- Per-skill config values from `skills.config`.
- Supported OAuth credentials from `auth.json` when interactive credential migration is accepted, or when `--include-secrets` is set.
- Supported API keys from `.env` when interactive credential migration is accepted, or when `--include-secrets` is set.
- Supported OAuth credentials from Hermes `auth.json` and OpenCode OpenAI OAuth credentials from OpenCode `auth.json` when interactive credential migration is accepted, or when `--include-secrets` is set.
- Supported API keys and tokens from Hermes `.env` and OpenCode `auth.json` when interactive credential migration is accepted, or when `--include-secrets` is set.
### Supported `.env` keys
`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `OPENROUTER_API_KEY`, `GOOGLE_API_KEY`, `GEMINI_API_KEY`, `GROQ_API_KEY`, `XAI_API_KEY`, `MISTRAL_API_KEY`, `DEEPSEEK_API_KEY`.
- `AI_GATEWAY_API_KEY`
- `ALIBABA_API_KEY`
- `ANTHROPIC_API_KEY`
- `ARCEEAI_API_KEY`
- `CEREBRAS_API_KEY`
- `CHUTES_API_KEY`
- `CLOUDFLARE_AI_GATEWAY_API_KEY`
- `COPILOT_GITHUB_TOKEN`
- `DASHSCOPE_API_KEY`
- `DEEPINFRA_API_KEY`
- `DEEPSEEK_API_KEY`
- `FIREWORKS_API_KEY`
- `GEMINI_API_KEY`
- `GH_TOKEN`
- `GITHUB_TOKEN`
- `GLM_API_KEY`
- `GOOGLE_API_KEY`
- `GROQ_API_KEY`
- `HF_TOKEN`
- `HUGGINGFACE_HUB_TOKEN`
- `KILOCODE_API_KEY`
- `KIMICODE_API_KEY`
- `KIMI_API_KEY`
- `MINIMAX_API_KEY`
- `MINIMAX_CODING_API_KEY`
- `MISTRAL_API_KEY`
- `MODELSTUDIO_API_KEY`
- `MOONSHOT_API_KEY`
- `NVIDIA_API_KEY`
- `OPENAI_API_KEY`
- `OPENCODE_API_KEY`
- `OPENCODE_GO_API_KEY`
- `OPENCODE_ZEN_API_KEY`
- `OPENROUTER_API_KEY`
- `QIANFAN_API_KEY`
- `QWEN_API_KEY`
- `TOGETHER_API_KEY`
- `VENICE_API_KEY`
- `XAI_API_KEY`
- `XIAOMI_API_KEY`
- `ZAI_API_KEY`
- `Z_AI_API_KEY`
### Archive-only state

View File

@@ -66,7 +66,7 @@ Imports require a fresh OpenClaw setup. If you already have local OpenClaw state
Skills with a `SKILL.md` file under `skills/<name>/` are copied, along with per-skill config values from `skills.config`.
</Accordion>
<Accordion title="Auth credentials">
Interactive `openclaw migrate` asks before importing auth credentials, with yes selected by default. Accepted imports include supported OAuth credentials from `auth.json` and supported `.env` keys: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `OPENROUTER_API_KEY`, `GOOGLE_API_KEY`, `GEMINI_API_KEY`, `GROQ_API_KEY`, `XAI_API_KEY`, `MISTRAL_API_KEY`, `DEEPSEEK_API_KEY`. Use `--include-secrets` for non-interactive `openclaw migrate` credential import, `--no-auth-credentials` to skip it, or onboarding `--import-secrets` when importing from the onboarding wizard.
Interactive `openclaw migrate` asks before importing auth credentials, with yes selected by default. Accepted imports include supported OAuth credentials from Hermes `auth.json`, OpenCode OpenAI OAuth credentials from OpenCode `auth.json`, OpenCode and GitHub Copilot entries from OpenCode `auth.json`, and the [supported `.env` keys](/cli/migrate#supported-env-keys). Use `--include-secrets` for non-interactive `openclaw migrate` credential import, `--no-auth-credentials` to skip it, or onboarding `--import-secrets` when importing from the onboarding wizard.
</Accordion>
</AccordionGroup>
@@ -137,7 +137,7 @@ If a conflict surfaces mid-apply (for example, an unexpected race on a config fi
Interactive `openclaw migrate` asks whether to import detected auth credentials, with yes selected by default.
- Accepting the prompt imports supported OAuth credentials from `auth.json` and supported `.env` keys.
- Accepting the prompt imports supported OAuth credentials from Hermes `auth.json`, OpenCode OpenAI OAuth credentials from OpenCode `auth.json`, OpenCode and GitHub Copilot entries from OpenCode `auth.json`, and the [supported `.env` keys](/cli/migrate#supported-env-keys).
- Use `--no-auth-credentials` or choose no at the prompt to import non-secret state only.
- Use `--include-secrets` when running unattended with `--yes`.
- Use onboarding `--import-secrets` when importing credentials from the onboarding wizard.
@@ -165,7 +165,7 @@ With `--json` and no `--yes`, apply prints the plan and does not mutate state. T
Onboarding imports require a fresh setup. Either reset state and re-onboard, or use `openclaw migrate apply hermes` directly, which supports `--overwrite` and explicit backup control.
</Accordion>
<Accordion title="API keys did not import">
Interactive `openclaw migrate` runs import API keys only when you accept the credential prompt. Non-interactive `--yes` runs require `--include-secrets`; onboarding imports require `--import-secrets`. Only the keys listed above are recognized; other variables in `.env` are ignored.
Interactive `openclaw migrate` imports API keys only when you accept the credential prompt. Non-interactive `--yes` runs require `--include-secrets`; onboarding imports require `--import-secrets`. Only the [supported `.env` keys](/cli/migrate#supported-env-keys) are recognized; other variables in `.env` are ignored.
</Accordion>
</AccordionGroup>

View File

@@ -166,7 +166,7 @@ focused channel/runtime subpaths, `config-contracts`, `string-coerce-runtime`,
| `plugin-sdk/provider-auth-api-key` | API-key onboarding/profile-write helpers such as `upsertApiKeyProfile` |
| `plugin-sdk/provider-auth-result` | Standard OAuth auth-result builder |
| `plugin-sdk/provider-env-vars` | Provider auth env-var lookup helpers |
| `plugin-sdk/provider-auth` | `createProviderApiKeyAuthMethod`, `ensureApiKeyFromOptionEnvOrPrompt`, `applyProviderAuthConfigPatch`, `upsertAuthProfile`, `upsertApiKeyProfile`, `writeOAuthCredentials`, deprecated `resolveOpenClawAgentDir` compatibility export |
| `plugin-sdk/provider-auth` | `createProviderApiKeyAuthMethod`, `ensureApiKeyFromOptionEnvOrPrompt`, `upsertAuthProfile`, `upsertApiKeyProfile`, `writeOAuthCredentials`, deprecated `resolveOpenClawAgentDir` compatibility export |
| `plugin-sdk/provider-model-shared` | `ProviderReplayFamily`, `buildProviderReplayFamilyHooks`, `normalizeModelCompat`, shared replay-policy builders, provider-endpoint helpers, and shared model-id normalization helpers |
| `plugin-sdk/provider-catalog-runtime` | Provider catalog augmentation runtime hook and plugin-provider registry seams for contract tests |
| `plugin-sdk/provider-catalog-shared` | `findCatalogTemplate`, `buildSingleProviderApiKeyCatalog`, `buildManifestModelProviderConfig`, `supportsNativeStreamingUsageCompat`, `applyProviderNativeStreamingUsageCompat` |

View File

@@ -8,7 +8,6 @@ import {
import type { MigrationItem, MigrationProviderContext } from "openclaw/plugin-sdk/plugin-entry";
import {
applyAuthProfileConfig,
applyProviderAuthConfigPatch,
buildApiKeyCredential,
buildOauthProviderAuthResult,
readCodexCliCredentialsCached,
@@ -34,6 +33,10 @@ const CODEX_REASON_MISSING_AUTH_METADATA = "missing auth metadata";
const CODEX_CONFIG_PATCH_MODE_RETURN = "return";
type CodexMigrationTargets = ReturnType<typeof resolveCodexMigrationTargets>;
type AgentDefaultModelConfigs = NonNullable<
NonNullable<NonNullable<OpenClawConfig["agents"]>["defaults"]>["models"]
>;
type AgentDefaultModelConfigEntry = AgentDefaultModelConfigs[string];
type CodexAuthCredential =
| {
@@ -41,6 +44,7 @@ type CodexAuthCredential =
provider: typeof OPENAI_CODEX_PROVIDER_ID;
profileId: string;
result: ProviderAuthResult;
modelConfigs: AgentDefaultModelConfigs;
}
| {
kind: "api_key";
@@ -170,6 +174,15 @@ async function readModelRefs(source: CodexSource): Promise<string[]> {
return [...refs].toSorted();
}
function readProviderAuthModelConfigs(result: ProviderAuthResult): AgentDefaultModelConfigs {
const models = result.configPatch?.agents?.defaults?.models;
if (isRecord(models)) {
return { ...models };
}
const defaultModel = readString(result.defaultModel) ?? OPENAI_CODEX_DEFAULT_MODEL;
return { [defaultModel]: {} };
}
async function buildCodexOAuthCredential(source: CodexSource): Promise<CodexAuthCredential | null> {
const credential = readCodexCliCredentialsCached({
codexHome: source.codexHome,
@@ -206,7 +219,13 @@ async function buildCodexOAuthCredential(source: CodexSource): Promise<CodexAuth
});
const profile = result.profiles[0];
return profile
? { kind: "oauth", provider: OPENAI_CODEX_PROVIDER_ID, profileId: profile.profileId, result }
? {
kind: "oauth",
provider: OPENAI_CODEX_PROVIDER_ID,
profileId: profile.profileId,
result,
modelConfigs: readProviderAuthModelConfigs(result),
}
: null;
}
@@ -351,17 +370,47 @@ function applyDefaultModelIfMissing(cfg: OpenClawConfig): OpenClawConfig {
};
}
function mergeModelConfigEntry(
existing: AgentDefaultModelConfigEntry | undefined,
patch: AgentDefaultModelConfigEntry,
): AgentDefaultModelConfigEntry {
if (existing && isRecord(existing) && isRecord(patch)) {
return { ...existing, ...patch } as AgentDefaultModelConfigEntry;
}
return existing ?? patch;
}
function applyOAuthModelConfigsToConfig(
cfg: OpenClawConfig,
credential: Extract<CodexAuthCredential, { kind: "oauth" }>,
): OpenClawConfig {
const existingModels = cfg.agents?.defaults?.models ?? {};
const models: AgentDefaultModelConfigs = credential.result.replaceDefaultModels
? { ...credential.modelConfigs }
: { ...existingModels };
if (!credential.result.replaceDefaultModels) {
for (const [modelRef, modelConfig] of Object.entries(credential.modelConfigs)) {
models[modelRef] = mergeModelConfigEntry(models[modelRef], modelConfig);
}
}
return {
...cfg,
agents: {
...cfg.agents,
defaults: {
...cfg.agents?.defaults,
models,
},
},
};
}
function applyOAuthConfigToConfig(
cfg: OpenClawConfig,
credential: Extract<CodexAuthCredential, { kind: "oauth" }>,
profileId: string,
): OpenClawConfig {
let next = cfg;
if (credential.result.configPatch) {
next = applyProviderAuthConfigPatch(next, credential.result.configPatch, {
replaceDefaultModels: credential.result.replaceDefaultModels,
});
}
let next = applyOAuthModelConfigsToConfig(cfg, credential);
const profile = credential.result.profiles[0];
if (profile) {
next = applyAuthProfileConfig(next, {

View File

@@ -8,11 +8,11 @@ import {
} from "openclaw/plugin-sdk/migration";
import type { MigrationItem, MigrationProviderContext } from "openclaw/plugin-sdk/plugin-entry";
import {
applyProviderAuthConfigPatch,
buildOauthProviderAuthResult,
updateAuthProfileStoreWithLock,
type AuthProfileStore,
type OAuthCredential,
type OpenClawConfig,
type ProviderAuthResult,
} from "openclaw/plugin-sdk/provider-auth";
import {
@@ -37,6 +37,11 @@ const OPENAI_CODEX_PROVIDER_ID = "openai-codex";
const OPENAI_CODEX_DEFAULT_MODEL = "openai/gpt-5.5";
const HERMES_AUTH_DISPLAY_NAME = "Hermes import";
type AgentDefaultModelConfigs = NonNullable<
NonNullable<NonNullable<OpenClawConfig["agents"]>["defaults"]>["models"]
>;
type AgentDefaultModelConfigEntry = AgentDefaultModelConfigs[string];
type HermesCodexAuthCandidate = {
access: string;
accountId?: string;
@@ -303,6 +308,51 @@ function buildAuthResult(
});
}
function readProviderAuthModelConfigs(result: ProviderAuthResult): AgentDefaultModelConfigs {
const models = result.configPatch?.agents?.defaults?.models;
if (isRecord(models)) {
return { ...models };
}
const defaultModel = readString(result.defaultModel) ?? OPENAI_CODEX_DEFAULT_MODEL;
return { [defaultModel]: {} };
}
function mergeModelConfigEntry(
existing: AgentDefaultModelConfigEntry | undefined,
patch: AgentDefaultModelConfigEntry,
): AgentDefaultModelConfigEntry {
if (existing && isRecord(existing) && isRecord(patch)) {
return { ...existing, ...patch } as AgentDefaultModelConfigEntry;
}
return existing ?? patch;
}
function applyOAuthModelConfigsToConfig(
cfg: OpenClawConfig,
result: ProviderAuthResult,
): OpenClawConfig {
const patchModels = readProviderAuthModelConfigs(result);
const existingModels = cfg.agents?.defaults?.models ?? {};
const models: AgentDefaultModelConfigs = result.replaceDefaultModels
? { ...patchModels }
: { ...existingModels };
if (!result.replaceDefaultModels) {
for (const [modelRef, modelConfig] of Object.entries(patchModels)) {
models[modelRef] = mergeModelConfigEntry(models[modelRef], modelConfig);
}
}
return {
...cfg,
agents: {
...cfg.agents,
defaults: {
...cfg.agents?.defaults,
models,
},
},
};
}
function authProfileDedupeKey(profile: HermesCodexAuthProfile): string {
if (profile.credential.accountId) {
return `${profile.credential.provider}:account:${profile.credential.accountId}`;
@@ -564,12 +614,7 @@ export async function applyAuthItem(
ctx,
profile: configProfile,
applyConfigPatch(config) {
if (!profile.result.configPatch) {
return config;
}
return applyProviderAuthConfigPatch(config, profile.result.configPatch, {
replaceDefaultModels: profile.result.replaceDefaultModels,
});
return applyOAuthModelConfigsToConfig(config, profile.result);
},
});
if (configResult === "conflict") {

View File

@@ -81,7 +81,6 @@ export {
type ApiKeyStorageOptions,
type WriteOAuthCredentialsOptions,
} from "../plugins/provider-auth-helpers.js";
export { applyProviderAuthConfigPatch } from "../plugins/provider-auth-choice-helpers.js";
export { createProviderApiKeyAuthMethod } from "../plugins/provider-api-key-auth.js";
export { coerceSecretRef, hasConfiguredSecretInput } from "../config/types.secrets.js";
export { resolveDefaultSecretProviderAlias } from "../secrets/ref-contract.js";

View File

@@ -1206,7 +1206,6 @@ describe("plugin-sdk subpath exports", () => {
expectSourceOmitsImportPattern("provider-setup", "./vllm.js");
expectSourceOmitsImportPattern("provider-setup", "./sglang.js");
expectSourceMentions("provider-auth", [
"applyProviderAuthConfigPatch",
"buildOauthProviderAuthResult",
"generateHexPkceVerifierChallenge",
"generatePkceVerifierChallenge",