diff --git a/src/agents/model-scan.test.ts b/src/agents/model-scan.test.ts index d69445324..7919a8bf6 100644 --- a/src/agents/model-scan.test.ts +++ b/src/agents/model-scan.test.ts @@ -85,4 +85,40 @@ describe("scanOpenRouterModels", () => { } } }); + + it("avoids double-prefixing openrouter/ in modelRef", async () => { + const fetchImpl = createFetchFixture({ + data: [ + { + id: "openrouter/auto", + name: "OpenRouter Auto", + context_length: 128_000, + max_completion_tokens: 4096, + supported_parameters: ["tools"], + modality: "text", + pricing: { prompt: "0", completion: "0" }, + }, + { + id: "meta-llama/llama-3.3-70b:free", + name: "Llama 3.3 70B", + context_length: 8_192, + supported_parameters: [], + modality: "text", + pricing: { prompt: "0", completion: "0" }, + }, + ], + }); + + const results = await scanOpenRouterModels({ + fetchImpl, + probe: false, + }); + + // IDs are normalized (prefix stripped), but modelRefs are correct + expect(results.map((entry) => entry.id)).toEqual(["auto", "meta-llama/llama-3.3-70b:free"]); + expect(results.map((entry) => entry.modelRef)).toEqual([ + "openrouter/auto", + "openrouter/meta-llama/llama-3.3-70b:free", + ]); + }); }); diff --git a/src/agents/model-scan.ts b/src/agents/model-scan.ts index ba4775372..cb6e58f81 100644 --- a/src/agents/model-scan.ts +++ b/src/agents/model-scan.ts @@ -177,8 +177,13 @@ async function fetchOpenRouterModels(fetchImpl: typeof fetch): Promise { if (!entry || typeof entry !== "object") return null; const obj = entry as Record; - const id = typeof obj.id === "string" ? obj.id.trim() : ""; + let id = typeof obj.id === "string" ? obj.id.trim() : ""; if (!id) return null; + // Normalize: strip "openrouter/" prefix if present to keep internal IDs clean. + // The prefix will be added back when constructing modelRef. + if (id.startsWith("openrouter/")) { + id = id.slice("openrouter/".length); + } const name = typeof obj.name === "string" && obj.name.trim() ? obj.name.trim() : id; const contextLength = diff --git a/src/agents/pi-embedded-runner/model.ts b/src/agents/pi-embedded-runner/model.ts index 15248aeaa..a6090f932 100644 --- a/src/agents/pi-embedded-runner/model.ts +++ b/src/agents/pi-embedded-runner/model.ts @@ -49,7 +49,14 @@ export function resolveModel( const resolvedAgentDir = agentDir ?? resolveClawdbotAgentDir(); const authStorage = discoverAuthStorage(resolvedAgentDir); const modelRegistry = discoverModels(authStorage, resolvedAgentDir); - const model = modelRegistry.find(provider, modelId) as Model | null; + let model = modelRegistry.find(provider, modelId) as Model | null; + + // Fallback: Some models in pi-ai have the provider prefix in their ID (e.g., "openrouter/auto"). + // Our internal representation normalizes this to "auto", but pi-ai expects "openrouter/auto". + if (!model && provider === "openrouter") { + model = modelRegistry.find(provider, `${provider}/${modelId}`) as Model | null; + } + if (!model) { const providers = cfg?.models?.providers ?? {}; const inlineModels = buildInlineProviderModels(providers);