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
14 changes: 14 additions & 0 deletions src/browser/components/Settings/sections/ProvidersSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ function getProviderFields(provider: ProviderName): FieldConfig[] {
];
}

// Mux Gateway only needs couponCode
if (provider === "mux-gateway") {
return [
{ key: "couponCode", label: "Coupon Code", placeholder: "Enter coupon code", type: "secret" },
];
}

// Default for most providers
return [
{ key: "apiKey", label: "API Key", placeholder: "Enter API key", type: "secret" },
Expand Down Expand Up @@ -138,6 +145,11 @@ export function ProvidersSection() {
);
}

// For Mux Gateway, check couponCodeSet
if (provider === "mux-gateway") {
return providerConfig.couponCodeSet ?? false;
}

// For other providers, check apiKeySet
return providerConfig.apiKeySet ?? false;
};
Expand All @@ -153,6 +165,8 @@ export function ProvidersSection() {
if (fieldConfig.type === "secret") {
// For apiKey, we have apiKeySet from the sanitized config
if (field === "apiKey") return config[provider]?.apiKeySet ?? false;
// For couponCode (mux-gateway), check couponCodeSet
if (field === "couponCode") return config[provider]?.couponCodeSet ?? false;
// For other secrets, check if the field exists in the raw config
// Since we don't expose secret values, we assume they're not set if undefined
const providerConfig = config[provider] as Record<string, unknown> | undefined;
Expand Down
2 changes: 2 additions & 0 deletions src/browser/components/Settings/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export interface ProviderConfigDisplay {
bearerTokenSet?: boolean;
accessKeyIdSet?: boolean;
secretAccessKeySet?: boolean;
// Mux Gateway-specific fields
couponCodeSet?: boolean;
// Allow additional fields for extensibility
[key: string]: unknown;
}
Expand Down
9 changes: 9 additions & 0 deletions src/common/constants/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ export async function importBedrock() {
return import("@ai-sdk/amazon-bedrock");
}

/**
* Dynamically import the Gateway provider from the AI SDK
*/
export async function importMuxGateway() {
return import("ai");
}

/**
* Centralized provider registry mapping provider names to their import functions
*
Expand All @@ -76,6 +83,7 @@ export const PROVIDER_REGISTRY = {
ollama: importOllama,
openrouter: importOpenRouter,
bedrock: importBedrock,
"mux-gateway": importMuxGateway,
} as const;

/**
Expand All @@ -99,6 +107,7 @@ export const PROVIDER_DISPLAY_NAMES: Record<ProviderName, string> = {
ollama: "Ollama",
openrouter: "OpenRouter",
bedrock: "Amazon Bedrock",
"mux-gateway": "Mux Gateway",
};

/**
Expand Down
19 changes: 19 additions & 0 deletions src/node/services/aiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,25 @@ export class AIService extends EventEmitter {
return Ok(provider(modelId));
}

// Handle Mux Gateway provider
if (providerName === "mux-gateway") {
// Mux Gateway uses couponCode as the API key
const couponCode = providerConfig.couponCode;
if (typeof couponCode !== "string" || !couponCode) {
return Err({
type: "api_key_not_found",
provider: providerName,
});
}

const { createGateway } = await PROVIDER_REGISTRY["mux-gateway"]();
const gateway = createGateway({
apiKey: couponCode,
baseURL: "https://gateway.mux.coder.com/api/v1/ai-gateway/v1/ai",
});
return Ok(gateway(modelId));
}

return Err({
type: "provider_not_supported",
provider: providerName,
Expand Down
26 changes: 26 additions & 0 deletions src/node/services/ipcMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1467,6 +1467,14 @@ export class IpcMain {
// Load current providers config or create empty
const providersConfig = this.config.loadProvidersConfig() ?? {};

// Track if this is first time setting couponCode for mux-gateway
const isFirstMuxGatewayCoupon =
provider === "mux-gateway" &&
keyPath.length === 1 &&
keyPath[0] === "couponCode" &&
value !== "" &&
!providersConfig[provider]?.couponCode;

// Ensure provider exists
if (!providersConfig[provider]) {
providersConfig[provider] = {};
Expand All @@ -1492,6 +1500,19 @@ export class IpcMain {
}
}

// Add default models when setting up mux-gateway for the first time
if (isFirstMuxGatewayCoupon) {
const providerConfig = providersConfig[provider] as Record<string, unknown>;
if (!providerConfig.models || (providerConfig.models as string[]).length === 0) {
providerConfig.models = [
"anthropic/claude-sonnet-4-5-20250514",
"anthropic/claude-opus-4-5-20250514",
"openai/gpt-5.1",
"openai/gpt-5.1-codex",
];
}
}

// Save updated config
this.config.saveProvidersConfig(providersConfig);

Expand Down Expand Up @@ -1562,6 +1583,11 @@ export class IpcMain {
providerData.secretAccessKeySet = !!providerConfig.secretAccessKey;
}

// Mux Gateway-specific fields
if (provider === "mux-gateway") {
providerData.couponCodeSet = !!providerConfig.couponCode;
}

sanitized[provider] = providerData;
}
return sanitized;
Expand Down