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) +}