fix: limit /model list output
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user