fix(gateway): keep plugin model allowlists static (#118854)

This commit is contained in:
Peter Steinberger
2026-08-03 17:39:45 -07:00
committed by GitHub
parent 1aea2df935
commit e16bc0108c
3 changed files with 34 additions and 7 deletions

View File

@@ -1,5 +1,10 @@
import { parseModelCatalogRef } from "@openclaw/model-catalog-core/model-catalog-refs";
import { normalizeModelRef, parseModelRef } from "../agents/model-selection.js";
import {
normalizeBuiltInProviderModelId,
stripSelfProviderModelPrefix,
} from "@openclaw/model-catalog-core/provider-model-id-normalization";
import { normalizeModelRef } from "../agents/model-ref-shared.js";
import { parseModelRef } from "../agents/model-selection-normalize.js";
import type { PluginRuntime } from "../plugins/runtime/types.js";
export function normalizePluginSubagentAllowedModelRef(raw: string): string | null {
@@ -14,8 +19,13 @@ export function normalizePluginSubagentAllowedModelRef(raw: string): string | nu
if (!parsed) {
return null;
}
const normalized = normalizeModelRef(parsed.provider, parsed.modelId);
return `${normalized.provider}/${normalized.model}`;
// Operator allowlists already name canonical targets; keep policy setup independent
// of plugin metadata and provider-runtime discovery.
const modelId = normalizeBuiltInProviderModelId(
parsed.provider,
stripSelfProviderModelPrefix(parsed.provider, parsed.modelId),
);
return `${parsed.provider}/${modelId}`;
}
export function resolvePluginSubagentRequestedModelRef(params: {

View File

@@ -30,6 +30,7 @@ const applyPluginAutoEnable = vi.hoisted(() =>
const primeConfiguredBindingRegistry = vi.hoisted(() =>
vi.fn(() => ({ bindingCount: 0, channelCount: 0 })),
);
const normalizeProviderModelIdWithRuntime = vi.hoisted(() => vi.fn(() => undefined));
const pluginRuntimeLoaderLogger = vi.hoisted(() => ({
info: vi.fn(),
warn: vi.fn(),
@@ -60,6 +61,10 @@ vi.mock("../config/plugin-auto-enable.js", () => ({
applyPluginAutoEnable,
}));
vi.mock("../agents/provider-model-normalization.runtime.js", () => ({
normalizeProviderModelIdWithRuntime,
}));
vi.mock("../channels/plugins/binding-registry.js", async () => {
const actual = await vi.importActual<typeof import("../channels/plugins/binding-registry.js")>(
"../channels/plugins/binding-registry.js",
@@ -401,6 +406,7 @@ beforeEach(() => {
.mockReset()
.mockImplementation(({ config }) => ({ config, changes: [], autoEnabledReasons: {} }));
primeConfiguredBindingRegistry.mockClear().mockReturnValue({ bindingCount: 0, channelCount: 0 });
normalizeProviderModelIdWithRuntime.mockReset().mockReturnValue(undefined);
pluginRuntimeLoaderLogger.info.mockClear();
pluginRuntimeLoaderLogger.warn.mockClear();
pluginRuntimeLoaderLogger.error.mockClear();
@@ -1612,6 +1618,7 @@ describe("loadGatewayPlugins", () => {
},
},
});
expect(normalizeProviderModelIdWithRuntime).not.toHaveBeenCalled();
serverPlugins.setFallbackGatewayContext(createTestContext("fallback-trusted-overrides"));
await gatewayRequestScopeModule.withPluginRuntimePluginIdScope("voice-call", () =>
runtime.run({
@@ -1627,6 +1634,7 @@ describe("loadGatewayPlugins", () => {
expect(params.sessionKey).toBe("s-trusted-override");
expect(params.provider).toBe("anthropic");
expect(params.model).toBe("claude-haiku-4-5");
expect(normalizeProviderModelIdWithRuntime).toHaveBeenCalledOnce();
});
test("tags plugin fallback subagent runs with the creating plugin id", async () => {

View File

@@ -1,16 +1,20 @@
// Runtime LLM helpers adapt plugin provider hooks into the core model runtime.
import { parseModelCatalogRef } from "@openclaw/model-catalog-core/model-catalog-refs";
import {
normalizeBuiltInProviderModelId,
stripSelfProviderModelPrefix,
} from "@openclaw/model-catalog-core/provider-model-id-normalization";
import { asFiniteNumber } from "@openclaw/normalization-core/number-coercion";
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import { splitTrailingAuthProfile } from "../../agents/model-ref-profile.js";
import { modelKey } from "../../agents/model-ref-shared.js";
import { normalizeModelRef } from "../../agents/model-selection.js";
import { normalizeModelRef } from "../../agents/model-ref-shared.js";
import type { NormalizedUsage, UsageLike } from "../../agents/usage.js";
import { normalizeUsage } from "../../agents/usage.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import type { Api, Message } from "../../llm/types.js";
import { getChildLogger } from "../../logging.js";
import { normalizeAgentId } from "../../routing/session-key.js";
import { modelKey } from "../../shared/model-key.js";
import { estimateUsageCost, resolveModelCostConfig } from "../../utils/usage-format.js";
import { normalizePluginsConfig } from "../config-state.js";
import { getPluginRuntimeGatewayRequestScope } from "./gateway-request-scope.js";
@@ -271,8 +275,13 @@ function normalizeAllowedModelRef(raw: string): string | null {
if (!parsed) {
return null;
}
const normalized = normalizeModelRef(parsed.provider, parsed.modelId);
return modelKey(normalized.provider, normalized.model);
// Operator allowlists already name canonical targets; keep policy checks independent
// of plugin metadata and provider-runtime discovery.
const modelId = normalizeBuiltInProviderModelId(
parsed.provider,
stripSelfProviderModelPrefix(parsed.provider, parsed.modelId),
);
return modelKey(parsed.provider, modelId);
}
function normalizeModelAllowlist(params: {