From c6fe3b2b792204273751723955761f6519ca9e3a Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Tue, 5 May 2026 16:50:05 +0200 Subject: [PATCH] fix: restrict visible aggregate providers --- .../core/model-registry-proxy-routing.test.ts | 66 ++++++++++++++++++- .../src/core/model-registry.ts | 6 +- .../extensions/sf/preferences-models.js | 6 +- 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/packages/pi-coding-agent/src/core/model-registry-proxy-routing.test.ts b/packages/pi-coding-agent/src/core/model-registry-proxy-routing.test.ts index a6fb164f6..48cf8e050 100644 --- a/packages/pi-coding-agent/src/core/model-registry-proxy-routing.test.ts +++ b/packages/pi-coding-agent/src/core/model-registry-proxy-routing.test.ts @@ -114,8 +114,46 @@ describe("ModelRegistry.getModelsForProxy — basic", () => { assert.equal(result.length, 2); }); - it("filters paid OpenRouter and OpenCode models while keeping subscribed OpenCode Go models", () => { + it("filters paid OpenRouter and OpenCode models while keeping zero-cost and subscribed models", () => { const registry = createRegistry(() => true); + registry.registerProvider("openrouter", { + authMode: "none", + baseUrl: "https://openrouter.ai/api/v1", + api: "openai-completions", + streamSimple: noopStream, + models: [ + { + id: "qwen/qwen3-coder:free", + name: "Qwen Free", + api: "openai-completions", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 16384, + }, + { + id: "z-ai/glm-5.1", + name: "GLM Paid", + api: "openai-completions", + reasoning: false, + input: ["text"], + cost: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 16384, + }, + { + id: "zero-cost/non-free-slug", + name: "Zero Cost Non-Free Slug", + api: "openai-completions", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128000, + maxTokens: 16384, + }, + ], + }); const available = registry.getAvailable(); assert.ok( @@ -133,8 +171,15 @@ describe("ModelRegistry.getModelsForProxy — basic", () => { assert.ok( available .filter((m) => m.provider === "openrouter") - .every((m) => m.id.endsWith(":free")), - "every available OpenRouter model must use the :free SKU", + .every((m) => m.id.endsWith(":free") || m.cost?.input === 0), + "every available OpenRouter model must use the :free SKU or carry zero-cost metadata", + ); + assert.ok( + available.some( + (m) => + m.provider === "openrouter" && m.id === "zero-cost/non-free-slug", + ), + "OpenRouter models with explicit zero-cost metadata should remain available", ); assert.equal( registry.find("openrouter", "z-ai/glm-5.1"), @@ -228,6 +273,21 @@ describe("ModelRegistry.getModelsForProxy — basic", () => { ); }); + it("hides Groq as a selectable LLM model provider", () => { + const registry = createRegistry(() => true); + const available = registry.getAvailable(); + + assert.ok( + !available.some((m) => m.provider === "groq"), + "Groq should not be listed or selected by SF provider policy", + ); + assert.equal( + registry.find("groq", "llama-3.1-8b-instant"), + undefined, + "direct lookup should also hide Groq models", + ); + }); + it("hides Claude Code because it is not part of the managed provider pool", () => { const registry = createRegistry(() => true); const available = registry.getAvailable(); diff --git a/packages/pi-coding-agent/src/core/model-registry.ts b/packages/pi-coding-agent/src/core/model-registry.ts index 061fb80c2..98d8be914 100644 --- a/packages/pi-coding-agent/src/core/model-registry.ts +++ b/packages/pi-coding-agent/src/core/model-registry.ts @@ -247,6 +247,7 @@ const HIDDEN_MODEL_PROVIDERS = new Set([ "claude-code", "google", "google-vertex", + "groq", "github-copilot", "xai", "xiaomi-token-plan-ams", @@ -318,7 +319,10 @@ function isModelAllowedByBuiltInProviderPolicy( return isMistralSelectionModel(model.id); } if (provider === "openrouter") { - return providerModelAllowEntryMatches(":free", modelKey); + return ( + providerModelAllowEntryMatches(":free", modelKey) || + isZeroCost(model.cost) + ); } if (provider === "opencode") { return ( diff --git a/src/resources/extensions/sf/preferences-models.js b/src/resources/extensions/sf/preferences-models.js index f1735c6de..afe94c815 100644 --- a/src/resources/extensions/sf/preferences-models.js +++ b/src/resources/extensions/sf/preferences-models.js @@ -37,6 +37,7 @@ const HIDDEN_MODEL_PROVIDERS = new Set([ "claude-code", "google", "google-vertex", + "groq", "github-copilot", "xai", "xiaomi-token-plan-ams", @@ -106,7 +107,10 @@ function isModelAllowedByBuiltInProviderPolicy(provider, modelId, model) { return isMistralSelectionModel(modelId); } if (providerKey === "openrouter") { - return providerModelAllowEntryMatches(":free", modelKey); + return ( + providerModelAllowEntryMatches(":free", modelKey) || + isZeroCost(model?.cost) + ); } if (providerKey === "opencode") { return (