Skip to content

Commit 896fd72

Browse files
committed
🤖 fix: use user-selected provider/model for title generation (no fallback)
- Title generator now builds a LanguageModel from the exact model string, via PROVIDER_REGISTRY - Supports anthropic/openai/openrouter/ollama; errors if required API key missing - Removed temporary chat-* fallback path and dead code _Generated with _
1 parent 1b52391 commit 896fd72

File tree

1 file changed

+45
-48
lines changed

1 file changed

+45
-48
lines changed

‎src/node/services/workspaceTitleGenerator.ts‎

Lines changed: 45 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import type { Config } from "@/node/config";
44
import { log } from "./log";
55
import { createAnthropic } from "@ai-sdk/anthropic";
66
import { createOpenAI } from "@ai-sdk/openai";
7-
import { MODEL_NAMES } from "@/common/constants/knownModels";
7+
import { PROVIDER_REGISTRY } from "@/common/constants/providers";
8+
89
import type { Result } from "@/common/types/result";
910
import { Ok, Err } from "@/common/types/result";
1011
import type { SendMessageError } from "@/common/types/errors";
@@ -29,7 +30,7 @@ export async function generateWorkspaceName(
2930
config: Config
3031
): Promise<Result<string, SendMessageError>> {
3132
try {
32-
const model = getModelForTitleGeneration(modelString, config);
33+
const model = await getModelForTitleGeneration(modelString, config);
3334

3435
if (!model) {
3536
// Infer error from provider + config (mirrors createModel in aiService)
@@ -41,23 +42,19 @@ export async function generateWorkspaceName(
4142
});
4243
}
4344

44-
// For non-Anthropic/OpenAI providers (e.g., OpenRouter, Azure, Fireworks, Ollama),
45-
// title generation isn't supported. Do NOT block workspace creation — use a
46-
// temporary fallback name and let the subsequent send surface any provider issues.
47-
if (providerName !== "anthropic" && providerName !== "openai") {
48-
log.info(
49-
`Title generation not supported for provider "${providerName}"; using temporary fallback name.`
50-
);
51-
return Ok(createFallbackName());
52-
}
53-
54-
// Anthropic/OpenAI path: require API key so we can actually generate a title
5545
const providers = config.loadProvidersConfig();
56-
const hasApiKey = providers?.[providerName]?.apiKey;
57-
if (!hasApiKey) {
46+
47+
// Require API keys for providers that need them
48+
if (
49+
(providerName === "anthropic" ||
50+
providerName === "openai" ||
51+
providerName === "openrouter") &&
52+
!providers?.[providerName]?.apiKey
53+
) {
5854
return Err({ type: "api_key_not_found", provider: providerName });
5955
}
6056

57+
// Unknown/unsupported provider
6158
return Err({ type: "provider_not_supported", provider: providerName });
6259
}
6360

@@ -80,67 +77,67 @@ export async function generateWorkspaceName(
8077
* Priority: Haiku 4.5 (Anthropic) → GPT-5-mini (OpenAI) → fallback to user's model
8178
* Falls back to null if no provider configured
8279
*/
83-
function getModelForTitleGeneration(modelString: string, config: Config): LanguageModel | null {
80+
async function getModelForTitleGeneration(
81+
modelString: string,
82+
config: Config
83+
): Promise<LanguageModel | null> {
8484
const providersConfig = config.loadProvidersConfig();
8585

8686
if (!providersConfig) {
8787
return null;
8888
}
8989

9090
try {
91-
// Try Anthropic Haiku first (fastest/cheapest)
92-
if (providersConfig.anthropic?.apiKey) {
93-
const provider = createAnthropic({
94-
apiKey: String(providersConfig.anthropic.apiKey),
95-
});
96-
return provider(MODEL_NAMES.anthropic.HAIKU);
97-
}
98-
99-
// Try OpenAI GPT-5-mini second
100-
if (providersConfig.openai?.apiKey) {
101-
const provider = createOpenAI({
102-
apiKey: String(providersConfig.openai.apiKey),
103-
});
104-
return provider(MODEL_NAMES.openai.GPT_MINI);
105-
}
106-
107-
// Parse user's model as fallback
91+
// Use exactly what the user selected. Prefer their model over any defaults.
10892
const [providerName, modelId] = modelString.split(":", 2);
10993
if (!providerName || !modelId) {
11094
log.error("Invalid model string format:", modelString);
11195
return null;
11296
}
11397

114-
if (providerName === "anthropic" && providersConfig.anthropic?.apiKey) {
115-
const provider = createAnthropic({
116-
apiKey: String(providersConfig.anthropic.apiKey),
98+
if (providerName === "anthropic") {
99+
if (!providersConfig.anthropic?.apiKey) return null;
100+
const provider = createAnthropic({ apiKey: String(providersConfig.anthropic.apiKey) });
101+
return provider(modelId);
102+
}
103+
104+
if (providerName === "openai") {
105+
if (!providersConfig.openai?.apiKey) return null;
106+
const provider = createOpenAI({ apiKey: String(providersConfig.openai.apiKey) });
107+
// Use Responses API model variant to match aiService
108+
return provider.responses(modelId);
109+
}
110+
111+
if (providerName === "openrouter") {
112+
if (!providersConfig.openrouter?.apiKey) return null;
113+
const { createOpenRouter } = await PROVIDER_REGISTRY.openrouter();
114+
const provider = createOpenRouter({
115+
apiKey: String(providersConfig.openrouter.apiKey),
116+
baseURL: providersConfig.openrouter.baseUrl,
117+
headers: providersConfig.openrouter.headers as Record<string, string> | undefined,
117118
});
118119
return provider(modelId);
119120
}
120121

121-
if (providerName === "openai" && providersConfig.openai?.apiKey) {
122-
const provider = createOpenAI({
123-
apiKey: String(providersConfig.openai.apiKey),
122+
if (providerName === "ollama") {
123+
const { createOllama } = await PROVIDER_REGISTRY.ollama();
124+
const provider = createOllama({
125+
baseURL: (providersConfig.ollama?.baseUrl ?? providersConfig.ollama?.baseURL) as
126+
| string
127+
| undefined,
124128
});
125129
return provider(modelId);
126130
}
127131

128-
log.error(`Provider ${providerName} not configured or not supported`);
132+
// Unknown provider
133+
log.error(`Provider ${providerName} not configured or not supported for titles`);
129134
return null;
130135
} catch (error) {
131136
log.error(`Failed to create model for title generation`, error);
132137
return null;
133138
}
134139
}
135140

136-
/**
137-
* Create fallback name using timestamp (used for providers without title support)
138-
*/
139-
function createFallbackName(): string {
140-
const timestamp = Date.now().toString(36);
141-
return `chat-${timestamp}`;
142-
}
143-
144141
/**
145142
* Validate and sanitize branch name to be git-safe
146143
*/

0 commit comments

Comments
 (0)