Skip to content

Commit e06746e

Browse files
committed
🤖 fix: auto-naming works with mux-gateway
When users only have mux-gateway configured (no direct Anthropic/OpenAI API keys), getPreferredNameModel now tries gateway versions of preferred models. The fix: 1. Frontend passes `useGateway: boolean` based on whether gateway is configured 2. Backend tries gateway versions of preferred models only when useGateway=true 3. Still prefers direct access when available for lower latency This ensures auto-naming works for gateway-only users while maintaining optimal performance for users with direct API keys. --- _Generated with `mux` • Model: `anthropic:claude-opus-4-5` • Thinking: `high`_
1 parent b98b323 commit e06746e

File tree

5 files changed

+69
-6
lines changed

5 files changed

+69
-6
lines changed

src/browser/hooks/useWorkspaceName.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState, useRef, useCallback, useEffect, useMemo } from "react";
22
import { useAPI } from "@/browser/contexts/API";
3+
import { useGateway } from "@/browser/hooks/useGatewayModels";
34

45
export interface UseWorkspaceNameOptions {
56
/** The user's message to generate a name for */
@@ -51,6 +52,7 @@ export interface UseWorkspaceNameReturn extends WorkspaceNameState {
5152
export function useWorkspaceName(options: UseWorkspaceNameOptions): UseWorkspaceNameReturn {
5253
const { message, debounceMs = 500, fallbackModel } = options;
5354
const { api } = useAPI();
55+
const { isActive: gatewayActive } = useGateway();
5456

5557
// Generated identity (name + title) from AI
5658
const [generatedIdentity, setGeneratedIdentity] = useState<WorkspaceIdentity | null>(null);
@@ -121,6 +123,7 @@ export function useWorkspaceName(options: UseWorkspaceNameOptions): UseWorkspace
121123
const result = await api.nameGeneration.generate({
122124
message: forMessage,
123125
fallbackModel,
126+
useGateway: gatewayActive,
124127
});
125128

126129
// Check if this request is still current (wasn't cancelled)
@@ -162,7 +165,7 @@ export function useWorkspaceName(options: UseWorkspaceNameOptions): UseWorkspace
162165
}
163166
}
164167
},
165-
[api, fallbackModel]
168+
[api, fallbackModel, gatewayActive]
166169
);
167170

168171
// Debounced generation effect

src/common/orpc/schemas/api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,8 @@ export const nameGeneration = {
487487
message: z.string(),
488488
/** Model to use if preferred small models (Haiku, GPT-Mini) aren't available */
489489
fallbackModel: z.string().optional(),
490+
/** Whether to try mux-gateway versions of preferred models (set by frontend based on config) */
491+
useGateway: z.boolean().optional(),
490492
}),
491493
output: ResultSchema(
492494
z.object({

src/node/orpc/router.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,9 @@ export const router = (authToken?: string) => {
522522
.output(schemas.nameGeneration.generate.output)
523523
.handler(async ({ context, input }) => {
524524
// Prefer small/fast models, fall back to user's configured model
525-
const model = (await getPreferredNameModel(context.aiService)) ?? input.fallbackModel;
525+
const model =
526+
(await getPreferredNameModel(context.aiService, input.useGateway)) ??
527+
input.fallbackModel;
526528
if (!model) {
527529
return {
528530
success: false,

src/node/services/workspaceTitleGenerator.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ function createMockAIService(availableModels: string[]): AIService {
3030
describe("workspaceTitleGenerator", () => {
3131
const HAIKU_ID = getKnownModel("HAIKU").id;
3232
const GPT_MINI_ID = getKnownModel("GPT_MINI").id;
33+
// Gateway format: mux-gateway:provider/model instead of provider:model
34+
const HAIKU_GATEWAY = "mux-gateway:anthropic/claude-haiku-4-5";
35+
const GPT_MINI_GATEWAY = "mux-gateway:openai/gpt-5.1-codex-mini";
3336

3437
it("getPreferredNameModel returns null when no models available", async () => {
3538
const aiService = createMockAIService([]);
@@ -47,4 +50,32 @@ describe("workspaceTitleGenerator", () => {
4750
const model = await getPreferredNameModel(aiService);
4851
expect(model).toBe(GPT_MINI_ID);
4952
});
53+
54+
it("getPreferredNameModel uses gateway Haiku when direct is unavailable and useGateway=true", async () => {
55+
// User has mux-gateway configured but no direct Anthropic/OpenAI API keys
56+
const aiService = createMockAIService([HAIKU_GATEWAY, GPT_MINI_GATEWAY]);
57+
const model = await getPreferredNameModel(aiService, true);
58+
expect(model).toBe(HAIKU_GATEWAY);
59+
});
60+
61+
it("getPreferredNameModel falls back to gateway GPT Mini when useGateway=true", async () => {
62+
// Only GPT Mini available via gateway
63+
const aiService = createMockAIService([GPT_MINI_GATEWAY]);
64+
const model = await getPreferredNameModel(aiService, true);
65+
expect(model).toBe(GPT_MINI_GATEWAY);
66+
});
67+
68+
it("getPreferredNameModel prefers direct over gateway", async () => {
69+
// Both direct and gateway available - should prefer direct
70+
const aiService = createMockAIService([HAIKU_ID, HAIKU_GATEWAY]);
71+
const model = await getPreferredNameModel(aiService, true);
72+
expect(model).toBe(HAIKU_ID);
73+
});
74+
75+
it("getPreferredNameModel ignores gateway models when useGateway=false", async () => {
76+
// Gateway models available but useGateway not set - should return null
77+
const aiService = createMockAIService([HAIKU_GATEWAY, GPT_MINI_GATEWAY]);
78+
expect(await getPreferredNameModel(aiService)).toBeNull();
79+
expect(await getPreferredNameModel(aiService, false)).toBeNull();
80+
});
5081
});

src/node/services/workspaceTitleGenerator.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ import crypto from "crypto";
1111
/** Models to try in order of preference for name generation (small, fast models) */
1212
const PREFERRED_MODELS = [getKnownModel("HAIKU").id, getKnownModel("GPT_MINI").id] as const;
1313

14+
/**
15+
* Convert a canonical model string (provider:model) to mux-gateway format.
16+
* Example: "anthropic:claude-haiku-4-5" → "mux-gateway:anthropic/claude-haiku-4-5"
17+
*/
18+
function toGatewayModel(modelId: string): string {
19+
const [provider, model] = modelId.split(":", 2);
20+
if (!provider || !model) return modelId;
21+
return `mux-gateway:${provider}/${model}`;
22+
}
23+
1424
/** Schema for AI-generated workspace identity (area name + descriptive title) */
1525
const workspaceIdentitySchema = z.object({
1626
name: z
@@ -39,14 +49,29 @@ export interface WorkspaceIdentity {
3949
* Get the preferred model for name generation by testing which models the AIService
4050
* can actually create. This delegates credential checking to AIService, avoiding
4151
* duplication of provider-specific API key logic.
52+
*
53+
* @param useGateway - If true, also try mux-gateway versions of preferred models.
54+
* This should be set by the frontend based on whether the user has gateway configured.
4255
*/
43-
export async function getPreferredNameModel(aiService: AIService): Promise<string | null> {
56+
export async function getPreferredNameModel(
57+
aiService: AIService,
58+
useGateway?: boolean
59+
): Promise<string | null> {
4460
for (const modelId of PREFERRED_MODELS) {
45-
const result = await aiService.createModel(modelId);
46-
if (result.success) {
61+
// Try direct provider access first
62+
const directResult = await aiService.createModel(modelId);
63+
if (directResult.success) {
4764
return modelId;
4865
}
49-
// If it's an API key error, try the next model; other errors are also skipped
66+
67+
// Try via mux-gateway if the frontend indicates it's available
68+
if (useGateway) {
69+
const gatewayModelId = toGatewayModel(modelId);
70+
const gatewayResult = await aiService.createModel(gatewayModelId);
71+
if (gatewayResult.success) {
72+
return gatewayModelId;
73+
}
74+
}
5075
}
5176
return null;
5277
}

0 commit comments

Comments
 (0)