diff --git a/bun.lock b/bun.lock
index c319e107f8..2422a9d109 100644
--- a/bun.lock
+++ b/bun.lock
@@ -1,12 +1,12 @@
{
"lockfileVersion": 1,
- "configVersion": 0,
"workspaces": {
"": {
"name": "mux",
"dependencies": {
"@ai-sdk/amazon-bedrock": "^3.0.61",
"@ai-sdk/anthropic": "^2.0.47",
+ "@ai-sdk/deepseek": "^1.0.31",
"@ai-sdk/google": "^2.0.43",
"@ai-sdk/mcp": "^0.0.11",
"@ai-sdk/openai": "^2.0.72",
@@ -172,6 +172,8 @@
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.53", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ih7NV+OFSNWZCF+tYYD7ovvvM+gv7TRKQblpVohg2ipIwC9Y0TirzocJVREzZa/v9luxUwFbsPji++DUDWWxsg=="],
+ "@ai-sdk/deepseek": ["@ai-sdk/deepseek@1.0.31", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Il7WJp8bA3CmlreYSl1YzCucGTn2e5P81IANYIIEeLtWrbK0Y9CLoOCROj8xKYyUSMKlINyGZX2uP79cKewtSg=="],
+
"@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.18", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-sDQcW+6ck2m0pTIHW6BPHD7S125WD3qNkx/B8sEzJp/hurocmJ5Cni0ybExg6sQMGo+fr/GWOwpHF1cmCdg5rQ=="],
"@ai-sdk/google": ["@ai-sdk/google@2.0.44", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-c5dck36FjqiVoeeMJQLTEmUheoURcGTU/nBT6iJu8/nZiKFT/y8pD85KMDRB7RerRYaaQOtslR2d6/5PditiRw=="],
diff --git a/docs/models.mdx b/docs/models.mdx
index e5d9ded20f..b4bd35c137 100644
--- a/docs/models.mdx
+++ b/docs/models.mdx
@@ -1,6 +1,6 @@
---
title: Models
-description: Configure AI providers including Anthropic, OpenAI, Google, xAI, and more
+description: Configure AI providers including Anthropic, OpenAI, Google, xAI, DeepSeek, and more
---
See also:
@@ -121,6 +121,26 @@ Frontier reasoning models from xAI with built-in search orchestration:
Mux enables Grok's live search by default using `mode: "auto"` with citations. Add [`searchParameters`](https://docs.x.ai/docs/resources/search) to `providers.jsonc` if you want to customize the defaults (e.g., regional focus, time filters, or disabling search entirely per workspace).
+#### DeepSeek (Cloud)
+
+Access DeepSeek's reasoning and chat models:
+
+- `deepseek:deepseek-chat` — Fast, cost-effective chat model
+- `deepseek:deepseek-reasoner` — Advanced reasoning model with extended thinking
+
+**Setup:**
+
+1. Get your API key from [platform.deepseek.com](https://platform.deepseek.com/)
+2. Add to `~/.mux/providers.jsonc`:
+
+```jsonc
+{
+ "deepseek": {
+ "apiKey": "sk-...",
+ },
+}
+```
+
#### OpenRouter (Cloud)
Access 300+ models from multiple providers through a single API:
@@ -322,6 +342,10 @@ All providers are configured in `~/.mux/providers.jsonc`. Example configurations
"xai": {
"apiKey": "sk-xai-...",
},
+ // Required for DeepSeek models
+ "deepseek": {
+ "apiKey": "sk-...",
+ },
// Required for OpenRouter models
"openrouter": {
"apiKey": "sk-or-v1-...",
diff --git a/package.json b/package.json
index 1a1f6fcb0e..c6af4e1015 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,7 @@
"dependencies": {
"@ai-sdk/amazon-bedrock": "^3.0.61",
"@ai-sdk/anthropic": "^2.0.47",
+ "@ai-sdk/deepseek": "^1.0.31",
"@ai-sdk/google": "^2.0.43",
"@ai-sdk/mcp": "^0.0.11",
"@ai-sdk/openai": "^2.0.72",
diff --git a/src/browser/assets/icons/deepseek.svg b/src/browser/assets/icons/deepseek.svg
new file mode 100644
index 0000000000..3eb6db4a5f
--- /dev/null
+++ b/src/browser/assets/icons/deepseek.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/src/browser/components/ProviderIcon.tsx b/src/browser/components/ProviderIcon.tsx
index 1ed6999e81..c34f991f21 100644
--- a/src/browser/components/ProviderIcon.tsx
+++ b/src/browser/components/ProviderIcon.tsx
@@ -3,23 +3,30 @@ import AnthropicIcon from "@/browser/assets/icons/anthropic.svg?react";
import OpenAIIcon from "@/browser/assets/icons/openai.svg?react";
import GoogleIcon from "@/browser/assets/icons/google.svg?react";
import XAIIcon from "@/browser/assets/icons/xai.svg?react";
+import DeepSeekIcon from "@/browser/assets/icons/deepseek.svg?react";
import AWSIcon from "@/browser/assets/icons/aws.svg?react";
import { GatewayIcon } from "@/browser/components/icons/GatewayIcon";
-import { PROVIDER_DISPLAY_NAMES, type ProviderName } from "@/common/constants/providers";
+import {
+ PROVIDER_DEFINITIONS,
+ PROVIDER_DISPLAY_NAMES,
+ type ProviderName,
+} from "@/common/constants/providers";
import { cn } from "@/common/lib/utils";
+/**
+ * Provider icons mapped by provider name.
+ * When adding a new provider, add its icon import above and entry here.
+ */
const PROVIDER_ICONS: Partial> = {
anthropic: AnthropicIcon,
openai: OpenAIIcon,
google: GoogleIcon,
xai: XAIIcon,
+ deepseek: DeepSeekIcon,
bedrock: AWSIcon,
"mux-gateway": GatewayIcon,
};
-// Icons that use stroke instead of fill for their styling
-const STROKE_BASED_ICONS = new Set(["mux-gateway"]);
-
export interface ProviderIconProps {
provider: string;
className?: string;
@@ -30,10 +37,13 @@ export interface ProviderIconProps {
* Icons are sized to 1em by default to match surrounding text.
*/
export function ProviderIcon(props: ProviderIconProps) {
- const IconComponent = PROVIDER_ICONS[props.provider as keyof typeof PROVIDER_ICONS];
+ const providerName = props.provider as ProviderName;
+ const IconComponent = PROVIDER_ICONS[providerName];
if (!IconComponent) return null;
- const isStrokeBased = STROKE_BASED_ICONS.has(props.provider);
+ // Check if this provider uses stroke-based icon styling (from PROVIDER_DEFINITIONS)
+ const def = PROVIDER_DEFINITIONS[providerName] as { strokeBasedIcon?: boolean } | undefined;
+ const isStrokeBased = def?.strokeBasedIcon ?? false;
return (
Promise;
+ /** Name of the factory function exported by the package */
+ factoryName: string;
+ /** Whether provider requires an API key (false for local services like Ollama) */
+ requiresApiKey: boolean;
+ /** Whether this provider uses stroke-based icon styling instead of fill */
+ strokeBasedIcon?: boolean;
}
-/**
- * Centralized provider registry mapping provider names to their import functions
- *
- * This is the single source of truth for supported providers. By mapping to import
- * functions rather than package strings, we eliminate duplication while maintaining
- * perfect type safety.
- *
- * When adding a new provider:
- * 1. Create an importXxx() function above
- * 2. Add entry mapping provider name to the import function
- * 3. Implement provider handling in aiService.ts createModel()
- * 4. Runtime check will fail if provider in registry but no handler
- */
-export const PROVIDER_REGISTRY = {
- anthropic: importAnthropic,
- openai: importOpenAI,
- google: importGoogle,
- xai: importXAI,
- ollama: importOllama,
- openrouter: importOpenRouter,
- bedrock: importBedrock,
- "mux-gateway": importMuxGateway,
-} as const;
+// Order determines display order in UI (Settings, model selectors, etc.)
+export const PROVIDER_DEFINITIONS = {
+ "mux-gateway": {
+ displayName: "Mux Gateway",
+ import: () => import("ai"),
+ factoryName: "createGateway",
+ requiresApiKey: true, // Uses couponCode
+ strokeBasedIcon: true,
+ },
+ anthropic: {
+ displayName: "Anthropic",
+ import: () => import("@ai-sdk/anthropic"),
+ factoryName: "createAnthropic",
+ requiresApiKey: true,
+ },
+ openai: {
+ displayName: "OpenAI",
+ import: () => import("@ai-sdk/openai"),
+ factoryName: "createOpenAI",
+ requiresApiKey: true,
+ },
+ google: {
+ displayName: "Google",
+ import: () => import("@ai-sdk/google"),
+ factoryName: "createGoogleGenerativeAI",
+ requiresApiKey: true,
+ },
+ xai: {
+ displayName: "xAI",
+ import: () => import("@ai-sdk/xai"),
+ factoryName: "createXai",
+ requiresApiKey: true,
+ },
+ deepseek: {
+ displayName: "DeepSeek",
+ import: () => import("@ai-sdk/deepseek"),
+ factoryName: "createDeepSeek",
+ requiresApiKey: true,
+ },
+ openrouter: {
+ displayName: "OpenRouter",
+ import: () => import("@openrouter/ai-sdk-provider"),
+ factoryName: "createOpenRouter",
+ requiresApiKey: true,
+ },
+ bedrock: {
+ displayName: "Amazon Bedrock",
+ import: () => import("@ai-sdk/amazon-bedrock"),
+ factoryName: "createAmazonBedrock",
+ requiresApiKey: false, // Uses AWS credential chain
+ },
+ ollama: {
+ displayName: "Ollama",
+ import: () => import("ollama-ai-provider-v2"),
+ factoryName: "createOllama",
+ requiresApiKey: false, // Local service
+ },
+} as const satisfies Record;
/**
* Union type of all supported provider names
*/
-export type ProviderName = keyof typeof PROVIDER_REGISTRY;
+export type ProviderName = keyof typeof PROVIDER_DEFINITIONS;
/**
* Array of all supported provider names (for UI lists, iteration, etc.)
*/
-export const SUPPORTED_PROVIDERS = Object.keys(PROVIDER_REGISTRY) as ProviderName[];
+export const SUPPORTED_PROVIDERS = Object.keys(PROVIDER_DEFINITIONS) as ProviderName[];
/**
* Display names for providers (proper casing for UI)
+ * Derived from PROVIDER_DEFINITIONS - do not edit directly
+ */
+export const PROVIDER_DISPLAY_NAMES: Record = Object.fromEntries(
+ Object.entries(PROVIDER_DEFINITIONS).map(([key, def]) => [key, def.displayName])
+) as Record;
+
+/**
+ * Legacy registry for backward compatibility with aiService.ts
+ * Maps provider names to their import functions
*/
-export const PROVIDER_DISPLAY_NAMES: Record = {
- anthropic: "Anthropic",
- openai: "OpenAI",
- google: "Google",
- xai: "xAI",
- ollama: "Ollama",
- openrouter: "OpenRouter",
- bedrock: "Amazon Bedrock",
- "mux-gateway": "Mux Gateway",
-};
+export const PROVIDER_REGISTRY = Object.fromEntries(
+ Object.entries(PROVIDER_DEFINITIONS).map(([key, def]) => [key, def.import])
+) as { [K in ProviderName]: (typeof PROVIDER_DEFINITIONS)[K]["import"] };
/**
* Type guard to check if a string is a valid provider name
diff --git a/src/node/services/aiService.ts b/src/node/services/aiService.ts
index b48789a0cf..297d5e8847 100644
--- a/src/node/services/aiService.ts
+++ b/src/node/services/aiService.ts
@@ -9,7 +9,11 @@ import { sanitizeToolInputs } from "@/browser/utils/messages/sanitizeToolInput";
import type { Result } from "@/common/types/result";
import { Ok, Err } from "@/common/types/result";
import type { WorkspaceMetadata } from "@/common/types/workspace";
-import { PROVIDER_REGISTRY } from "@/common/constants/providers";
+import {
+ PROVIDER_REGISTRY,
+ PROVIDER_DEFINITIONS,
+ type ProviderName,
+} from "@/common/constants/providers";
import type { MuxMessage, MuxTextPart } from "@/common/types/message";
import { createMuxMessage } from "@/common/types/message";
@@ -558,24 +562,6 @@ export class AIService extends EventEmitter {
return Ok(model);
}
- // Handle Google provider
- if (providerName === "google") {
- if (!providerConfig.apiKey) {
- return Err({
- type: "api_key_not_found",
- provider: providerName,
- });
- }
-
- // Lazy-load Google provider to reduce startup time
- const { createGoogleGenerativeAI } = await PROVIDER_REGISTRY.google();
- const provider = createGoogleGenerativeAI({
- ...providerConfig,
- fetch: getProviderFetch(providerConfig),
- });
- return Ok(provider(modelId));
- }
-
// Handle xAI provider
if (providerName === "xai") {
if (!providerConfig.apiKey) {
@@ -791,6 +777,40 @@ export class AIService extends EventEmitter {
return Ok(gateway(modelId));
}
+ // Generic handler for simple providers (standard API key + factory pattern)
+ // Providers with custom logic (anthropic, openai, xai, ollama, openrouter, bedrock, mux-gateway)
+ // are handled explicitly above. New providers using the standard pattern need only be
+ // added to PROVIDER_DEFINITIONS - no code changes required here.
+ const providerDef = PROVIDER_DEFINITIONS[providerName as ProviderName];
+ if (providerDef) {
+ // Check API key requirement
+ if (providerDef.requiresApiKey && !providerConfig.apiKey) {
+ return Err({
+ type: "api_key_not_found",
+ provider: providerName,
+ });
+ }
+
+ // Lazy-load and create provider using factoryName from definition
+ const providerModule = (await providerDef.import()) as unknown as Record<
+ string,
+ (config: Record) => (modelId: string) => LanguageModel
+ >;
+ const factory = providerModule[providerDef.factoryName];
+ if (!factory) {
+ return Err({
+ type: "provider_not_supported",
+ provider: providerName,
+ });
+ }
+
+ const provider = factory({
+ ...providerConfig,
+ fetch: getProviderFetch(providerConfig),
+ });
+ return Ok(provider(modelId));
+ }
+
return Err({
type: "provider_not_supported",
provider: providerName,