From 45bf5bbce5d63a8c99d317432dc51ec8d8a6791e Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Thu, 4 Dec 2025 12:20:03 +0000 Subject: [PATCH] feat: use couponCode as the only mux-gateway config field - Add mux-gateway to slash command provider suggestions - Replace voucher with couponCode in UI, schema, and backend - Maintain backwards compatibility by falling back to legacy voucher field _Generated with mux_ --- .../Settings/sections/ProvidersSection.tsx | 22 ++++++++++--------- src/browser/utils/slashCommands/registry.ts | 10 +++++++++ src/common/orpc/schemas/api.test.ts | 12 +++++----- src/common/orpc/schemas/api.ts | 2 +- src/node/services/aiService.ts | 8 +++---- src/node/services/providerService.ts | 15 +++++++------ 6 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/browser/components/Settings/sections/ProvidersSection.tsx b/src/browser/components/Settings/sections/ProvidersSection.tsx index 3d89b0790f..61067fab02 100644 --- a/src/browser/components/Settings/sections/ProvidersSection.tsx +++ b/src/browser/components/Settings/sections/ProvidersSection.tsx @@ -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 @@ -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); @@ -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 @@ -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 @@ -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) { diff --git a/src/browser/utils/slashCommands/registry.ts b/src/browser/utils/slashCommands/registry.ts index e9fe5933fa..3110ee6a1f 100644 --- a/src/browser/utils/slashCommands/registry.ts +++ b/src/browser/utils/slashCommands/registry.ts @@ -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 = { @@ -111,6 +115,12 @@ const DEFAULT_PROVIDER_KEYS: Record = { description: "AWS Secret Access Key (use with accessKeyId)", }, ], + "mux-gateway": [ + { + key: "couponCode", + description: "Coupon code for Mux Gateway access", + }, + ], default: [ { key: "apiKey", diff --git a/src/common/orpc/schemas/api.test.ts b/src/common/orpc/schemas/api.test.ts index dc035398d2..567c3f84b1 100644 --- a/src/common/orpc/schemas/api.test.ts +++ b/src/common/orpc/schemas/api.test.ts @@ -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)", () => { @@ -89,7 +89,7 @@ describe("ProviderConfigInfoSchema conformance", () => { accessKeyIdSet: true, secretAccessKeySet: true, }, - voucherSet: true, + couponCodeSet: true, }; const parsed = ProviderConfigInfoSchema.parse(full); @@ -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", () => { @@ -122,7 +122,7 @@ describe("ProviderConfigInfoSchema conformance", () => { }, "mux-gateway": { apiKeySet: false, - voucherSet: true, + couponCodeSet: true, models: ["anthropic/claude-sonnet-4-5"], }, }; diff --git a/src/common/orpc/schemas/api.ts b/src/common/orpc/schemas/api.ts index e1fd2d8604..b0f21ea049 100644 --- a/src/common/orpc/schemas/api.ts +++ b/src/common/orpc/schemas/api.ts @@ -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); diff --git a/src/node/services/aiService.ts b/src/node/services/aiService.ts index 2b3b3dceb8..e9862723d2 100644 --- a/src/node/services/aiService.ts +++ b/src/node/services/aiService.ts @@ -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, @@ -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, }); diff --git a/src/node/services/providerService.ts b/src/node/services/providerService.ts index c984209711..6cdb358cee 100644 --- a/src/node/services/providerService.ts +++ b/src/node/services/providerService.ts @@ -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; @@ -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]) { @@ -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; if (!providerConfig.models || (providerConfig.models as string[]).length === 0) { providerConfig.models = [