mirror of https://github.com/openclaw/openclaw.git
fix: restrict gap check to exact synthetic fallback IDs and guard allowAny
- Replace broad provider-based check with exact SYNTHETIC_FALLBACK_KEYS set matching the 4 IDs from applySyntheticCatalogFallbacks(). Prevents false positives for real catalog models from openai/openai-codex. - Add early return when buildAllowedModelSet returns allowAny: true. - Add missing mockLoadModelCatalog assertion in empty-allowlist test. - Add test for allowAny: true path. Addresses review feedback from chatgpt-codex-connector[bot] and greptile-apps[bot]. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0a9a085cfb
commit
3b393e074f
|
|
@ -4,14 +4,14 @@ vi.mock("../agents/model-catalog.js", () => ({
|
|||
loadModelCatalog: vi.fn(),
|
||||
}));
|
||||
vi.mock("../agents/model-selection.js", async (importOriginal) => {
|
||||
const actual = (await importOriginal()) as Record<string, unknown>;
|
||||
const actual = await importOriginal();
|
||||
return { ...actual, buildAllowedModelSet: vi.fn() };
|
||||
});
|
||||
vi.mock("../terminal/note.js", () => ({ note: vi.fn() }));
|
||||
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { loadModelCatalog } from "../agents/model-catalog.js";
|
||||
import { buildAllowedModelSet } from "../agents/model-selection.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { note } from "../terminal/note.js";
|
||||
import { noteSyntheticAllowlistGaps } from "./doctor-synthetic-allowlist.js";
|
||||
|
||||
|
|
@ -38,6 +38,20 @@ describe("noteSyntheticAllowlistGaps", () => {
|
|||
|
||||
it("skips when allowlist is empty", async () => {
|
||||
await noteSyntheticAllowlistGaps(makeCfg({}));
|
||||
expect(mockLoadModelCatalog).not.toHaveBeenCalled();
|
||||
expect(mockNote).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips when allowAny is true", async () => {
|
||||
const catalog = [{ id: "gpt-5.4", name: "gpt-5.4", provider: "openai" }];
|
||||
mockLoadModelCatalog.mockResolvedValue(catalog);
|
||||
mockBuildAllowedModelSet.mockReturnValue({
|
||||
allowAny: true,
|
||||
allowedCatalog: catalog,
|
||||
allowedKeys: new Set(["openai/gpt-5.4"]),
|
||||
});
|
||||
|
||||
await noteSyntheticAllowlistGaps(makeCfg({ "anthropic/claude-opus-4-6": {} }));
|
||||
expect(mockNote).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
@ -69,7 +83,7 @@ describe("noteSyntheticAllowlistGaps", () => {
|
|||
mockLoadModelCatalog.mockResolvedValue(catalog);
|
||||
mockBuildAllowedModelSet.mockReturnValue({
|
||||
allowAny: false,
|
||||
allowedCatalog: [catalog[0]!, catalog[3]!],
|
||||
allowedCatalog: [catalog[0], catalog[3]],
|
||||
allowedKeys: new Set(["openai-codex/gpt-5.4", "anthropic/claude-opus-4-6"]),
|
||||
});
|
||||
|
||||
|
|
@ -78,7 +92,7 @@ describe("noteSyntheticAllowlistGaps", () => {
|
|||
);
|
||||
|
||||
expect(mockNote).toHaveBeenCalledTimes(1);
|
||||
const noteText = mockNote.mock.calls[0]![0] as string;
|
||||
const noteText = mockNote.mock.calls[0][0];
|
||||
expect(noteText).toContain("2 synthetic model");
|
||||
expect(noteText).toContain("openai/gpt-5.4");
|
||||
expect(noteText).toContain("openai/gpt-5.4-pro");
|
||||
|
|
@ -92,7 +106,7 @@ describe("noteSyntheticAllowlistGaps", () => {
|
|||
mockLoadModelCatalog.mockResolvedValue(catalog);
|
||||
mockBuildAllowedModelSet.mockReturnValue({
|
||||
allowAny: false,
|
||||
allowedCatalog: [catalog[1]!],
|
||||
allowedCatalog: [catalog[1]],
|
||||
allowedKeys: new Set(["anthropic/claude-opus-4-6"]),
|
||||
});
|
||||
|
||||
|
|
@ -110,7 +124,7 @@ describe("noteSyntheticAllowlistGaps", () => {
|
|||
});
|
||||
|
||||
await noteSyntheticAllowlistGaps(makeCfg({ "anthropic/claude-opus-4-6": {} }));
|
||||
const noteText = mockNote.mock.calls[0]![0] as string;
|
||||
const noteText = mockNote.mock.calls[0][0];
|
||||
expect(noteText).toContain("1 synthetic model available");
|
||||
expect(noteText).not.toContain("models available");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,11 +6,16 @@ import type { OpenClawConfig } from "../config/config.js";
|
|||
import { note } from "../terminal/note.js";
|
||||
|
||||
/**
|
||||
* Known providers whose models may appear in the runtime catalog via
|
||||
* `applySyntheticCatalogFallbacks()`. Only these are checked to avoid
|
||||
* false positives from Pi SDK registry models.
|
||||
* The exact provider/id pairs created by `applySyntheticCatalogFallbacks()`.
|
||||
* Only these are checked to avoid false positives from real catalog models
|
||||
* that happen to share the same provider.
|
||||
*/
|
||||
const SYNTHETIC_FALLBACK_PROVIDERS = new Set(["openai", "openai-codex"]);
|
||||
const SYNTHETIC_FALLBACK_KEYS = new Set([
|
||||
"openai/gpt-5.4",
|
||||
"openai/gpt-5.4-pro",
|
||||
"openai-codex/gpt-5.4",
|
||||
"openai-codex/gpt-5.3-codex-spark",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Warn when synthetic catalog fallback models are present in the runtime
|
||||
|
|
@ -28,19 +33,22 @@ export async function noteSyntheticAllowlistGaps(cfg: OpenClawConfig): Promise<v
|
|||
}
|
||||
|
||||
const catalog = await loadModelCatalog({ config: cfg });
|
||||
const { allowedKeys } = buildAllowedModelSet({
|
||||
const { allowAny, allowedKeys } = buildAllowedModelSet({
|
||||
cfg,
|
||||
catalog,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
if (allowAny) {
|
||||
return; // all models are allowed; nothing to warn about
|
||||
}
|
||||
|
||||
const gaps: Array<{ provider: string; id: string }> = [];
|
||||
for (const entry of catalog) {
|
||||
if (!SYNTHETIC_FALLBACK_PROVIDERS.has(entry.provider.toLowerCase())) {
|
||||
const key = modelKey(entry.provider, entry.id);
|
||||
if (!SYNTHETIC_FALLBACK_KEYS.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const key = modelKey(entry.provider, entry.id);
|
||||
if (!allowedKeys.has(key)) {
|
||||
gaps.push(entry);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue