import Foundation import Observation @MainActor final class Listeners { private var fns: [(id: UUID, fn: (T) -> Void)] = [] func add(_ fn: @escaping (T) -> Void) -> () -> Void { let id = UUID() fns.append((id, fn)) return { [weak self] in self?.fns.removeAll { $0.id == id } } } func send(_ value: T) { for entry in fns { entry.fn(value) } } } struct Capture: Identifiable { let id: String let tool: String let note: String let when: Date let base64: String } @MainActor @Observable final class Feed { private(set) var events: [ByreEvent] = [] private(set) var captures: [Capture] = [] private(set) var ready = false private(set) var inherited = 0 let appended = Listeners<[ByreEvent]>() let streamed = Listeners() @ObservationIgnored private var last: Int64 = 0 @ObservationIgnored private var buffer: [ByreEvent] = [] @ObservationIgnored private var scheduled = false @ObservationIgnored private var toolNames: [String: String] = [:] @ObservationIgnored private let shown: () -> Bool init(shown: @escaping () -> Bool) { self.shown = shown } func push(_ ev: ByreEvent) { if ev.seq == 0 && (!ready || !shown()) { return } buffer.append(ev) if ready && !scheduled { scheduled = true Task { @MainActor in self.flush() } } } func load(_ history: [ByreEvent], inherited: Int = 0) { if ready { return } ready = true self.inherited = inherited buffer = history + buffer flush() } var own: ArraySlice { events.dropFirst(inherited) } func prepend(_ older: [ByreEvent]) { events.insert(contentsOf: older, at: 0) captures.insert(contentsOf: findCaptures(older), at: 0) } private func findCaptures(_ batch: [ByreEvent]) -> [Capture] { var found: [Capture] = [] for ev in batch { let p = ev.payload.value if ev.kind == "agent.tool_call" { toolNames[jsString(p["id"])] = toolLabel(jsString(p["name"])) continue } guard ev.kind == "agent.tool_result", let list = p["content"] as? [(any Sendable)?] else { continue } let blocks = list.compactMap { $0 as? JSONDict } let note = blocks.compactMap { str($0, "type") == "text" ? str($0, "text") : nil }.first ?? "" for (i, b) in blocks.enumerated() { guard str(b, "type") == "image", let source = dict(b, "source"), let data = str(source, "data") else { continue } found.append(Capture(id: "\(ev.seq)/\(i)", tool: toolNames[jsString(p["tool_use_id"])] ?? "", note: note, when: ev.createdAt, base64: data)) } } return found } private func flush() { scheduled = false let pending = buffer buffer = [] var stored: [ByreEvent] = [] for ev in pending { if ev.seq != 0 { stored.append(ev) continue } append(stored) stored = [] if shown() { streamed.send(ev) } } append(stored) } private func append(_ batch: [ByreEvent]) { var fresh: [ByreEvent] = [] for ev in batch where ev.seq > last { fresh.append(ev) last = ev.seq } if fresh.isEmpty { return } events.append(contentsOf: fresh) let found = findCaptures(fresh) if !found.isEmpty { captures.append(contentsOf: found) } if shown() { appended.send(fresh) } } }