Dolphin3.0-CoreML / examples /ios /DolphinCoreMLTestApp /Sources /SystemTools /EventKitCalendarReader.swift
| import EventKit | |
| import Foundation | |
| /// The production adapter is intentionally read-only and non-interactive. | |
| /// Permission prompts belong to an explicit Settings flow, never tool | |
| /// execution, so an agent run cannot surprise the user with a system prompt. | |
| actor EventKitCalendarReader: SystemCalendarReading { | |
| private let eventStore: EKEventStore | |
| init(eventStore: EKEventStore = EKEventStore()) { | |
| self.eventStore = eventStore | |
| } | |
| func authorizationStatus() -> SystemCalendarAuthorization { | |
| Self.authorizationStatus( | |
| from: EKEventStore.authorizationStatus(for: .event) | |
| ) | |
| } | |
| /// Called only from the explicit Settings button. Tool execution uses | |
| /// `readEvents` and therefore can never trigger an iOS permission prompt. | |
| func requestFullAccess() async throws -> SystemCalendarAuthorization { | |
| _ = try await eventStore.requestFullAccessToEvents() | |
| return authorizationStatus() | |
| } | |
| func readEvents( | |
| in interval: DateInterval, | |
| limit: Int | |
| ) throws -> SystemCalendarEventPage { | |
| let authorization = authorizationStatus() | |
| guard authorization == .fullAccess else { | |
| throw SystemCalendarReaderError.fullAccessRequired(authorization) | |
| } | |
| let safeLimit = min(max(limit, 1), 11) | |
| let predicate = eventStore.predicateForEvents( | |
| withStart: interval.start, | |
| end: interval.end, | |
| calendars: nil | |
| ) | |
| let matchingEvents = eventStore.events(matching: predicate) | |
| .filter { event in | |
| event.startDate < interval.end && event.endDate > interval.start | |
| } | |
| .sorted { first, second in | |
| if first.startDate != second.startDate { | |
| return first.startDate < second.startDate | |
| } | |
| if first.endDate != second.endDate { | |
| return first.endDate < second.endDate | |
| } | |
| return (first.title ?? "").localizedStandardCompare( | |
| second.title ?? "" | |
| ) == .orderedAscending | |
| } | |
| let visibleEvents = matchingEvents.prefix(safeLimit).map { event in | |
| SystemCalendarEvent( | |
| title: event.title ?? "Untitled event", | |
| startsAt: event.startDate, | |
| endsAt: max(event.startDate, event.endDate), | |
| isAllDay: event.isAllDay | |
| ) | |
| } | |
| return SystemCalendarEventPage( | |
| events: Array(visibleEvents), | |
| hasMore: matchingEvents.count > safeLimit | |
| ) | |
| } | |
| private static func authorizationStatus( | |
| from status: EKAuthorizationStatus | |
| ) -> SystemCalendarAuthorization { | |
| switch status { | |
| case .fullAccess: | |
| .fullAccess | |
| case .notDetermined: | |
| .notDetermined | |
| case .denied: | |
| .denied | |
| case .restricted: | |
| .restricted | |
| case .writeOnly: | |
| .writeOnly | |
| @unknown default: | |
| .restricted | |
| } | |
| } | |
| } | |