Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

170 lines
4.1 KiB
Go

package agenthost
import (
"strings"
"unicode/utf8"
canonical "github.com/tutti-os/tutti/packages/agent/store-sqlite/canonical"
)
const MaxSessionTitleRunes = 120
// NormalizeTitle converts provider/Tutti rich-text serialization to a plain
// title. It intentionally does not apply UI-local labels or localization.
// Call it only at a rich-text-to-title boundary; persisted canonical titles
// must flow through the rest of the system without being parsed again.
func NormalizeTitle(value string) string {
value = strings.TrimSpace(value)
if value == "" {
return ""
}
var output strings.Builder
for index := 0; index < len(value); {
labelStart, hrefStart, hrefEnd, ok := markdownLinkAt(value, index)
if !ok {
output.WriteByte(value[index])
index++
continue
}
output.WriteString(unescapeMarkdownLabel(value[labelStart : hrefStart-2]))
index = hrefEnd + 1
}
return strings.Join(strings.Fields(strings.TrimSpace(output.String())), " ")
}
// DeriveInitialTitle converts the first user-visible prompt into the canonical
// title for a session that still has no title. The empty result is a safe
// compare-and-set candidate: an established title is never overwritten.
func DeriveInitialTitle(currentTitle string, visiblePrompt string) string {
if strings.TrimSpace(currentTitle) != "" {
return ""
}
title := NormalizeTitle(visiblePrompt)
if title == "" {
return ""
}
if utf8.RuneCountInString(title) <= MaxSessionTitleRunes {
return title
}
const suffix = "..."
runes := []rune(title)
suffixRunes := utf8.RuneCountInString(suffix)
return strings.TrimSpace(string(runes[:MaxSessionTitleRunes-suffixRunes])) + suffix
}
// IsLegacyTitlePlaceholder recognizes historical provider/target identity titles.
// This is migration-only compatibility; live submit code must not use it.
func IsLegacyTitlePlaceholder(value string, provider string, targetAliases ...string) bool {
title := normalizeIdentity(value)
provider = normalizeIdentity(provider)
if title == "" || title == provider {
return true
}
for _, candidate := range targetAliases {
if title == normalizeIdentity(candidate) {
return true
}
}
descriptor, ok := canonical.FindProviderIdentity(provider)
if !ok {
return false
}
for _, candidate := range append(
[]string{descriptor.ID, descriptor.DisplayName},
descriptor.Aliases...,
) {
if title == normalizeIdentity(candidate) {
return true
}
}
return false
}
func normalizeIdentity(value string) string {
return strings.ToLower(strings.TrimSpace(value))
}
func markdownLinkAt(value string, start int) (labelStart, hrefStart, hrefEnd int, ok bool) {
if start >= len(value) || value[start] != '[' {
return 0, 0, 0, false
}
labelEnd := findUnescaped(value, start+1, ']')
if labelEnd < 0 || labelEnd+1 >= len(value) || value[labelEnd+1] != '(' {
return 0, 0, 0, false
}
hrefEnd = findBalancedHrefEnd(value, labelEnd+2)
if hrefEnd < 0 {
return 0, 0, 0, false
}
return start + 1, labelEnd + 2, hrefEnd, true
}
func findUnescaped(value string, start int, target byte) int {
escaped := false
for index := start; index < len(value); index++ {
if escaped {
escaped = false
continue
}
if value[index] == '\\' {
escaped = true
continue
}
if value[index] == target {
return index
}
}
return -1
}
func findBalancedHrefEnd(value string, start int) int {
depth := 0
escaped := false
for index := start; index < len(value); index++ {
if escaped {
escaped = false
continue
}
switch value[index] {
case '\\':
escaped = true
case '(':
depth++
case ')':
if depth == 0 {
return index
}
depth--
}
}
return -1
}
func unescapeMarkdownLabel(value string) string {
var output strings.Builder
escaped := false
for index := 0; index < len(value); index++ {
char := value[index]
if escaped {
if strings.ContainsRune(`\\[]()`, rune(char)) {
output.WriteByte(char)
} else {
output.WriteByte('\\')
output.WriteByte(char)
}
escaped = false
continue
}
if char == '\\' {
escaped = true
continue
}
output.WriteByte(char)
}
if escaped {
output.WriteByte('\\')
}
return output.String()
}