diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index e33919649..cb6073a8b 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixed +- Fixed startup model selection to skip unauthenticated saved defaults so configured local custom models can be selected instead ([#6231](https://github.com/earendil-works/pi/issues/6231)). - Fixed Escape aborts to clear runs stuck in extension context hooks that ignore abort signals ([#6234](https://github.com/earendil-works/pi/issues/6234)). - Fixed the question extension example to run question tool calls sequentially so multiple questions in one assistant turn remain answerable ([#6189](https://github.com/earendil-works/pi/issues/6189)). - Fixed `/login` to report auth storage persistence failures instead of claiming credentials were saved when `auth.json` is locked ([#6223](https://github.com/earendil-works/pi/issues/6223)). diff --git a/packages/coding-agent/src/core/model-resolver.ts b/packages/coding-agent/src/core/model-resolver.ts index 35e59d54c..3a3341df0 100644 --- a/packages/coding-agent/src/core/model-resolver.ts +++ b/packages/coding-agent/src/core/model-resolver.ts @@ -596,10 +596,10 @@ export async function findInitialModel(options: { }; } - // 3. Try saved default from settings + // 3. Try saved default from settings if auth is configured. if (defaultProvider && defaultModelId) { const found = modelRegistry.find(defaultProvider, defaultModelId); - if (found) { + if (found && modelRegistry.hasConfiguredAuth(found)) { model = found; if (defaultThinkingLevel) { thinkingLevel = defaultThinkingLevel; diff --git a/packages/coding-agent/test/model-resolver.test.ts b/packages/coding-agent/test/model-resolver.test.ts index b889abb9e..db8bbdb06 100644 --- a/packages/coding-agent/test/model-resolver.test.ts +++ b/packages/coding-agent/test/model-resolver.test.ts @@ -648,4 +648,43 @@ describe("default model selection", () => { expect(result.model?.provider).toBe("vercel-ai-gateway"); expect(result.model?.id).toBe("anthropic/claude-opus-4-6"); }); + + test("findInitialModel ignores an unauthenticated saved default", async () => { + const savedDeepSeekModel: Model<"anthropic-messages"> = { + id: "deepseek-v4-flash", + name: "DeepSeek V4 Flash", + api: "anthropic-messages", + provider: "deepseek", + baseUrl: "https://api.deepseek.com", + reasoning: true, + input: ["text"], + cost: { input: 1, output: 2, cacheRead: 0.1, cacheWrite: 1 }, + contextWindow: 128000, + maxTokens: 8192, + }; + const localDeepSeekModel: Model<"anthropic-messages"> = { + ...savedDeepSeekModel, + provider: "spark-two", + baseUrl: "http://spark-two:8000/v1", + }; + const registry = { + find: (provider: string, modelId: string) => + provider === savedDeepSeekModel.provider && modelId === savedDeepSeekModel.id + ? savedDeepSeekModel + : undefined, + hasConfiguredAuth: (model: Model<"anthropic-messages">) => model.provider === "spark-two", + getAvailable: async () => [localDeepSeekModel], + } as unknown as Parameters[0]["modelRegistry"]; + + const result = await findInitialModel({ + scopedModels: [], + isContinuing: false, + defaultProvider: "deepseek", + defaultModelId: "deepseek-v4-flash", + modelRegistry: registry, + }); + + expect(result.model?.provider).toBe("spark-two"); + expect(result.model?.id).toBe("deepseek-v4-flash"); + }); });