fix: limit /model list output

This commit is contained in:
Peter Steinberger
2026-01-21 10:47:05 +00:00
parent 8479dc97da
commit 4e4f5558fc
3 changed files with 95 additions and 0 deletions

View File

@@ -98,6 +98,7 @@ Docs: https://docs.clawd.bot
- **BREAKING:** Reject invalid/unknown config entries and refuse to start the gateway for safety. Run `clawdbot doctor --fix` to repair, then update plugins (`clawdbot plugins update`) if you use any.
### Fixes
- Models: limit `/model list` chat output to configured models when no allowlist is set.
- Discovery: shorten Bonjour DNS-SD service type to `_clawdbot-gw._tcp` and update discovery clients/docs.
- Diagnostics: export OTLP logs, correct queue depth tracking, and document message-flow telemetry.
- Diagnostics: emit message-flow diagnostics across channels via shared dispatch. (#1244)

View File

@@ -121,6 +121,42 @@ describe("directive behavior", () => {
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
});
});
it("uses configured models when no allowlist is set", async () => {
await withTempHome(async (home) => {
vi.mocked(runEmbeddedPiAgent).mockReset();
vi.mocked(loadModelCatalog).mockResolvedValueOnce([
{ id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" },
{ id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" },
{ id: "grok-4", name: "Grok 4", provider: "xai" },
]);
const storePath = path.join(home, "sessions.json");
const res = await getReplyFromConfig(
{ Body: "/model list", From: "+1222", To: "+1222", CommandAuthorized: true },
{},
{
agents: {
defaults: {
model: {
primary: "anthropic/claude-opus-4-5",
fallbacks: ["openai/gpt-4.1-mini"],
},
imageModel: { primary: "minimax/MiniMax-M2.1" },
workspace: path.join(home, "clawd"),
},
},
session: { store: storePath },
},
);
const text = Array.isArray(res) ? res[0]?.text : res?.text;
expect(text).toContain("anthropic/claude-opus-4-5");
expect(text).toContain("openai/gpt-4.1-mini");
expect(text).toContain("minimax/MiniMax-M2.1");
expect(text).not.toContain("xai/grok-4");
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
});
});
it("merges config allowlist models even when catalog is present", async () => {
await withTempHome(async (home) => {
vi.mocked(runEmbeddedPiAgent).mockReset();

View File

@@ -36,6 +36,56 @@ function buildModelPickerCatalog(params: {
defaultModel: params.defaultModel,
});
const buildConfiguredCatalog = (): ModelPickerCatalogEntry[] => {
const out: ModelPickerCatalogEntry[] = [];
const keys = new Set<string>();
const pushRef = (ref: { provider: string; model: string }, name?: string) => {
const provider = normalizeProviderId(ref.provider);
const id = String(ref.model ?? "").trim();
if (!provider || !id) return;
const key = modelKey(provider, id);
if (keys.has(key)) return;
keys.add(key);
out.push({ provider, id, name: name ?? id });
};
const pushRaw = (raw?: string) => {
const value = String(raw ?? "").trim();
if (!value) return;
const resolved = resolveModelRefFromString({
raw: value,
defaultProvider: params.defaultProvider,
aliasIndex: params.aliasIndex,
});
if (!resolved) return;
pushRef(resolved.ref);
};
pushRef(resolvedDefault);
const modelConfig = params.cfg.agents?.defaults?.model;
const modelFallbacks =
modelConfig && typeof modelConfig === "object" ? (modelConfig.fallbacks ?? []) : [];
for (const fallback of modelFallbacks) {
pushRaw(String(fallback ?? ""));
}
const imageConfig = params.cfg.agents?.defaults?.imageModel;
if (imageConfig && typeof imageConfig === "object") {
pushRaw(imageConfig.primary);
for (const fallback of imageConfig.fallbacks ?? []) {
pushRaw(String(fallback ?? ""));
}
}
for (const raw of Object.keys(params.cfg.agents?.defaults?.models ?? {})) {
pushRaw(raw);
}
return out;
};
const keys = new Set<string>();
const out: ModelPickerCatalogEntry[] = [];
@@ -49,6 +99,14 @@ function buildModelPickerCatalog(params: {
out.push({ provider, id, name: entry.name });
};
const hasAllowlist = Object.keys(params.cfg.agents?.defaults?.models ?? {}).length > 0;
if (!hasAllowlist) {
for (const entry of buildConfiguredCatalog()) {
push(entry);
}
return out;
}
// Prefer catalog entries (when available), but always merge in config-only
// allowlist entries. This keeps custom providers/models visible in /model.
for (const entry of params.allowedModelCatalog) {