diff --git a/internal/agent/agent.go b/internal/agent/agent.go
index c916cfd886372ab86f6d1fbb0e8b7bde2c87dabb..b336cf757059133d901dd62f034453065b00a5cc 100644
--- a/internal/agent/agent.go
+++ b/internal/agent/agent.go
@@ -766,6 +766,8 @@ func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, user
)
}
+ var reasoningContent strings.Builder
+
streamCall := fantasy.AgentStreamCall{
Prompt: fmt.Sprintf("Generate a concise title for the following content:\n\n%s\n \n\n", userPrompt),
PrepareStep: func(callCtx context.Context, opts fantasy.PrepareStepFunctionOptions) (_ context.Context, prepared fantasy.PrepareStepResult, err error) {
@@ -777,6 +779,11 @@ func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, user
}
return callCtx, prepared, nil
},
+ OnReasoningDelta: func(id string, text string) error {
+ // Also capture reasoning for title fallback.
+ reasoningContent.WriteString(text)
+ return nil
+ },
}
// Use the small model to generate the title.
@@ -822,9 +829,23 @@ func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, user
title = strings.ReplaceAll(resp.Response.Content.Text(), "\n", " ")
// Remove thinking tags if present.
- title = thinkTagRegex.ReplaceAllString(title, "")
+ title = removeThinkingTags(title)
+
+ // If title is empty, try reasoning content (models may put the title in
+ // reasoning).
+ if title == "" && reasoningContent.Len() > 0 {
+ reasoningTitle := strings.ReplaceAll(reasoningContent.String(), "\n", " ")
+ reasoningTitle = removeThinkingTags(reasoningTitle)
+ // Extract last sentence or reasonable length from reasoning, if
+ // present.
+ if len(reasoningTitle) > 0 {
+ if sentences := strings.Split(reasoningTitle, "."); len(sentences) > 1 {
+ reasoningTitle = strings.TrimSpace(sentences[len(sentences)-1])
+ }
+ }
+ title = reasoningTitle
+ }
- title = strings.TrimSpace(title)
if title == "" {
slog.Warn("empty title; using fallback")
title = defaultSessionName
@@ -1135,3 +1156,10 @@ func buildSummaryPrompt(todos []session.Todo) string {
}
return sb.String()
}
+
+// removeThinkingTags removes ... tags from the given string.
+// Used to clean up generated session titles.
+func removeThinkingTags(s string) string {
+ s = thinkTagRegex.ReplaceAllString(s, "")
+ return strings.TrimSpace(s)
+}