import Foundation public struct RDAIDocumentIdentifier: RawRepresentable, Codable, Hashable, Sendable { public let rawValue: String public init(rawValue: String) { self.rawValue = rawValue } } public struct RDAIResourceIdentifier: RawRepresentable, Codable, Hashable, Sendable { public let rawValue: String public init(rawValue: String) { self.rawValue = rawValue } } /// UTF-16 offsets are shared by PDF text runs, EPUB search and speech progress. public struct RDAITextRange: Codable, Hashable, Sendable { public var location: Int public var length: Int public init(location: Int, length: Int) { self.location = max(0, location) self.length = max(0, length) } public var upperBound: Int { location + length } public func intersects(_ other: RDAITextRange) -> Bool { location < other.upperBound && other.location < upperBound } } public struct RDAINormalizedRect: Codable, Hashable, Sendable { public var x: Double public var y: Double public var width: Double public var height: Double public init(x: Double, y: Double, width: Double, height: Double) { self.x = min(max(x, 0), 1) self.y = min(max(y, 0), 1) self.width = min(max(width, 0), 1) self.height = min(max(height, 0), 1) } } public struct RDAIPDFAnchor: Codable, Hashable, Sendable { public enum TextSource: String, Codable, Sendable { case native case ocr } public var pageIndex: Int public var rects: [RDAINormalizedRect] public var textSource: TextSource public var readingOrder: Int? public init(pageIndex: Int, rects: [RDAINormalizedRect], textSource: TextSource, readingOrder: Int? = nil) { self.pageIndex = max(0, pageIndex) self.rects = rects self.textSource = textSource self.readingOrder = readingOrder } } public struct RDAIEPUBAnchor: Codable, Hashable, Sendable { public var href: String public var cfi: String? public var rangeCFI: String? public var progression: Double? public init(href: String, cfi: String? = nil, rangeCFI: String? = nil, progression: Double? = nil) { self.href = href self.cfi = cfi self.rangeCFI = rangeCFI self.progression = progression.map { min(max($0, 0), 1) } } } public enum RDAIAnchor: Hashable, Sendable { case pdf(RDAIPDFAnchor) case epub(RDAIEPUBAnchor) } extension RDAIAnchor: Codable { private enum CodingKeys: String, CodingKey { case type, pdf, epub } private enum Kind: String, Codable { case pdf, epub } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) switch try container.decode(Kind.self, forKey: .type) { case .pdf: self = .pdf(try container.decode(RDAIPDFAnchor.self, forKey: .pdf)) case .epub: self = .epub(try container.decode(RDAIEPUBAnchor.self, forKey: .epub)) } } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .pdf(let anchor): try container.encode(Kind.pdf, forKey: .type) try container.encode(anchor, forKey: .pdf) case .epub(let anchor): try container.encode(Kind.epub, forKey: .type) try container.encode(anchor, forKey: .epub) } } } public struct RDAILocator: Codable, Hashable, Sendable { public var documentIdentifier: RDAIDocumentIdentifier public var resourceIdentifier: RDAIResourceIdentifier public var textRange: RDAITextRange public var anchor: RDAIAnchor public var sourceHash: String public init( documentIdentifier: RDAIDocumentIdentifier, resourceIdentifier: RDAIResourceIdentifier, textRange: RDAITextRange, anchor: RDAIAnchor, sourceHash: String ) { self.documentIdentifier = documentIdentifier self.resourceIdentifier = resourceIdentifier self.textRange = textRange self.anchor = anchor self.sourceHash = sourceHash } } public struct RDAICitation: Codable, Hashable, Sendable, Identifiable { public let id: String public let passageIdentifier: String public let quote: String public let locator: RDAILocator public init(id: String = UUID().uuidString, passageIdentifier: String, quote: String, locator: RDAILocator) { self.id = id self.passageIdentifier = passageIdentifier self.quote = quote self.locator = locator } } public enum RDAIDocumentFormat: String, Codable, Sendable { case pdf case epub } public struct RDAIDocumentDescriptor: Codable, Equatable, Sendable { public let identifier: RDAIDocumentIdentifier public let title: String public let format: RDAIDocumentFormat public let contentRevision: String public init(identifier: RDAIDocumentIdentifier, title: String, format: RDAIDocumentFormat, contentRevision: String) { self.identifier = identifier self.title = title self.format = format self.contentRevision = contentRevision } } public struct RDAIResourceDescriptor: Codable, Equatable, Sendable { public let identifier: RDAIResourceIdentifier public let title: String? public let order: Int public let estimatedUTF16Length: Int? public init(identifier: RDAIResourceIdentifier, title: String? = nil, order: Int, estimatedUTF16Length: Int? = nil) { self.identifier = identifier self.title = title self.order = max(0, order) self.estimatedUTF16Length = estimatedUTF16Length } } public struct RDAILocatorRun: Sendable { public let textRange: RDAITextRange public let anchor: RDAIAnchor public init(textRange: RDAITextRange, anchor: RDAIAnchor) { self.textRange = textRange self.anchor = anchor } } public struct RDAIResourceSnapshot: Sendable { public let descriptor: RDAIResourceDescriptor public let sourceText: String public let sourceHash: String public let locatorRuns: [RDAILocatorRun] public init(descriptor: RDAIResourceDescriptor, sourceText: String, sourceHash: String, locatorRuns: [RDAILocatorRun]) { self.descriptor = descriptor self.sourceText = sourceText self.sourceHash = sourceHash self.locatorRuns = locatorRuns } } @MainActor public protocol RDAIContentProvider: AnyObject { func aiDocumentDescriptor() -> RDAIDocumentDescriptor func aiResources() async throws -> [RDAIResourceDescriptor] func aiResourceSnapshot(for identifier: RDAIResourceIdentifier) async throws -> RDAIResourceSnapshot func aiNavigate(to locator: RDAILocator, animated: Bool) async throws func aiShowCitationHighlight(_ citation: RDAICitation) async throws func aiClearCitationHighlight() } public struct RDAIReadScope: Codable, Equatable, Sendable { public let upperBound: RDAILocator? public let includesWholeDocument: Bool public init(upperBound: RDAILocator? = nil, includesWholeDocument: Bool = false) { self.upperBound = upperBound self.includesWholeDocument = includesWholeDocument } public static let wholeDocument = RDAIReadScope(includesWholeDocument: true) } @MainActor public protocol RDAIReadScopeProviding: AnyObject { func aiCurrentReadScope() -> RDAIReadScope } public enum RDAIPassageKind: String, Codable, Sendable { case paragraph case unknown } public struct RDAIPassage: Codable, Equatable, Sendable, Identifiable { public let id: String public let documentIdentifier: RDAIDocumentIdentifier public let resourceIdentifier: RDAIResourceIdentifier public let text: String public let languageCode: String? public let kind: RDAIPassageKind public let locator: RDAILocator public let contentHash: String public let order: Int public init( id: String, documentIdentifier: RDAIDocumentIdentifier, resourceIdentifier: RDAIResourceIdentifier, text: String, languageCode: String?, kind: RDAIPassageKind = .paragraph, locator: RDAILocator, contentHash: String, order: Int ) { self.id = id self.documentIdentifier = documentIdentifier self.resourceIdentifier = resourceIdentifier self.text = text self.languageCode = languageCode self.kind = kind self.locator = locator self.contentHash = contentHash self.order = order } } public enum RDAIEntityKind: String, Codable, Sendable { case person case place case organization case other } public struct RDAIEntityMention: Codable, Equatable, Sendable, Identifiable { public let id: String public let normalizedName: String public let surfaceText: String public let kind: RDAIEntityKind public let confidence: Double public let locator: RDAILocator public init(id: String, normalizedName: String, surfaceText: String, kind: RDAIEntityKind, confidence: Double, locator: RDAILocator) { self.id = id self.normalizedName = normalizedName self.surfaceText = surfaceText self.kind = kind self.confidence = min(max(confidence, 0), 1) self.locator = locator } } public struct RDAIAnalysisResult: Sendable { public let passages: [RDAIPassage] public let entityMentions: [RDAIEntityMention] public init(passages: [RDAIPassage], entityMentions: [RDAIEntityMention]) { self.passages = passages self.entityMentions = entityMentions } } public protocol RDAIAnalyzing: Sendable { func analyze(document: RDAIDocumentDescriptor, snapshot: RDAIResourceSnapshot) async -> RDAIAnalysisResult } public protocol RDAISemanticScoring: Sendable { func score(query: String, text: String, languageCode: String?) async -> Double? } public enum RDAIError: Error, Equatable, Sendable { case disabled case invalidDocument case resourceUnavailable(RDAIResourceIdentifier) case staleCitation case indexingFailed(String) case modelUnavailable(RDAIUnavailableReason) case insufficientEvidence case storageFailure(String) case cancelled } public enum RDAICapability: String, Codable, Sendable { case languageAnalysis, entityExtraction, lexicalSearch, semanticSearch case summarization, questionAnswering, characterRelationships } public enum RDAIUnavailableReason: Equatable, Sendable { case operatingSystemUnsupported, deviceNotEligible, appleIntelligenceNotEnabled case modelNotReady, languageUnsupported(String?), embeddingAssetsUnavailable case providerNotInstalled, unknown(String) } public enum RDAICapabilityAvailability: Equatable, Sendable { case available case degraded(reason: RDAIUnavailableReason) case unavailable(reason: RDAIUnavailableReason) } public protocol RDAICapabilityProviding: Sendable { func availability(for capability: RDAICapability, locale: Locale?) async -> RDAICapabilityAvailability } public enum RDAISummaryLength: String, Codable, Sendable { case brief, standard, detailed } public enum RDAIAnswerStatus: String, Codable, Sendable { case answered, insufficientEvidence, unsupportedLanguage, unavailable } public enum RDAIRelationshipStatus: String, Codable, Sendable { case confirmed, possible, conflicting } public struct RDAIGenerationMetadata: Codable, Sendable { public let providerIdentifier: String public let modelVersion: String? public let promptIdentifier: String public let promptVersion: Int public let generatedAt: Date public let scopeHash: String public init(providerIdentifier: String, modelVersion: String? = nil, promptIdentifier: String, promptVersion: Int, generatedAt: Date = .init(), scopeHash: String) { self.providerIdentifier = providerIdentifier self.modelVersion = modelVersion self.promptIdentifier = promptIdentifier self.promptVersion = promptVersion self.generatedAt = generatedAt self.scopeHash = scopeHash } } public struct RDAISourcedStatement: Codable, Sendable, Identifiable { public let id: String public let text: String public let citationIdentifiers: [String] public init(id: String = UUID().uuidString, text: String, citationIdentifiers: [String]) { self.id = id; self.text = text; self.citationIdentifiers = citationIdentifiers } } public struct RDAISummary: Codable, Sendable { public let title: String public let overview: String public let keyPoints: [RDAISourcedStatement] public let citations: [RDAICitation] public let metadata: RDAIGenerationMetadata } public struct RDAIAnswer: Codable, Sendable { public let status: RDAIAnswerStatus public let text: String public let statements: [RDAISourcedStatement] public let citations: [RDAICitation] public let metadata: RDAIGenerationMetadata } public struct RDAICharacter: Codable, Sendable, Identifiable { public let id: String public let displayName: String public let aliases: [String] public let description: String public let firstAppearance: RDAICitation? public let evidence: [RDAICitation] } public struct RDAIRelationship: Codable, Sendable, Identifiable { public let id: String public let sourceCharacterIdentifier: String public let targetCharacterIdentifier: String public let label: String public let status: RDAIRelationshipStatus public let evidence: [RDAICitation] } public enum RDAIGenerationTask: Sendable { case summary(RDAISummaryLength), answer, characters, relationships } public struct RDAIGenerationRequest: Sendable { public let task: RDAIGenerationTask public let userText: String? public let passages: [RDAIPassage] public let locale: Locale public let scope: RDAIReadScope public init(task: RDAIGenerationTask, userText: String? = nil, passages: [RDAIPassage], locale: Locale = .current, scope: RDAIReadScope) { self.task = task; self.userText = userText; self.passages = passages; self.locale = locale; self.scope = scope } } public enum RDAIGeneratedArtifact: Sendable { case summary(overview: String, statements: [RDAISourcedStatement]) case answer(text: String, statements: [RDAISourcedStatement]) case characters([RDAICharacter]) case relationships([RDAIRelationship]) } public protocol RDAIGenerativeProvider: RDAICapabilityProviding { var identifier: String { get } func generate(_ request: RDAIGenerationRequest) async throws -> RDAIGeneratedArtifact } public enum RDAISpoilerPolicy: String, Codable, Sendable { case readContentOnly, wholeDocument } public enum RDAIDiagnosticsLevel: String, Codable, Sendable { case disabled, metadataOnly } public struct RDAIReaderConfiguration: Sendable { public var isAIEnabled: Bool public var isLocalOnly: Bool public var spoilerPolicy: RDAISpoilerPolicy public var maximumRetrievedPassages: Int public var storesGeneratedArtifacts: Bool public var diagnosticsLevel: RDAIDiagnosticsLevel public init(isAIEnabled: Bool = true, isLocalOnly: Bool = true, spoilerPolicy: RDAISpoilerPolicy = .readContentOnly, maximumRetrievedPassages: Int = 4, storesGeneratedArtifacts: Bool = true, diagnosticsLevel: RDAIDiagnosticsLevel = .metadataOnly) { self.isAIEnabled = isAIEnabled; self.isLocalOnly = isLocalOnly; self.spoilerPolicy = spoilerPolicy; self.maximumRetrievedPassages = max(1, maximumRetrievedPassages); self.storesGeneratedArtifacts = storesGeneratedArtifacts; self.diagnosticsLevel = diagnosticsLevel } public static let `default` = RDAIReaderConfiguration() }