@@ -132,7 +132,7 @@ type LocalRuntime struct {
132132 elicitationEventsChannel chan Event // Current events channel for sending elicitation requests
133133 elicitationEventsChannelMux sync.RWMutex // Protects elicitationEventsChannel
134134 ragInitialized atomic.Bool
135- titleGenerationWg sync. WaitGroup // Wait group for title generation
135+ titleGen * titleGenerator
136136}
137137
138138type streamResult struct {
@@ -210,6 +210,13 @@ func New(agents *team.Team, opts ...Opt) (*LocalRuntime, error) {
210210 return nil , err
211211 }
212212
213+ model := agents .Model ()
214+ if model == nil {
215+ return nil , errors .New ("no model found for the team; ensure at least one agent has a valid model" )
216+ }
217+
218+ r .titleGen = newTitleGenerator (model )
219+
213220 slog .Debug ("Creating new runtime" , "agent" , r .currentAgent , "available_agents" , agents .Size ())
214221
215222 return r , nil
@@ -488,8 +495,7 @@ func (r *LocalRuntime) finalizeEventChannel(ctx context.Context, sess *session.S
488495
489496 telemetry .RecordSessionEnd (ctx )
490497
491- // Wait for title generation if it's in progress
492- r .titleGenerationWg .Wait ()
498+ r .titleGen .Wait ()
493499}
494500
495501// RunStream starts the agent's interaction loop and returns a channel of events
@@ -543,7 +549,6 @@ func (r *LocalRuntime) RunStream(ctx context.Context, sess *session.Session) <-c
543549 return
544550 }
545551
546- // Emit toolset information
547552 events <- ToolsetInfo (len (agentTools ), r .currentAgent )
548553
549554 messages := sess .GetMessages (a )
@@ -558,9 +563,7 @@ func (r *LocalRuntime) RunStream(ctx context.Context, sess *session.Session) <-c
558563 r .registerDefaultTools ()
559564
560565 if sess .Title == "" {
561- r .titleGenerationWg .Go (func () {
562- r .generateSessionTitle (ctx , sess , events )
563- })
566+ r .titleGen .Generate (ctx , sess , events )
564567 }
565568
566569 iteration := 0
@@ -1353,72 +1356,6 @@ func (r *LocalRuntime) handleHandoff(_ context.Context, _ *session.Session, tool
13531356 }, nil
13541357}
13551358
1356- // truncateTitle truncates a title to maxLength characters, adding an ellipsis if needed
1357- func truncateTitle (title string , maxLength int ) string {
1358- if len (title ) <= maxLength {
1359- return title
1360- }
1361- // Ensure we have room for the ellipsis
1362- if maxLength < 3 {
1363- return "..."
1364- }
1365- return title [:maxLength - 3 ] + "..."
1366- }
1367-
1368- // generateSessionTitle generates a title for the session based on the first user message
1369- func (r * LocalRuntime ) generateSessionTitle (ctx context.Context , sess * session.Session , events chan Event ) {
1370- slog .Debug ("Generating title for session" , "session_id" , sess .ID )
1371-
1372- firstUserMessage := sess .GetLastUserMessageContent ()
1373- if firstUserMessage == "" {
1374- slog .Error ("Failed generating session title: no user message found in session" , "session_id" , sess .ID )
1375- events <- SessionTitle (sess .ID , "Untitled" , r .currentAgent )
1376- return
1377- }
1378-
1379- systemPrompt := "You are a helpful AI assistant that generates concise, descriptive titles for conversations. You will be given a conversation history and asked to create a title that captures the main topic."
1380- userPrompt := fmt .Sprintf ("Based on the following message a user sent to an AI assistant, generate a short, descriptive title (maximum 50 characters) that captures the main topic or purpose of the conversation. Return ONLY the title text, nothing else.\n \n User message: %s\n \n " , firstUserMessage )
1381-
1382- titleModel := provider .CloneWithOptions (
1383- ctx ,
1384- r .CurrentAgent ().Model (),
1385- options .WithStructuredOutput (nil ),
1386- options .WithMaxTokens (100 ),
1387- options .WithGeneratingTitle (),
1388- )
1389- newTeam := team .New (
1390- team .WithAgents (agent .New ("root" , systemPrompt , agent .WithModel (titleModel ))),
1391- )
1392- titleSession := session .New (
1393- session .WithUserMessage (userPrompt ),
1394- session .WithTitle ("Generating title..." ),
1395- )
1396-
1397- titleRuntime , err := New (newTeam , WithSessionCompaction (false ))
1398- if err != nil {
1399- slog .Error ("Failed to create title generator runtime" , "error" , err )
1400- return
1401- }
1402-
1403- // Run the title generation (this will be a simple back-and-forth)
1404- _ , err = titleRuntime .Run (ctx , titleSession )
1405- if err != nil {
1406- slog .Error ("Failed to generate session title" , "session_id" , sess .ID , "error" , err )
1407- return
1408- }
1409-
1410- // Get the generated title from the last assistant message
1411- title := titleSession .GetLastAssistantMessageContent ()
1412- if title == "" {
1413- return
1414- }
1415- // Truncate title to 50 characters with ellipsis if needed
1416- title = truncateTitle (title , 50 )
1417- sess .Title = title
1418- slog .Debug ("Generated session title" , "session_id" , sess .ID , "title" , title )
1419- events <- SessionTitle (sess .ID , title , r .currentAgent )
1420- }
1421-
14221359// Summarize generates a summary for the session based on the conversation history
14231360func (r * LocalRuntime ) Summarize (ctx context.Context , sess * session.Session , events chan Event ) {
14241361 slog .Debug ("Generating summary for session" , "session_id" , sess .ID )
0 commit comments