import Foundation struct AssistantTextSegment: Identifiable { enum Kind { case thinking case response } let id = UUID() let kind: Kind let text: String } enum AssistantTextParser { static func segments(from raw: String) -> [AssistantTextSegment] { let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return [] } guard raw.contains("<") else { return [AssistantTextSegment(kind: .response, text: trimmed)] } var segments: [AssistantTextSegment] = [] var cursor = raw.startIndex var currentKind: AssistantTextSegment.Kind = .response var matchedTag = false while let match = self.nextTag(in: raw, from: cursor) { matchedTag = true if match.range.lowerBound > cursor { self.appendSegment(kind: currentKind, text: raw[cursor..", range: match.range.upperBound.. Bool { !self.segments(from: raw).isEmpty } private enum TagKind { case think case final } private struct TagMatch { let kind: TagKind let closing: Bool let range: Range } private static func nextTag(in text: String, from start: String.Index) -> TagMatch? { let candidates: [TagMatch] = [ self.findTagStart(tag: "think", closing: false, in: text, from: start).map { TagMatch(kind: .think, closing: false, range: $0) }, self.findTagStart(tag: "think", closing: true, in: text, from: start).map { TagMatch(kind: .think, closing: true, range: $0) }, self.findTagStart(tag: "final", closing: false, in: text, from: start).map { TagMatch(kind: .final, closing: false, range: $0) }, self.findTagStart(tag: "final", closing: true, in: text, from: start).map { TagMatch(kind: .final, closing: true, range: $0) }, ].compactMap(\.self) return candidates.min { $0.range.lowerBound < $1.range.lowerBound } } private static func findTagStart( tag: String, closing: Bool, in text: String, from start: String.Index) -> Range? { let token = closing ? "" || boundary.isWhitespace || (!closing && boundary == "/") if isBoundary { return range } searchRange = boundaryIndex..) -> Bool { var cursor = tagEnd.lowerBound while cursor > text.startIndex { cursor = text.index(before: cursor) let char = text[cursor] if char.isWhitespace { continue } return char == "/" } return false } private static func appendSegment( kind: AssistantTextSegment.Kind, text: Substring, to segments: inout [AssistantTextSegment]) { let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return } segments.append(AssistantTextSegment(kind: kind, text: trimmed)) } }