More free models (#1244)

<!-- This is an auto-generated description by cubic. -->

## Summary by cubic
Adds support for free OpenRouter models and a new “Free (OpenRouter)”
auto option that fails over across free models for reliability. Improves
setup flow and UI with provider cards, a “Free” price badge, and an
OpenRouter setup prompt in chat.

- **New Features**
- Added OpenRouter free models: Qwen3 Coder (free), DeepSeek v3 (free),
DeepSeek v3.1 (free), marked with dollarSigns=0 and a “Free” badge.
- New auto model: “Free (OpenRouter)” that uses a fallback client to
cycle through free models with smart retry on transient errors.
- New SetupProviderCard component and updated SetupBanner with dedicated
Google and OpenRouter setup cards.
- Chat shows an OpenRouter setup prompt when “Free (OpenRouter)” is
selected and OpenRouter isn’t configured.
- New PriceBadge component in ModelPicker to display “Free” or price
tier.
- E2E: added setup flow test and option to show the setup screen in
tests.
- Model updates: added DeepSeek v3.1, updated Kimi K2 to kimi-k2-0905,
migrated providers to LanguageModelV2.

<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Will Chen
2025-09-10 14:20:17 -07:00
committed by GitHub
parent 7150082f5a
commit 72acb31d59
11 changed files with 573 additions and 72 deletions

View File

@@ -4,6 +4,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
import { createXai } from "@ai-sdk/xai";
import { createVertex as createGoogleVertex } from "@ai-sdk/google-vertex";
import { azure } from "@ai-sdk/azure";
import { LanguageModelV2 } from "@ai-sdk/provider";
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock";
@@ -14,14 +15,17 @@ import type {
} from "../../lib/schemas";
import { getEnvVar } from "./read_env";
import log from "electron-log";
import { getLanguageModelProviders } from "../shared/language_model_helpers";
import {
FREE_OPENROUTER_MODEL_NAMES,
getLanguageModelProviders,
} from "../shared/language_model_helpers";
import { LanguageModelProvider } from "../ipc_types";
import { createDyadEngine } from "./llm_engine_provider";
import { LM_STUDIO_BASE_URL } from "./lm_studio_utils";
import { LanguageModel } from "ai";
import { createOllamaProvider } from "./ollama_provider";
import { getOllamaApiUrl } from "../handlers/local_model_ollama_handler";
import { createFallback } from "./fallback_ai_model";
const dyadEngineUrl = process.env.DYAD_ENGINE_URL;
const dyadGatewayUrl = process.env.DYAD_GATEWAY_URL;
@@ -31,6 +35,10 @@ const AUTO_MODELS = [
provider: "google",
name: "gemini-2.5-flash",
},
{
provider: "openrouter",
name: "qwen/qwen3-coder:free",
},
{
provider: "anthropic",
name: "claude-sonnet-4-20250514",
@@ -42,7 +50,7 @@ const AUTO_MODELS = [
];
export interface ModelClient {
model: LanguageModel;
model: LanguageModelV2;
builtinProviderId?: string;
}
@@ -142,6 +150,30 @@ export async function getModelClient(
}
// Handle 'auto' provider by trying each model in AUTO_MODELS until one works
if (model.provider === "auto") {
if (model.name === "free") {
const openRouterProvider = allProviders.find(
(p) => p.id === "openrouter",
);
if (!openRouterProvider) {
throw new Error("OpenRouter provider not found");
}
return {
modelClient: {
model: createFallback({
models: FREE_OPENROUTER_MODEL_NAMES.map(
(name: string) =>
getRegularModelClient(
{ provider: "openrouter", name },
settings,
openRouterProvider,
).modelClient.model,
),
}),
builtinProviderId: "openrouter",
},
isEngineEnabled: false,
};
}
for (const autoModel of AUTO_MODELS) {
const providerInfo = allProviders.find(
(p) => p.id === autoModel.provider,