Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion bun.lock
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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=="],
Expand Down
26 changes: 25 additions & 1 deletion docs/models.mdx
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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-...",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions src/browser/assets/icons/deepseek.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 16 additions & 6 deletions src/browser/components/ProviderIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Record<ProviderName, React.FC>> = {
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<string>(["mux-gateway"]);

export interface ProviderIconProps {
provider: string;
className?: string;
Expand All @@ -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 (
<span
Expand Down
184 changes: 91 additions & 93 deletions src/common/constants/providers.ts
Original file line number Diff line number Diff line change
@@ -1,114 +1,112 @@
/**
* Typed import helpers for provider packages
* Provider Definitions - Single source of truth for all provider metadata
*
* These functions provide type-safe dynamic imports for provider packages.
* TypeScript can infer the correct module type from literal string imports,
* giving consuming code full type safety for provider constructors.
*/

/**
* Dynamically import the Anthropic provider package
*/
export function importAnthropic() {
return import("@ai-sdk/anthropic");
}

/**
* Dynamically import the OpenAI provider package
*/
export function importOpenAI() {
return import("@ai-sdk/openai");
}

/**
* Dynamically import the Ollama provider package
*/
export function importOllama() {
return import("ollama-ai-provider-v2");
}

/**
* Dynamically import the Google provider package
*/
export function importGoogle() {
return import("@ai-sdk/google");
}

/**
* Dynamically import the OpenRouter provider package
*/
export function importOpenRouter() {
return import("@openrouter/ai-sdk-provider");
}

/**
* Dynamically import the xAI provider package
*/
export function importXAI() {
return import("@ai-sdk/xai");
}

/**
* Dynamically import the Amazon Bedrock provider package
* When adding a new provider:
* 1. Add entry to PROVIDER_DEFINITIONS below
* 2. Add SVG icon + import in src/browser/components/ProviderIcon.tsx
* 3. If provider needs custom logic, add handler in aiService.ts
* (simple providers using standard pattern are handled automatically)
*
* Simple providers (requiresApiKey + standard factory pattern) need NO aiService.ts changes.
*/
export function importBedrock() {
return import("@ai-sdk/amazon-bedrock");
}

/**
* Dynamically import the Gateway provider from the AI SDK
*/
export function importMuxGateway() {
return import("ai");
interface ProviderDefinition {
/** Display name for UI (proper casing) */
displayName: string;
/** Dynamic import function for lazy loading */
import: () => Promise<unknown>;
/** 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<string, ProviderDefinition>;

/**
* 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<ProviderName, string> = Object.fromEntries(
Object.entries(PROVIDER_DEFINITIONS).map(([key, def]) => [key, def.displayName])
) as Record<ProviderName, string>;

/**
* Legacy registry for backward compatibility with aiService.ts
* Maps provider names to their import functions
*/
export const PROVIDER_DISPLAY_NAMES: Record<ProviderName, string> = {
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
Expand Down
Loading