





























@@ -241,7 +241,7 @@ describe("handleModelsCommand", () => {
241241expect(result?.reply?.text).not.toContain("- anthropic");
242242});
243243244-it("hides legacy runtime providers from /models provider lists", async () => {
244+it("hides bare backwards-compat aliases but surfaces CLI runtime providers in /models lists", async () => {
245245modelCatalogMocks.loadModelCatalog.mockResolvedValueOnce([
246246{ provider: "codex", id: "gpt-5.5", name: "GPT-5.5" },
247247{ provider: "codex-cli", id: "gpt-5.5", name: "GPT-5.5" },
@@ -251,6 +251,14 @@ describe("handleModelsCommand", () => {
251251{ provider: "google", id: "gemini-3.1-pro-preview", name: "Gemini Pro" },
252252{ provider: "openai", id: "gpt-5.5", name: "GPT-5.5" },
253253]);
254+modelProviderAuthMocks.authenticatedProviders = new Set([
255+"anthropic",
256+"google",
257+"openai",
258+"claude-cli",
259+"codex-cli",
260+"google-gemini-cli",
261+]);
254262255263const result = await handleModelsCommand(
256264buildParams("/models", {
@@ -262,10 +270,115 @@ describe("handleModelsCommand", () => {
262270expect(result?.reply?.text).toContain("- anthropic (1)");
263271expect(result?.reply?.text).toContain("- google (1)");
264272expect(result?.reply?.text).toContain("- openai (1)");
265-expect(result?.reply?.text).not.toContain("- codex");
266-expect(result?.reply?.text).not.toContain("- codex-cli");
267-expect(result?.reply?.text).not.toContain("- claude-cli");
268-expect(result?.reply?.text).not.toContain("- google-gemini-cli");
273+expect(result?.reply?.text).toContain("- claude-cli (1)");
274+expect(result?.reply?.text).toContain("- codex-cli (1)");
275+expect(result?.reply?.text).toContain("- google-gemini-cli (1)");
276+expect(result?.reply?.text).not.toMatch(/^- codex \(/m);
277+});
278+279+it("sources CLI runtime provider model lists from the catalog, not user agents.defaults.models", async () => {
280+modelCatalogMocks.loadModelCatalog.mockResolvedValue([
281+{ provider: "claude-cli", id: "claude-opus-4-7", name: "Claude Opus 4.7" },
282+{ provider: "claude-cli", id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
283+{ provider: "claude-cli", id: "claude-opus-4-6", name: "Claude Opus 4.6" },
284+{ provider: "claude-cli", id: "claude-opus-4-5", name: "Claude Opus 4.5" },
285+{ provider: "claude-cli", id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5" },
286+{ provider: "claude-cli", id: "claude-haiku-4-5", name: "Claude Haiku 4.5" },
287+{ provider: "anthropic", id: "claude-opus-4-7", name: "Claude Opus 4.7" },
288+// A non-CLI configured provider — its narrowing IS respected.
289+{ provider: "minimax", id: "abab-7", name: "Abab 7" },
290+{ provider: "minimax", id: "abab-6.5", name: "Abab 6.5" },
291+]);
292+modelProviderAuthMocks.authenticatedProviders = new Set(["anthropic", "claude-cli", "minimax"]);
293+294+const result = await handleModelsCommand(
295+buildParams("/models claude-cli", {
296+agents: {
297+defaults: {
298+model: { primary: "anthropic/claude-opus-4-7" },
299+// User only declared 2 of claude-cli's 6 supported models, plus 1
300+// of minimax's 2. For claude-cli this narrowing must be ignored;
301+// for minimax it must still gate.
302+models: {
303+"claude-cli/claude-opus-4-6": {},
304+"claude-cli/claude-sonnet-4-6": {},
305+"minimax/abab-7": {},
306+},
307+},
308+},
309+}),
310+true,
311+);
312+313+expect(result?.reply?.text).toContain("- claude-cli/claude-opus-4-7");
314+expect(result?.reply?.text).toContain("- claude-cli/claude-sonnet-4-6");
315+expect(result?.reply?.text).toContain("- claude-cli/claude-opus-4-6");
316+expect(result?.reply?.text).toContain("- claude-cli/claude-opus-4-5");
317+expect(result?.reply?.text).toContain("- claude-cli/claude-sonnet-4-5");
318+expect(result?.reply?.text).toContain("- claude-cli/claude-haiku-4-5");
319+expect(result?.reply?.text).toContain("of 6");
320+321+// For non-CLI configured providers (e.g. Minimax / LM Studio / custom
322+// OpenAI-compatible endpoints), user config is still the source of truth.
323+const minimaxResult = await handleModelsCommand(
324+buildParams("/models minimax", {
325+agents: {
326+defaults: {
327+model: { primary: "anthropic/claude-opus-4-7" },
328+models: {
329+"claude-cli/claude-opus-4-6": {},
330+"minimax/abab-7": {},
331+},
332+},
333+},
334+}),
335+true,
336+);
337+expect(minimaxResult?.reply?.text).toContain("- minimax/abab-7");
338+expect(minimaxResult?.reply?.text).not.toContain("- minimax/abab-6.5");
339+});
340+341+it("does not synthesize claude-cli models when the catalog has no claude-cli entries", async () => {
342+modelCatalogMocks.loadModelCatalog.mockResolvedValue([
343+{ provider: "anthropic", id: "claude-opus-4-7", name: "Claude Opus 4.7" },
344+]);
345+modelProviderAuthMocks.authenticatedProviders = new Set(["anthropic", "claude-cli"]);
346+347+const result = await handleModelsCommand(
348+buildParams("/models claude-cli", {
349+agents: {
350+defaults: {
351+model: { primary: "anthropic/claude-opus-4-7" },
352+},
353+},
354+}),
355+true,
356+);
357+358+expect(result?.reply?.text).not.toMatch(/^- claude-cli\//m);
359+});
360+361+it("hides CLI runtime providers from the picker when the user has no CLI auth", async () => {
362+modelCatalogMocks.loadModelCatalog.mockResolvedValue([
363+{ provider: "anthropic", id: "claude-opus-4-7", name: "Claude Opus 4.7" },
364+{ provider: "claude-cli", id: "claude-opus-4-7", name: "Claude Opus 4.7 (CLI)" },
365+{ provider: "codex-cli", id: "gpt-5.5", name: "GPT-5.5 (CLI)" },
366+{ provider: "google-gemini-cli", id: "gemini-2.5-pro", name: "Gemini 2.5 Pro (CLI)" },
367+]);
368+// Default mock state: only anthropic / google / openai authenticated — no CLI providers.
369+modelProviderAuthMocks.authenticatedProviders = new Set(["anthropic"]);
370+371+const result = await handleModelsCommand(
372+buildParams("/models", {
373+agents: { defaults: { model: { primary: "anthropic/claude-opus-4-7" } } },
374+}),
375+true,
376+);
377+378+expect(result?.reply?.text).toContain("- anthropic (");
379+expect(result?.reply?.text).not.toMatch(/^- claude-cli \(/m);
380+expect(result?.reply?.text).not.toMatch(/^- codex-cli \(/m);
381+expect(result?.reply?.text).not.toMatch(/^- google-gemini-cli \(/m);
269382});
270383271384it("labels the default runtime choice as OpenClaw Pi", async () => {
@@ -285,6 +398,20 @@ describe("handleModelsCommand", () => {
285398});
286399287400it("keeps the telegram provider picker browse-only", async () => {
401+modelCatalogMocks.loadModelCatalog.mockResolvedValue([
402+{ provider: "anthropic", id: "claude-opus-4-5", name: "Claude Opus" },
403+{ provider: "anthropic", id: "claude-sonnet-4-5", name: "Claude Sonnet" },
404+{ provider: "claude-cli", id: "claude-opus-4-7", name: "Claude Opus (CLI)" },
405+{ provider: "openai", id: "gpt-4.1", name: "GPT-4.1" },
406+{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 Mini" },
407+{ provider: "google", id: "gemini-2.0-flash", name: "Gemini Flash" },
408+]);
409+modelProviderAuthMocks.authenticatedProviders = new Set([
410+"anthropic",
411+"claude-cli",
412+"google",
413+"openai",
414+]);
288415const params = buildParams("/models");
289416params.ctx.Surface = "telegram";
290417params.command.channel = "telegram";
@@ -297,6 +424,7 @@ describe("handleModelsCommand", () => {
297424telegram: {
298425buttons: [
299426[{ text: "anthropic", callback_data: "models:anthropic" }],
427+[{ text: "claude-cli", callback_data: "models:claude-cli" }],
300428[{ text: "google", callback_data: "models:google" }],
301429[{ text: "openai", callback_data: "models:openai" }],
302430],
@@ -305,6 +433,20 @@ describe("handleModelsCommand", () => {
305433});
306434307435it("keeps plugin menu hook compatibility for provider pickers", async () => {
436+modelCatalogMocks.loadModelCatalog.mockResolvedValue([
437+{ provider: "anthropic", id: "claude-opus-4-5", name: "Claude Opus" },
438+{ provider: "anthropic", id: "claude-sonnet-4-5", name: "Claude Sonnet" },
439+{ provider: "claude-cli", id: "claude-opus-4-7", name: "Claude Opus (CLI)" },
440+{ provider: "openai", id: "gpt-4.1", name: "GPT-4.1" },
441+{ provider: "openai", id: "gpt-4.1-mini", name: "GPT-4.1 Mini" },
442+{ provider: "google", id: "gemini-2.0-flash", name: "Gemini Flash" },
443+]);
444+modelProviderAuthMocks.authenticatedProviders = new Set([
445+"anthropic",
446+"claude-cli",
447+"google",
448+"openai",
449+]);
308450const params = buildParams("/models");
309451params.ctx.Surface = "menuonly";
310452params.command.channel = "menuonly";
@@ -315,8 +457,8 @@ describe("handleModelsCommand", () => {
315457expect(result?.reply?.text).toBe("Select a provider:");
316458expect(result?.reply?.channelData).toEqual({
317459menuonly: {
318-providerIds: ["anthropic", "google", "openai"],
319-labels: ["anthropic:2", "google:1", "openai:2"],
460+providerIds: ["anthropic", "claude-cli", "google", "openai"],
461+labels: ["anthropic:2", "claude-cli:1", "google:1", "openai:2"],
320462},
321463});
322464});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。