88 AppSystemProp ,
99 cacheWrapper ,
1010 hashUtils ,
11+ logger ,
1112 system ,
1213} from '@openops/server-shared' ;
1314import {
@@ -23,6 +24,7 @@ import { generateObject, LanguageModel, ModelMessage, UIMessage } from 'ai';
2324import { z } from 'zod' ;
2425import { appConnectionService } from '../../app-connection/app-connection-service/app-connection-service' ;
2526import { aiConfigService } from '../config/ai-config.service' ;
27+ import { findFirstKeyInObject } from '../mcp/llm-query-router' ;
2628import { loadPrompt } from './prompts.service' ;
2729import { Conversation } from './types' ;
2830import {
@@ -100,6 +102,32 @@ const generatedChatNameSchema = z.object({
100102 isGenerated : z . boolean ( ) . describe ( 'Whether the name was generated or not' ) ,
101103} ) ;
102104
105+ /**
106+ * Attempts to repair a malformed JSON string produced by the model for chat name generation.
107+ * It extracts only the expected fields according to generatedChatNameSchema.
108+ * Returns null if the input cannot be parsed or repaired (so the AI SDK can retry/throw).
109+ */
110+ const repairText = ( text : string ) : string | null => {
111+ try {
112+ const parsed = JSON . parse ( text ) ;
113+
114+ const nameRaw = findFirstKeyInObject ( parsed , 'name' ) ;
115+ let name : string | null = null ;
116+ if ( typeof nameRaw === 'string' ) {
117+ const trimmed = nameRaw . trim ( ) ;
118+ name = trimmed . length > 0 ? trimmed . slice ( 0 , 100 ) : null ;
119+ }
120+
121+ const isGeneratedRaw = findFirstKeyInObject ( parsed , 'isGenerated' ) ;
122+ const isGenerated =
123+ typeof isGeneratedRaw === 'boolean' ? isGeneratedRaw : Boolean ( name ) ;
124+
125+ return JSON . stringify ( { name, isGenerated } ) ;
126+ } catch {
127+ return null ;
128+ }
129+ } ;
130+
103131export async function generateChatName (
104132 messages : ModelMessage [ ] ,
105133 projectId : string ,
@@ -117,17 +145,22 @@ export async function generateChatName(
117145 return { name : null , isGenerated : false } ;
118146 }
119147
120- const result = await generateObject ( {
121- model : languageModel ,
122- system : systemPrompt ,
123- messages : sanitizedMessages ,
124- schema : generatedChatNameSchema ,
125- ...aiConfig . modelSettings ,
126- experimental_telemetry : { isEnabled : isLLMTelemetryEnabled ( ) } ,
127- maxRetries : 2 ,
128- } ) ;
129-
130- return result . object ;
148+ try {
149+ const result = await generateObject ( {
150+ model : languageModel ,
151+ system : systemPrompt ,
152+ messages : sanitizedMessages ,
153+ schema : generatedChatNameSchema ,
154+ ...aiConfig . modelSettings ,
155+ experimental_telemetry : { isEnabled : isLLMTelemetryEnabled ( ) } ,
156+ experimental_repairText : async ( { text } ) => repairText ( text ) ,
157+ maxRetries : 2 ,
158+ } ) ;
159+ return result . object ;
160+ } catch ( error ) {
161+ logger . error ( 'Failed to generate chat name' , { error } ) ;
162+ return { name : null , isGenerated : false } ;
163+ }
131164}
132165
133166export const updateChatName = async (
0 commit comments