@@ -4,7 +4,8 @@ import type { Config } from "@/node/config";
44import { log } from "./log" ;
55import { createAnthropic } from "@ai-sdk/anthropic" ;
66import { createOpenAI } from "@ai-sdk/openai" ;
7- import { MODEL_NAMES } from "@/common/constants/knownModels" ;
7+ import { PROVIDER_REGISTRY } from "@/common/constants/providers" ;
8+
89import type { Result } from "@/common/types/result" ;
910import { Ok , Err } from "@/common/types/result" ;
1011import 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