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

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

// Default for most providers
Expand Down Expand Up @@ -102,8 +104,8 @@ export function ProvidersSection() {
updateOptimistically(provider, { apiKeySet: editValue !== "" });
} else if (field === "baseUrl") {
updateOptimistically(provider, { baseUrl: editValue || undefined });
} else if (field === "voucher") {
updateOptimistically(provider, { voucherSet: editValue !== "" });
} else if (field === "couponCode") {
updateOptimistically(provider, { couponCodeSet: editValue !== "" });
}

setEditingField(null);
Expand All @@ -122,8 +124,8 @@ export function ProvidersSection() {
updateOptimistically(provider, { apiKeySet: false });
} else if (field === "baseUrl") {
updateOptimistically(provider, { baseUrl: undefined });
} else if (field === "voucher") {
updateOptimistically(provider, { voucherSet: false });
} else if (field === "couponCode") {
updateOptimistically(provider, { couponCodeSet: false });
}

// Save in background
Expand All @@ -142,9 +144,9 @@ export function ProvidersSection() {
return !!(aws.region ?? aws.bearerTokenSet ?? aws.accessKeyIdSet ?? aws.secretAccessKeySet);
}

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

// For other providers, check apiKeySet
Expand Down Expand Up @@ -172,8 +174,8 @@ export function ProvidersSection() {
if (fieldConfig.type === "secret") {
// For apiKey, we have apiKeySet from the sanitized config
if (field === "apiKey") return providerConfig.apiKeySet ?? false;
// For voucher (mux-gateway), check voucherSet
if (field === "voucher") return providerConfig.voucherSet ?? false;
// For couponCode (mux-gateway), check couponCodeSet
if (field === "couponCode") return providerConfig.couponCodeSet ?? false;

// For AWS secrets, check the aws nested object
if (provider === "bedrock" && providerConfig.aws) {
Expand Down
10 changes: 10 additions & 0 deletions src/browser/utils/slashCommands/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ const DEFAULT_PROVIDER_NAMES: SuggestionDefinition[] = [
key: "bedrock",
description: "Amazon Bedrock provider (AWS)",
},
{
key: "mux-gateway",
description: "Mux Gateway provider",
},
];

const DEFAULT_PROVIDER_KEYS: Record<string, SuggestionDefinition[]> = {
Expand Down Expand Up @@ -111,6 +115,12 @@ const DEFAULT_PROVIDER_KEYS: Record<string, SuggestionDefinition[]> = {
description: "AWS Secret Access Key (use with accessKeyId)",
},
],
"mux-gateway": [
{
key: "couponCode",
description: "Coupon code for Mux Gateway access",
},
],
default: [
{
key: "apiKey",
Expand Down
12 changes: 6 additions & 6 deletions src/common/orpc/schemas/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,16 @@ describe("ProviderConfigInfoSchema conformance", () => {
expect(parsed.aws).toEqual(full.aws);
});

it("preserves all ProviderConfigInfo fields (with voucherSet)", () => {
it("preserves all ProviderConfigInfo fields (with couponCodeSet)", () => {
const full: ProviderConfigInfo = {
apiKeySet: true,
voucherSet: true,
couponCodeSet: true,
};

const parsed = ProviderConfigInfoSchema.parse(full);

expect(parsed).toEqual(full);
expect(parsed.voucherSet).toBe(true);
expect(parsed.couponCodeSet).toBe(true);
});

it("preserves all ProviderConfigInfo fields (full object with all optional fields)", () => {
Expand All @@ -89,7 +89,7 @@ describe("ProviderConfigInfoSchema conformance", () => {
accessKeyIdSet: true,
secretAccessKeySet: true,
},
voucherSet: true,
couponCodeSet: true,
};

const parsed = ProviderConfigInfoSchema.parse(full);
Expand All @@ -102,7 +102,7 @@ describe("ProviderConfigInfoSchema conformance", () => {
expect(parsed.baseUrl).toBe(full.baseUrl);
expect(parsed.models).toEqual(full.models);
expect(parsed.aws).toEqual(full.aws);
expect(parsed.voucherSet).toBe(full.voucherSet);
expect(parsed.couponCodeSet).toBe(full.couponCodeSet);
});

it("preserves ProvidersConfigMap with multiple providers", () => {
Expand All @@ -122,7 +122,7 @@ describe("ProviderConfigInfoSchema conformance", () => {
},
"mux-gateway": {
apiKeySet: false,
voucherSet: true,
couponCodeSet: true,
models: ["anthropic/claude-sonnet-4-5"],
},
};
Expand Down
2 changes: 1 addition & 1 deletion src/common/orpc/schemas/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const ProviderConfigInfoSchema = z.object({
/** AWS-specific fields (only present for bedrock provider) */
aws: AWSCredentialStatusSchema.optional(),
/** Mux Gateway-specific fields */
voucherSet: z.boolean().optional(),
couponCodeSet: z.boolean().optional(),
});

export const ProvidersConfigMapSchema = z.record(z.string(), ProviderConfigInfoSchema);
Expand Down
8 changes: 4 additions & 4 deletions src/node/services/aiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -751,9 +751,9 @@ export class AIService extends EventEmitter {

// Handle Mux Gateway provider
if (providerName === "mux-gateway") {
// Mux Gateway uses voucher as the API key (fallback to legacy couponCode)
const voucher = providerConfig.voucher ?? providerConfig.couponCode;
if (typeof voucher !== "string" || !voucher) {
// Mux Gateway uses couponCode as the API key (fallback to legacy voucher)
const couponCode = providerConfig.couponCode ?? providerConfig.voucher;
if (typeof couponCode !== "string" || !couponCode) {
return Err({
type: "api_key_not_found",
provider: providerName,
Expand All @@ -773,7 +773,7 @@ export class AIService extends EventEmitter {
const gatewayBaseURL =
providerConfig.baseURL ?? "https://gateway.mux.coder.com/api/v1/ai-gateway/v1/ai";
const gateway = createGateway({
apiKey: voucher,
apiKey: couponCode,
baseURL: gatewayBaseURL,
fetch: fetchWithCacheControl,
});
Expand Down
15 changes: 8 additions & 7 deletions src/node/services/providerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ export class ProviderService {
};
}

// Mux Gateway-specific fields
// Mux Gateway-specific fields (check couponCode first, fallback to legacy voucher)
if (provider === "mux-gateway") {
providerInfo.voucherSet = !!(config as { voucher?: string }).voucher;
const muxConfig = config as { couponCode?: string; voucher?: string };
providerInfo.couponCodeSet = !!(muxConfig.couponCode ?? muxConfig.voucher);
}

result[provider] = providerInfo;
Expand Down Expand Up @@ -111,13 +112,13 @@ export class ProviderService {
// Load current providers config or create empty
const providersConfig = this.config.loadProvidersConfig() ?? {};

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

// Ensure provider exists
if (!providersConfig[provider]) {
Expand Down Expand Up @@ -145,7 +146,7 @@ export class ProviderService {
}

// Add default models when setting up mux-gateway for the first time
if (isFirstMuxGatewayVoucher) {
if (isFirstMuxGatewayCoupon) {
const providerConfig = providersConfig[provider] as Record<string, unknown>;
if (!providerConfig.models || (providerConfig.models as string[]).length === 0) {
providerConfig.models = [
Expand Down