Turbo models (#1249)

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

## Summary by cubic
Adds “Dyad Turbo” models for Pro users and centralizes model/provider
constants. Pro users can pick fast, cost‑effective models directly from
the ModelPicker, with clearer labels and gating.

- **New Features**
- Added Dyad Turbo provider in ModelPicker with Qwen3 Coder and Kimi K2
(Pro only).
- Turbo options are hidden for non‑Pro users; “Pro only” badge shown
where applicable.
- “Smart Auto” label now applies only to the Auto model to avoid
confusion.

- **Refactors**
- Moved all model/provider constants into language_model_constants.ts
and updated imports (helpers, client, thinking utils).

<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Will Chen
2025-09-10 15:59:54 -07:00
committed by GitHub
parent 9bc5e1387a
commit a8e9caf7b0
5 changed files with 497 additions and 447 deletions

View File

@@ -25,6 +25,7 @@ import { LocalModel } from "@/ipc/ipc_types";
import { useLanguageModelProviders } from "@/hooks/useLanguageModelProviders";
import { useSettings } from "@/hooks/useSettings";
import { PriceBadge } from "@/components/PriceBadge";
import { TURBO_MODELS } from "@/ipc/shared/language_model_constants";
export function ModelPicker() {
const { settings, updateSettings } = useSettings();
@@ -108,6 +109,13 @@ export function ModelPicker() {
const autoModels =
!loading && modelsByProviders && modelsByProviders["auto"]
? modelsByProviders["auto"].filter((model) => {
if (
settings &&
!isDyadProEnabled(settings) &&
model.apiName === "turbo"
) {
return false;
}
if (
settings &&
isDyadProEnabled(settings) &&
@@ -144,6 +152,9 @@ export function ModelPicker() {
const provider = providers?.find((p) => p.id === providerId);
return !(provider && provider.secondary);
});
if (settings && isDyadProEnabled(settings)) {
primaryProviders.unshift(["auto", TURBO_MODELS]);
}
const secondaryProviders = providerEntries.filter(([providerId, models]) => {
if (models.length === 0) return false;
const provider = providers?.find((p) => p.id === providerId);
@@ -220,7 +231,7 @@ export function ModelPicker() {
<div className="flex justify-between items-start w-full">
<span className="flex flex-col items-start">
<span>
{isSmartAutoEnabled
{isSmartAutoEnabled && model.apiName === "auto"
? "Smart Auto"
: model.displayName}
</span>
@@ -228,7 +239,7 @@ export function ModelPicker() {
<div className="flex items-center gap-1.5">
{isSmartAutoEnabled && (
<span className="text-[10px] bg-gradient-to-r from-indigo-600 via-indigo-500 to-indigo-600 bg-[length:200%_100%] animate-[shimmer_5s_ease-in-out_infinite] text-white px-1.5 py-0.5 rounded-full font-medium">
Dyad Pro
Pro only
</span>
)}
{model.tag && (
@@ -241,7 +252,7 @@ export function ModelPicker() {
</DropdownMenuItem>
</TooltipTrigger>
<TooltipContent side="right">
{isSmartAutoEnabled ? (
{isSmartAutoEnabled && model.apiName === "auto" ? (
<p>
<strong>Smart Auto</strong> uses a cheaper model for
easier tasks
@@ -274,12 +285,16 @@ export function ModelPicker() {
return true;
});
const provider = providers?.find((p) => p.id === providerId);
const providerDisplayName =
provider?.id === "auto"
? "Dyad Turbo"
: (provider?.name ?? providerId);
return (
<DropdownMenuSub key={providerId}>
<DropdownMenuSubTrigger className="w-full font-normal">
<div className="flex flex-col items-start w-full">
<div className="flex items-center gap-2">
<span>{provider?.name ?? providerId}</span>
<span>{providerDisplayName}</span>
{provider?.type === "cloud" &&
!provider?.secondary &&
isDyadProEnabled(settings) && (
@@ -300,7 +315,7 @@ export function ModelPicker() {
</DropdownMenuSubTrigger>
<DropdownMenuSubContent className="w-56">
<DropdownMenuLabel>
{(provider?.name ?? providerId) + " Models"}
{providerDisplayName + " Models"}
</DropdownMenuLabel>
<DropdownMenuSeparator />
{models.map((model) => (

View File

@@ -0,0 +1,468 @@
import { LanguageModel } from "../ipc_types";
export const PROVIDERS_THAT_SUPPORT_THINKING: (keyof typeof MODEL_OPTIONS)[] = [
"google",
"vertex",
"auto",
];
export interface ModelOption {
name: string;
displayName: string;
description: string;
dollarSigns?: number;
temperature?: number;
tag?: string;
maxOutputTokens?: number;
contextWindow?: number;
}
export const MODEL_OPTIONS: Record<string, ModelOption[]> = {
openai: [
// https://platform.openai.com/docs/models/gpt-5
{
name: "gpt-5",
displayName: "GPT 5",
description: "OpenAI's flagship model",
// Technically it's 128k but OpenAI errors if you set max_tokens instead of max_completion_tokens
maxOutputTokens: undefined,
contextWindow: 400_000,
// Requires temperature to be default value (1)
temperature: 1,
dollarSigns: 3,
},
// https://platform.openai.com/docs/models/gpt-5-mini
{
name: "gpt-5-mini",
displayName: "GPT 5 Mini",
description: "OpenAI's lightweight, but intelligent model",
// Technically it's 128k but OpenAI errors if you set max_tokens instead of max_completion_tokens
maxOutputTokens: undefined,
contextWindow: 400_000,
// Requires temperature to be default value (1)
temperature: 1,
dollarSigns: 2,
},
// https://platform.openai.com/docs/models/gpt-5-nano
{
name: "gpt-5-nano",
displayName: "GPT 5 Nano",
description: "Fastest, most cost-efficient version of GPT-5",
// Technically it's 128k but OpenAI errors if you set max_tokens instead of max_completion_tokens
maxOutputTokens: undefined,
contextWindow: 400_000,
// Requires temperature to be default value (1)
temperature: 1,
dollarSigns: 1,
},
// https://platform.openai.com/docs/models/o4-mini
{
name: "o4-mini",
displayName: "o4 mini",
description: "Reasoning model",
// Technically the max output tokens is 100k, *however* if the user has a lot of input tokens,
// then setting a high max output token will cause the request to fail because
// the max output tokens is *included* in the context window limit.
maxOutputTokens: 32_000,
contextWindow: 200_000,
temperature: 0,
dollarSigns: 2,
},
],
// https://docs.anthropic.com/en/docs/about-claude/models/all-models#model-comparison-table
anthropic: [
{
name: "claude-sonnet-4-20250514",
displayName: "Claude 4 Sonnet",
description: "Excellent coder (note: >200k tokens is very expensive!)",
// See comment below for Claude 3.7 Sonnet for why we set this to 16k
maxOutputTokens: 16_000,
contextWindow: 1_000_000,
temperature: 0,
dollarSigns: 5,
},
{
name: "claude-3-7-sonnet-latest",
displayName: "Claude 3.7 Sonnet",
description: "Excellent coder",
// Technically the max output tokens is 64k, *however* if the user has a lot of input tokens,
// then setting a high max output token will cause the request to fail because
// the max output tokens is *included* in the context window limit, see:
// https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#max-tokens-and-context-window-size-with-extended-thinking
maxOutputTokens: 16_000,
contextWindow: 200_000,
temperature: 0,
dollarSigns: 4,
},
{
name: "claude-3-5-sonnet-20241022",
displayName: "Claude 3.5 Sonnet",
description: "Good coder, excellent at following instructions",
maxOutputTokens: 8_000,
contextWindow: 200_000,
temperature: 0,
dollarSigns: 4,
},
{
name: "claude-3-5-haiku-20241022",
displayName: "Claude 3.5 Haiku",
description: "Lightweight coder",
maxOutputTokens: 8_000,
contextWindow: 200_000,
temperature: 0,
dollarSigns: 2,
},
],
google: [
// https://ai.google.dev/gemini-api/docs/models#gemini-2.5-pro-preview-03-25
{
name: "gemini-2.5-pro",
displayName: "Gemini 2.5 Pro",
description: "Google's Gemini 2.5 Pro model",
// See Flash 2.5 comment below (go 1 below just to be safe, even though it seems OK now).
maxOutputTokens: 65_536 - 1,
// Gemini context window = input token + output token
contextWindow: 1_048_576,
temperature: 0,
dollarSigns: 3,
},
// https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview
{
name: "gemini-2.5-flash",
displayName: "Gemini 2.5 Flash",
description: "Google's Gemini 2.5 Flash model (free tier available)",
// Weirdly for Vertex AI, the output token limit is *exclusive* of the stated limit.
maxOutputTokens: 65_536 - 1,
// Gemini context window = input token + output token
contextWindow: 1_048_576,
temperature: 0,
dollarSigns: 2,
},
],
vertex: [
// Vertex Gemini 2.5 Pro
{
name: "gemini-2.5-pro",
displayName: "Gemini 2.5 Pro",
description: "Vertex Gemini 2.5 Pro",
maxOutputTokens: 65_536 - 1,
contextWindow: 1_048_576,
temperature: 0,
},
// Vertex Gemini 2.5 Flash
{
name: "gemini-2.5-flash",
displayName: "Gemini 2.5 Flash",
description: "Vertex Gemini 2.5 Flash",
maxOutputTokens: 65_536 - 1,
contextWindow: 1_048_576,
temperature: 0,
},
],
openrouter: [
{
name: "qwen/qwen3-coder:free",
displayName: "Qwen3 Coder (free)",
description: "Use for free (data may be used for training)",
maxOutputTokens: 32_000,
contextWindow: 262_000,
temperature: 0,
dollarSigns: 0,
},
// https://openrouter.ai/deepseek/deepseek-chat-v3-0324:free
{
name: "deepseek/deepseek-chat-v3.1:free",
displayName: "DeepSeek v3.1 (free)",
description: "Use for free (data may be used for training)",
maxOutputTokens: 32_000,
contextWindow: 128_000,
temperature: 0,
dollarSigns: 0,
},
{
name: "deepseek/deepseek-chat-v3-0324:free",
displayName: "DeepSeek v3 (free)",
description: "Use for free (data may be used for training)",
maxOutputTokens: 32_000,
contextWindow: 128_000,
temperature: 0,
dollarSigns: 0,
},
{
name: "qwen/qwen3-coder",
displayName: "Qwen3 Coder",
description: "Qwen's best coding model",
maxOutputTokens: 32_000,
contextWindow: 262_000,
temperature: 0,
dollarSigns: 2,
},
{
name: "deepseek/deepseek-chat-v3.1",
displayName: "DeepSeek v3.1",
description: "Strong cost-effective model with optional thinking",
maxOutputTokens: 32_000,
contextWindow: 128_000,
temperature: 0,
dollarSigns: 2,
},
// https://openrouter.ai/moonshotai/kimi-k2
{
name: "moonshotai/kimi-k2-0905",
displayName: "Kimi K2",
description: "Powerful cost-effective model (updated to 0905)",
maxOutputTokens: 32_000,
contextWindow: 256_000,
temperature: 0,
dollarSigns: 2,
},
],
auto: [
{
name: "auto",
displayName: "Auto",
description: "Automatically selects the best model",
tag: "Default",
// These are below Gemini 2.5 Pro & Flash limits
// which are the ones defaulted to for both regular auto
// and smart auto.
maxOutputTokens: 32_000,
contextWindow: 1_000_000,
temperature: 0,
},
{
name: "free",
displayName: "Free (OpenRouter)",
description: "Selects from one of the free OpenRouter models",
tag: "Free",
// These are below Gemini 2.5 Pro & Flash limits
// which are the ones defaulted to for both regular auto
// and smart auto.
maxOutputTokens: 32_000,
contextWindow: 128_000,
temperature: 0,
},
{
name: "turbo",
displayName: "Turbo (Pro)",
description: "Use very fast open-source frontier models",
maxOutputTokens: 32_000,
contextWindow: 256_000,
temperature: 0,
},
],
azure: [
{
name: "gpt-5",
displayName: "GPT-5",
description: "Azure OpenAI GPT-5 model with reasoning capabilities",
maxOutputTokens: 128_000,
contextWindow: 400_000,
temperature: 0,
},
{
name: "gpt-5-mini",
displayName: "GPT-5 Mini",
description: "Azure OpenAI GPT-5 Mini model",
maxOutputTokens: 128_000,
contextWindow: 400_000,
temperature: 0,
},
{
name: "gpt-5-nano",
displayName: "GPT-5 Nano",
description: "Azure OpenAI GPT-5 Nano model",
maxOutputTokens: 128_000,
contextWindow: 400_000,
temperature: 0,
},
{
name: "gpt-5-chat",
displayName: "GPT-5 Chat",
description: "Azure OpenAI GPT-5 Chat model",
maxOutputTokens: 16_384,
contextWindow: 128_000,
temperature: 0,
},
],
xai: [
// https://docs.x.ai/docs/models
{
name: "grok-code-fast-1",
displayName: "Grok Code Fast",
description: "Fast coding model",
maxOutputTokens: 32_000,
contextWindow: 256_000,
temperature: 0,
dollarSigns: 1,
},
{
name: "grok-4",
displayName: "Grok 4",
description: "Most capable coding model",
maxOutputTokens: 32_000,
contextWindow: 256_000,
temperature: 0,
dollarSigns: 4,
},
{
name: "grok-3",
displayName: "Grok 3",
description: "Powerful coding model",
maxOutputTokens: 32_000,
contextWindow: 131_072,
temperature: 0,
dollarSigns: 4,
},
],
bedrock: [
{
name: "us.anthropic.claude-sonnet-4-20250514-v1:0",
displayName: "Claude 4 Sonnet",
description: "Excellent coder (note: >200k tokens is very expensive!)",
maxOutputTokens: 16_000,
contextWindow: 1_000_000,
temperature: 0,
},
{
name: "us.anthropic.claude-3-7-sonnet-20250219-v1:0",
displayName: "Claude 3.7 Sonnet",
description: "Excellent coder",
maxOutputTokens: 16_000,
contextWindow: 200_000,
temperature: 0,
},
{
name: "us.anthropic.claude-3-5-sonnet-20241022-v2:0",
displayName: "Claude 3.5 Sonnet",
description: "Good coder, excellent at following instructions",
maxOutputTokens: 8_000,
contextWindow: 200_000,
temperature: 0,
},
],
};
export const TURBO_MODELS: LanguageModel[] = [
{
apiName: "qwen3-coder:turbo",
displayName: "Qwen3 Coder",
description: "Qwen's best coding model (very fast)",
maxOutputTokens: 32_000,
contextWindow: 131_000,
temperature: 0,
dollarSigns: 2,
type: "cloud",
},
{
apiName: "kimi-k2:turbo",
displayName: "Kimi K2",
description: "Kimi 0905 update (fast)",
maxOutputTokens: 16_000,
contextWindow: 256_000,
temperature: 0,
dollarSigns: 2,
type: "cloud",
},
];
export const FREE_OPENROUTER_MODEL_NAMES = MODEL_OPTIONS.openrouter
.filter((model) => model.name.endsWith(":free"))
.map((model) => model.name);
export const PROVIDER_TO_ENV_VAR: Record<string, string> = {
openai: "OPENAI_API_KEY",
anthropic: "ANTHROPIC_API_KEY",
google: "GEMINI_API_KEY",
openrouter: "OPENROUTER_API_KEY",
azure: "AZURE_API_KEY",
xai: "XAI_API_KEY",
bedrock: "AWS_BEARER_TOKEN_BEDROCK",
};
export const CLOUD_PROVIDERS: Record<
string,
{
displayName: string;
hasFreeTier?: boolean;
websiteUrl?: string;
gatewayPrefix: string;
secondary?: boolean;
}
> = {
openai: {
displayName: "OpenAI",
hasFreeTier: false,
websiteUrl: "https://platform.openai.com/api-keys",
gatewayPrefix: "",
},
anthropic: {
displayName: "Anthropic",
hasFreeTier: false,
websiteUrl: "https://console.anthropic.com/settings/keys",
gatewayPrefix: "anthropic/",
},
google: {
displayName: "Google",
hasFreeTier: true,
websiteUrl: "https://aistudio.google.com/app/apikey",
gatewayPrefix: "gemini/",
},
vertex: {
displayName: "Google Vertex AI",
hasFreeTier: false,
websiteUrl: "https://console.cloud.google.com/vertex-ai",
// Use the same gateway prefix as Google Gemini for Dyad Pro compatibility.
gatewayPrefix: "gemini/",
secondary: true,
},
openrouter: {
displayName: "OpenRouter",
hasFreeTier: true,
websiteUrl: "https://openrouter.ai/settings/keys",
gatewayPrefix: "openrouter/",
},
auto: {
displayName: "Dyad",
websiteUrl: "https://academy.dyad.sh/settings",
gatewayPrefix: "dyad/",
},
azure: {
displayName: "Azure OpenAI",
hasFreeTier: false,
websiteUrl: "https://portal.azure.com/",
gatewayPrefix: "",
secondary: true,
},
xai: {
displayName: "xAI",
hasFreeTier: false,
websiteUrl: "https://console.x.ai/",
gatewayPrefix: "xai/",
secondary: true,
},
bedrock: {
displayName: "AWS Bedrock",
hasFreeTier: false,
websiteUrl: "https://console.aws.amazon.com/bedrock/",
gatewayPrefix: "bedrock/",
secondary: true,
},
};
export const LOCAL_PROVIDERS: Record<
string,
{
displayName: string;
hasFreeTier: boolean;
}
> = {
ollama: {
displayName: "Ollama",
hasFreeTier: true,
},
lmstudio: {
displayName: "LM Studio",
hasFreeTier: true,
},
};

View File

@@ -5,443 +5,12 @@ import {
} from "@/db/schema";
import type { LanguageModelProvider, LanguageModel } from "@/ipc/ipc_types";
import { eq } from "drizzle-orm";
export const PROVIDERS_THAT_SUPPORT_THINKING: (keyof typeof MODEL_OPTIONS)[] = [
"google",
"vertex",
"auto",
];
export interface ModelOption {
name: string;
displayName: string;
description: string;
dollarSigns?: number;
temperature?: number;
tag?: string;
maxOutputTokens?: number;
contextWindow?: number;
}
export const MODEL_OPTIONS: Record<string, ModelOption[]> = {
openai: [
// https://platform.openai.com/docs/models/gpt-5
{
name: "gpt-5",
displayName: "GPT 5",
description: "OpenAI's flagship model",
// Technically it's 128k but OpenAI errors if you set max_tokens instead of max_completion_tokens
maxOutputTokens: undefined,
contextWindow: 400_000,
// Requires temperature to be default value (1)
temperature: 1,
dollarSigns: 3,
},
// https://platform.openai.com/docs/models/gpt-5-mini
{
name: "gpt-5-mini",
displayName: "GPT 5 Mini",
description: "OpenAI's lightweight, but intelligent model",
// Technically it's 128k but OpenAI errors if you set max_tokens instead of max_completion_tokens
maxOutputTokens: undefined,
contextWindow: 400_000,
// Requires temperature to be default value (1)
temperature: 1,
dollarSigns: 2,
},
// https://platform.openai.com/docs/models/gpt-5-nano
{
name: "gpt-5-nano",
displayName: "GPT 5 Nano",
description: "Fastest, most cost-efficient version of GPT-5",
// Technically it's 128k but OpenAI errors if you set max_tokens instead of max_completion_tokens
maxOutputTokens: undefined,
contextWindow: 400_000,
// Requires temperature to be default value (1)
temperature: 1,
dollarSigns: 1,
},
// https://platform.openai.com/docs/models/o4-mini
{
name: "o4-mini",
displayName: "o4 mini",
description: "Reasoning model",
// Technically the max output tokens is 100k, *however* if the user has a lot of input tokens,
// then setting a high max output token will cause the request to fail because
// the max output tokens is *included* in the context window limit.
maxOutputTokens: 32_000,
contextWindow: 200_000,
temperature: 0,
dollarSigns: 2,
},
],
// https://docs.anthropic.com/en/docs/about-claude/models/all-models#model-comparison-table
anthropic: [
{
name: "claude-sonnet-4-20250514",
displayName: "Claude 4 Sonnet",
description: "Excellent coder (note: >200k tokens is very expensive!)",
// See comment below for Claude 3.7 Sonnet for why we set this to 16k
maxOutputTokens: 16_000,
contextWindow: 1_000_000,
temperature: 0,
dollarSigns: 5,
},
{
name: "claude-3-7-sonnet-latest",
displayName: "Claude 3.7 Sonnet",
description: "Excellent coder",
// Technically the max output tokens is 64k, *however* if the user has a lot of input tokens,
// then setting a high max output token will cause the request to fail because
// the max output tokens is *included* in the context window limit, see:
// https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#max-tokens-and-context-window-size-with-extended-thinking
maxOutputTokens: 16_000,
contextWindow: 200_000,
temperature: 0,
dollarSigns: 4,
},
{
name: "claude-3-5-sonnet-20241022",
displayName: "Claude 3.5 Sonnet",
description: "Good coder, excellent at following instructions",
maxOutputTokens: 8_000,
contextWindow: 200_000,
temperature: 0,
dollarSigns: 4,
},
{
name: "claude-3-5-haiku-20241022",
displayName: "Claude 3.5 Haiku",
description: "Lightweight coder",
maxOutputTokens: 8_000,
contextWindow: 200_000,
temperature: 0,
dollarSigns: 2,
},
],
google: [
// https://ai.google.dev/gemini-api/docs/models#gemini-2.5-pro-preview-03-25
{
name: "gemini-2.5-pro",
displayName: "Gemini 2.5 Pro",
description: "Google's Gemini 2.5 Pro model",
// See Flash 2.5 comment below (go 1 below just to be safe, even though it seems OK now).
maxOutputTokens: 65_536 - 1,
// Gemini context window = input token + output token
contextWindow: 1_048_576,
temperature: 0,
dollarSigns: 3,
},
// https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview
{
name: "gemini-2.5-flash",
displayName: "Gemini 2.5 Flash",
description: "Google's Gemini 2.5 Flash model (free tier available)",
// Weirdly for Vertex AI, the output token limit is *exclusive* of the stated limit.
maxOutputTokens: 65_536 - 1,
// Gemini context window = input token + output token
contextWindow: 1_048_576,
temperature: 0,
dollarSigns: 2,
},
],
vertex: [
// Vertex Gemini 2.5 Pro
{
name: "gemini-2.5-pro",
displayName: "Gemini 2.5 Pro",
description: "Vertex Gemini 2.5 Pro",
maxOutputTokens: 65_536 - 1,
contextWindow: 1_048_576,
temperature: 0,
},
// Vertex Gemini 2.5 Flash
{
name: "gemini-2.5-flash",
displayName: "Gemini 2.5 Flash",
description: "Vertex Gemini 2.5 Flash",
maxOutputTokens: 65_536 - 1,
contextWindow: 1_048_576,
temperature: 0,
},
],
openrouter: [
{
name: "qwen/qwen3-coder:free",
displayName: "Qwen3 Coder (free)",
description: "Use for free (data may be used for training)",
maxOutputTokens: 32_000,
contextWindow: 262_000,
temperature: 0,
dollarSigns: 0,
},
// https://openrouter.ai/deepseek/deepseek-chat-v3-0324:free
{
name: "deepseek/deepseek-chat-v3.1:free",
displayName: "DeepSeek v3.1 (free)",
description: "Use for free (data may be used for training)",
maxOutputTokens: 32_000,
contextWindow: 128_000,
temperature: 0,
dollarSigns: 0,
},
{
name: "deepseek/deepseek-chat-v3-0324:free",
displayName: "DeepSeek v3 (free)",
description: "Use for free (data may be used for training)",
maxOutputTokens: 32_000,
contextWindow: 128_000,
temperature: 0,
dollarSigns: 0,
},
{
name: "qwen/qwen3-coder",
displayName: "Qwen3 Coder",
description: "Qwen's best coding model",
maxOutputTokens: 32_000,
contextWindow: 262_000,
temperature: 0,
dollarSigns: 2,
},
{
name: "deepseek/deepseek-chat-v3.1",
displayName: "DeepSeek v3.1",
description: "Strong cost-effective model with optional thinking",
maxOutputTokens: 32_000,
contextWindow: 128_000,
temperature: 0,
dollarSigns: 2,
},
// https://openrouter.ai/moonshotai/kimi-k2
{
name: "moonshotai/kimi-k2-0905",
displayName: "Kimi K2",
description: "Powerful cost-effective model (updated to 0905)",
maxOutputTokens: 32_000,
contextWindow: 256_000,
temperature: 0,
dollarSigns: 2,
},
],
auto: [
{
name: "auto",
displayName: "Auto",
description: "Automatically selects the best model",
tag: "Default",
// These are below Gemini 2.5 Pro & Flash limits
// which are the ones defaulted to for both regular auto
// and smart auto.
maxOutputTokens: 32_000,
contextWindow: 1_000_000,
temperature: 0,
},
{
name: "free",
displayName: "Free (OpenRouter)",
description: "Selects from one of the free OpenRouter models",
tag: "Free",
// These are below Gemini 2.5 Pro & Flash limits
// which are the ones defaulted to for both regular auto
// and smart auto.
maxOutputTokens: 32_000,
contextWindow: 128_000,
temperature: 0,
},
],
azure: [
{
name: "gpt-5",
displayName: "GPT-5",
description: "Azure OpenAI GPT-5 model with reasoning capabilities",
maxOutputTokens: 128_000,
contextWindow: 400_000,
temperature: 0,
},
{
name: "gpt-5-mini",
displayName: "GPT-5 Mini",
description: "Azure OpenAI GPT-5 Mini model",
maxOutputTokens: 128_000,
contextWindow: 400_000,
temperature: 0,
},
{
name: "gpt-5-nano",
displayName: "GPT-5 Nano",
description: "Azure OpenAI GPT-5 Nano model",
maxOutputTokens: 128_000,
contextWindow: 400_000,
temperature: 0,
},
{
name: "gpt-5-chat",
displayName: "GPT-5 Chat",
description: "Azure OpenAI GPT-5 Chat model",
maxOutputTokens: 16_384,
contextWindow: 128_000,
temperature: 0,
},
],
xai: [
// https://docs.x.ai/docs/models
{
name: "grok-code-fast-1",
displayName: "Grok Code Fast",
description: "Fast coding model",
maxOutputTokens: 32_000,
contextWindow: 256_000,
temperature: 0,
dollarSigns: 1,
},
{
name: "grok-4",
displayName: "Grok 4",
description: "Most capable coding model",
maxOutputTokens: 32_000,
contextWindow: 256_000,
temperature: 0,
dollarSigns: 4,
},
{
name: "grok-3",
displayName: "Grok 3",
description: "Powerful coding model",
maxOutputTokens: 32_000,
contextWindow: 131_072,
temperature: 0,
dollarSigns: 4,
},
],
bedrock: [
{
name: "us.anthropic.claude-sonnet-4-20250514-v1:0",
displayName: "Claude 4 Sonnet",
description: "Excellent coder (note: >200k tokens is very expensive!)",
maxOutputTokens: 16_000,
contextWindow: 1_000_000,
temperature: 0,
},
{
name: "us.anthropic.claude-3-7-sonnet-20250219-v1:0",
displayName: "Claude 3.7 Sonnet",
description: "Excellent coder",
maxOutputTokens: 16_000,
contextWindow: 200_000,
temperature: 0,
},
{
name: "us.anthropic.claude-3-5-sonnet-20241022-v2:0",
displayName: "Claude 3.5 Sonnet",
description: "Good coder, excellent at following instructions",
maxOutputTokens: 8_000,
contextWindow: 200_000,
temperature: 0,
},
],
};
export const FREE_OPENROUTER_MODEL_NAMES = MODEL_OPTIONS.openrouter
.filter((model) => model.name.endsWith(":free"))
.map((model) => model.name);
export const PROVIDER_TO_ENV_VAR: Record<string, string> = {
openai: "OPENAI_API_KEY",
anthropic: "ANTHROPIC_API_KEY",
google: "GEMINI_API_KEY",
openrouter: "OPENROUTER_API_KEY",
azure: "AZURE_API_KEY",
xai: "XAI_API_KEY",
bedrock: "AWS_BEARER_TOKEN_BEDROCK",
};
export const CLOUD_PROVIDERS: Record<
string,
{
displayName: string;
hasFreeTier?: boolean;
websiteUrl?: string;
gatewayPrefix: string;
secondary?: boolean;
}
> = {
openai: {
displayName: "OpenAI",
hasFreeTier: false,
websiteUrl: "https://platform.openai.com/api-keys",
gatewayPrefix: "",
},
anthropic: {
displayName: "Anthropic",
hasFreeTier: false,
websiteUrl: "https://console.anthropic.com/settings/keys",
gatewayPrefix: "anthropic/",
},
google: {
displayName: "Google",
hasFreeTier: true,
websiteUrl: "https://aistudio.google.com/app/apikey",
gatewayPrefix: "gemini/",
},
vertex: {
displayName: "Google Vertex AI",
hasFreeTier: false,
websiteUrl: "https://console.cloud.google.com/vertex-ai",
// Use the same gateway prefix as Google Gemini for Dyad Pro compatibility.
gatewayPrefix: "gemini/",
secondary: true,
},
openrouter: {
displayName: "OpenRouter",
hasFreeTier: true,
websiteUrl: "https://openrouter.ai/settings/keys",
gatewayPrefix: "openrouter/",
},
auto: {
displayName: "Dyad",
websiteUrl: "https://academy.dyad.sh/settings",
gatewayPrefix: "dyad/",
},
azure: {
displayName: "Azure OpenAI",
hasFreeTier: false,
websiteUrl: "https://portal.azure.com/",
gatewayPrefix: "",
secondary: true,
},
xai: {
displayName: "xAI",
hasFreeTier: false,
websiteUrl: "https://console.x.ai/",
gatewayPrefix: "xai/",
secondary: true,
},
bedrock: {
displayName: "AWS Bedrock",
hasFreeTier: false,
websiteUrl: "https://console.aws.amazon.com/bedrock/",
gatewayPrefix: "bedrock/",
secondary: true,
},
};
const LOCAL_PROVIDERS: Record<
string,
{
displayName: string;
hasFreeTier: boolean;
}
> = {
ollama: {
displayName: "Ollama",
hasFreeTier: true,
},
lmstudio: {
displayName: "LM Studio",
hasFreeTier: true,
},
};
import {
LOCAL_PROVIDERS,
CLOUD_PROVIDERS,
MODEL_OPTIONS,
PROVIDER_TO_ENV_VAR,
} from "./language_model_constants";
/**
* Fetches language model providers from both the database (custom) and hardcoded constants (cloud),
* merging them with custom providers taking precedence.

View File

@@ -15,10 +15,8 @@ import type {
} from "../../lib/schemas";
import { getEnvVar } from "./read_env";
import log from "electron-log";
import {
FREE_OPENROUTER_MODEL_NAMES,
getLanguageModelProviders,
} from "../shared/language_model_helpers";
import { FREE_OPENROUTER_MODEL_NAMES } from "../shared/language_model_constants";
import { getLanguageModelProviders } from "../shared/language_model_helpers";
import { LanguageModelProvider } from "../ipc_types";
import { createDyadEngine } from "./llm_engine_provider";

View File

@@ -1,4 +1,4 @@
import { PROVIDERS_THAT_SUPPORT_THINKING } from "../shared/language_model_helpers";
import { PROVIDERS_THAT_SUPPORT_THINKING } from "../shared/language_model_constants";
import type { UserSettings } from "../../lib/schemas";
function getThinkingBudgetTokens(