fix: scope model provider discovery metadata to workspace

This commit is contained in:
Shakker
2026-04-27 17:32:56 +01:00
parent 4e7de4b5c9
commit 4ceae8262f
4 changed files with 35 additions and 2 deletions

View File

@@ -151,7 +151,7 @@ Gateway startup builds one `PluginMetadataSnapshot` for the current config snaps
Plugin-aware config validation, startup auto-enable, and Gateway plugin bootstrap consume that snapshot instead of rebuilding manifest/index metadata independently. `PluginLookUpTable` is derived from the same snapshot and adds the startup plugin plan for the current runtime config.
After startup, Gateway keeps the current metadata snapshot as a replaceable runtime product. Repeated runtime provider discovery can borrow that snapshot instead of reconstructing the installed index and manifest registry for each provider-catalog pass. The snapshot is cleared or replaced on Gateway shutdown, config/plugin inventory changes, and installed index writes; callers fall back to the cold manifest/index path when no compatible current snapshot exists.
After startup, Gateway keeps the current metadata snapshot as a replaceable runtime product. Repeated runtime provider discovery can borrow that snapshot instead of reconstructing the installed index and manifest registry for each provider-catalog pass. The snapshot is cleared or replaced on Gateway shutdown, config/plugin inventory changes, and installed index writes; callers fall back to the cold manifest/index path when no compatible current snapshot exists. Compatibility checks must include plugin discovery roots such as `plugins.load.paths` and the default agent workspace, because workspace plugins are part of the metadata scope.
The snapshot and lookup table keep repeated startup decisions on the fast path:

View File

@@ -104,6 +104,27 @@ describe("models-config", () => {
expect(observedSnapshot).toBe(pluginMetadataSnapshot);
});
it("threads workspace scope into implicit provider discovery", async () => {
let observedWorkspaceDir: string | undefined;
await resolveProvidersForModelsJsonWithDeps(
{
cfg: { models: { providers: {} } },
agentDir: "/tmp/openclaw-models-config-env-vars-test",
env: {},
workspaceDir: "/tmp/openclaw-workspace",
},
{
resolveImplicitProviders: async ({ workspaceDir }) => {
observedWorkspaceDir = workspaceDir;
return {};
},
},
);
expect(observedWorkspaceDir).toBe("/tmp/openclaw-workspace");
});
it("threads plugin metadata snapshots through models.json planning", async () => {
const pluginMetadataSnapshot = {
index: { plugins: [] },

View File

@@ -19,6 +19,7 @@ export type ResolveImplicitProvidersForModelsJson = (params: {
agentDir: string;
config: OpenClawConfig;
env: NodeJS.ProcessEnv;
workspaceDir?: string;
explicitProviders: Record<string, ProviderConfig>;
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
}) => Promise<Record<string, ProviderConfig>>;
@@ -40,6 +41,7 @@ export async function resolveProvidersForModelsJsonWithDeps(
cfg: OpenClawConfig;
agentDir: string;
env: NodeJS.ProcessEnv;
workspaceDir?: string;
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
},
deps?: {
@@ -53,6 +55,7 @@ export async function resolveProvidersForModelsJsonWithDeps(
agentDir,
config: cfg,
env,
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
explicitProviders,
...(params.pluginMetadataSnapshot
? { pluginMetadataSnapshot: params.pluginMetadataSnapshot }
@@ -94,6 +97,7 @@ export async function planOpenClawModelsJsonWithDeps(
sourceConfigForSecrets?: OpenClawConfig;
agentDir: string;
env: NodeJS.ProcessEnv;
workspaceDir?: string;
existingRaw: string;
existingParsed: unknown;
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
@@ -108,6 +112,7 @@ export async function planOpenClawModelsJsonWithDeps(
cfg,
agentDir,
env,
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
...(params.pluginMetadataSnapshot
? { pluginMetadataSnapshot: params.pluginMetadataSnapshot }
: {}),

View File

@@ -11,6 +11,7 @@ import { getCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-meta
import { resolveInstalledManifestRegistryIndexFingerprint } from "../plugins/manifest-registry-installed.js";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
import { resolveOpenClawAgentDir } from "./agent-paths.js";
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "./agent-scope.js";
import { MODELS_JSON_STATE } from "./models-config-state.js";
import { planOpenClawModelsJson } from "./models-config.plan.js";
@@ -44,6 +45,7 @@ async function buildModelsJsonFingerprint(params: {
config: OpenClawConfig;
sourceConfigForSecrets: OpenClawConfig;
agentDir: string;
workspaceDir?: string;
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index">;
}): Promise<string> {
const authProfilesMtimeMs = await readFileMtimeMs(
@@ -60,6 +62,7 @@ async function buildModelsJsonFingerprint(params: {
envShape,
authProfilesMtimeMs,
modelsFileMtimeMs,
workspaceDir: params.workspaceDir,
pluginMetadataSnapshotIndexFingerprint,
});
}
@@ -152,14 +155,17 @@ export async function ensureOpenClawModelsJson(
): Promise<{ agentDir: string; wrote: boolean }> {
const resolved = resolveModelsConfigInput(config);
const cfg = resolved.config;
const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
const pluginMetadataSnapshot =
options.pluginMetadataSnapshot ?? getCurrentPluginMetadataSnapshot({ config: cfg });
options.pluginMetadataSnapshot ??
getCurrentPluginMetadataSnapshot({ config: cfg, workspaceDir });
const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveOpenClawAgentDir();
const targetPath = path.join(agentDir, "models.json");
const fingerprint = await buildModelsJsonFingerprint({
config: cfg,
sourceConfigForSecrets: resolved.sourceConfigForSecrets,
agentDir,
workspaceDir,
...(pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {}),
});
const cached = MODELS_JSON_STATE.readyCache.get(targetPath);
@@ -181,6 +187,7 @@ export async function ensureOpenClawModelsJson(
sourceConfigForSecrets: resolved.sourceConfigForSecrets,
agentDir,
env,
workspaceDir,
existingRaw: existingModelsFile.raw,
existingParsed: existingModelsFile.parsed,
...(pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {}),