refactor: split models registry loading from persistence

This commit is contained in:
Peter Steinberger
2026-03-08 16:53:46 +00:00
parent 749eb4efea
commit 8a18e2598f
3 changed files with 26 additions and 21 deletions

View File

@@ -324,7 +324,19 @@ describe("models list/status", () => {
await expect(loadModelRegistry({})).rejects.toThrow("model discovery unavailable");
});
it("loadModelRegistry persists using source config snapshot when provided", async () => {
it("loadModelRegistry does not persist models.json as a side effect", async () => {
modelRegistryState.models = [OPENAI_MODEL];
modelRegistryState.available = [OPENAI_MODEL];
const resolvedConfig = {
models: { providers: { openai: { apiKey: "sk-resolved-runtime-value" } } }, // pragma: allowlist secret
};
await loadModelRegistry(resolvedConfig as never);
expect(ensureOpenClawModelsJson).not.toHaveBeenCalled();
});
it("modelsListCommand persists using the write snapshot config when provided", async () => {
modelRegistryState.models = [OPENAI_MODEL];
modelRegistryState.available = [OPENAI_MODEL];
const sourceConfig = {
@@ -333,21 +345,14 @@ describe("models list/status", () => {
const resolvedConfig = {
models: { providers: { openai: { apiKey: "sk-resolved-runtime-value" } } }, // pragma: allowlist secret
};
readConfigFileSnapshotForWrite.mockResolvedValue({
snapshot: { valid: true, resolved: resolvedConfig, source: sourceConfig },
writeOptions: {},
});
setDefaultModel("openai/gpt-4.1-mini");
const runtime = makeRuntime();
await loadModelRegistry(resolvedConfig as never, { sourceConfig: sourceConfig as never });
expect(ensureOpenClawModelsJson).toHaveBeenCalledTimes(1);
expect(ensureOpenClawModelsJson).toHaveBeenCalledWith(sourceConfig);
});
it("loadModelRegistry uses resolved config when no source snapshot is provided", async () => {
modelRegistryState.models = [OPENAI_MODEL];
modelRegistryState.available = [OPENAI_MODEL];
const resolvedConfig = {
models: { providers: { openai: { apiKey: "sk-resolved-runtime-value" } } }, // pragma: allowlist secret
};
await loadModelRegistry(resolvedConfig as never);
await modelsListCommand({ all: true, json: true }, runtime);
expect(ensureOpenClawModelsJson).toHaveBeenCalledTimes(1);
expect(ensureOpenClawModelsJson).toHaveBeenCalledWith(resolvedConfig);

View File

@@ -23,6 +23,7 @@ export async function modelsListCommand(
) {
ensureFlagCompatibility(opts);
const { ensureAuthProfileStore } = await import("../../agents/auth-profiles.js");
const { ensureOpenClawModelsJson } = await import("../../agents/models-config.js");
const { sourceConfig, resolvedConfig: cfg } = await loadModelsConfigWithSource({
commandName: "models list",
runtime,
@@ -42,6 +43,9 @@ export async function modelsListCommand(
let availableKeys: Set<string> | undefined;
let availabilityErrorMessage: string | undefined;
try {
// Keep command behavior explicit: sync models.json from the source config
// before building the read-only model registry view.
await ensureOpenClawModelsJson(sourceConfig ?? cfg);
const loaded = await loadModelRegistry(cfg, { sourceConfig });
modelRegistry = loaded.registry;
models = loaded.models;

View File

@@ -8,7 +8,6 @@ import {
resolveAwsSdkEnvVarName,
resolveEnvApiKey,
} from "../../agents/model-auth.js";
import { ensureOpenClawModelsJson } from "../../agents/models-config.js";
import { discoverAuthStorage, discoverModels } from "../../agents/pi-model-discovery.js";
import type { OpenClawConfig } from "../../config/config.js";
import {
@@ -95,12 +94,9 @@ function loadAvailableModels(registry: ModelRegistry): Model<Api>[] {
}
export async function loadModelRegistry(
cfg: OpenClawConfig,
opts?: { sourceConfig?: OpenClawConfig },
_cfg: OpenClawConfig,
_opts?: { sourceConfig?: OpenClawConfig },
) {
// Persistence must be based on source config (pre-resolution) so SecretRef-managed
// credentials remain markers in models.json for command paths too.
await ensureOpenClawModelsJson(opts?.sourceConfig ?? cfg);
const agentDir = resolveOpenClawAgentDir();
const authStorage = discoverAuthStorage(agentDir);
const registry = discoverModels(authStorage, agentDir);