|
| 1 | +import Foundation |
| 2 | + |
| 3 | +nonisolated struct PineAdapterContractVersion: Hashable, Comparable, Sendable { |
| 4 | + let major: UInt16 |
| 5 | + let minor: UInt16 |
| 6 | + static func < (lhs: Self, rhs: Self) -> Bool { |
| 7 | + (lhs.major, lhs.minor) < (rhs.major, rhs.minor) |
| 8 | + } |
| 9 | +} |
| 10 | + |
| 11 | +nonisolated struct PineAdapterContractVersionRange: Equatable, Sendable { |
| 12 | + let minimum: PineAdapterContractVersion |
| 13 | + let maximum: PineAdapterContractVersion |
| 14 | + var isValid: Bool { minimum <= maximum } |
| 15 | + func contains(_ value: PineAdapterContractVersion) -> Bool { minimum <= value && value <= maximum } |
| 16 | +} |
| 17 | + |
| 18 | +nonisolated enum AdapterTransport: String, Hashable, Sendable { case ownedStandardIO, authenticatedLocalIPC } |
| 19 | +nonisolated enum AdapterLifecycleScope: String, Hashable, Sendable { case session, run, turn, item } |
| 20 | +nonisolated enum AdapterLifecyclePhase: String, Hashable, Sendable { |
| 21 | + case started, working, waitingForQuestion, waitingForApproval, succeeded, failed, cancelled, settled |
| 22 | +} |
| 23 | +nonisolated enum AdapterEvidence: String, Hashable, Sendable { case tool, fileChange } |
| 24 | +nonisolated enum AdapterReplay: String, Hashable, Sendable { case none, sourceCursor } |
| 25 | +nonisolated enum AdapterOrdering: String, Hashable, Sendable { case unordered, ordered } |
| 26 | +nonisolated enum CoreAuthenticationRequirement: String, Hashable, Sendable { |
| 27 | + case ownedChildPipe, authenticatedPeer |
| 28 | +} |
| 29 | + |
| 30 | +nonisolated enum AdapterProfileError: Error, Equatable, Sendable { |
| 31 | + case emptyLifecycle |
| 32 | + case missingDependency |
| 33 | + case replayRequiresOrdering |
| 34 | + case transportAuthenticationMismatch |
| 35 | +} |
| 36 | + |
| 37 | +nonisolated struct AdapterLifecycleCapabilities: Hashable, Sendable { |
| 38 | + struct Signal: Hashable, Sendable { |
| 39 | + let scope: AdapterLifecycleScope |
| 40 | + let phase: AdapterLifecyclePhase |
| 41 | + } |
| 42 | + |
| 43 | + let signals: Set<Signal> |
| 44 | + let evidence: Set<AdapterEvidence> |
| 45 | + |
| 46 | + init(signals: Set<Signal>, evidence: Set<AdapterEvidence>) throws { |
| 47 | + guard !signals.isEmpty else { throw AdapterProfileError.emptyLifecycle } |
| 48 | + let scopes = Set(signals.map(\.scope)) |
| 49 | + guard !scopes.contains(.item) || scopes.contains(.turn) else { throw AdapterProfileError.missingDependency } |
| 50 | + guard !evidence.contains(.fileChange) || evidence.contains(.tool) else { |
| 51 | + throw AdapterProfileError.missingDependency |
| 52 | + } |
| 53 | + self.signals = signals |
| 54 | + self.evidence = evidence |
| 55 | + } |
| 56 | + |
| 57 | + func authorizes(scope: AdapterLifecycleScope, phase: AdapterLifecyclePhase) -> Bool { |
| 58 | + signals.contains(Signal(scope: scope, phase: phase)) |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | +nonisolated struct AdapterDeliverySemantics: Hashable, Sendable { |
| 63 | + let replay: AdapterReplay |
| 64 | + let ordering: AdapterOrdering |
| 65 | + let minimumAuthentication: CoreAuthenticationRequirement |
| 66 | + |
| 67 | + init( |
| 68 | + replay: AdapterReplay = .none, |
| 69 | + ordering: AdapterOrdering, |
| 70 | + minimumAuthentication: CoreAuthenticationRequirement |
| 71 | + ) throws { |
| 72 | + guard replay == .none || ordering == .ordered else { throw AdapterProfileError.replayRequiresOrdering } |
| 73 | + self.replay = replay |
| 74 | + self.ordering = ordering |
| 75 | + self.minimumAuthentication = minimumAuthentication |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +nonisolated struct AdapterCapabilityProfile: Hashable, Sendable { |
| 80 | + let transport: AdapterTransport |
| 81 | + let lifecycle: AdapterLifecycleCapabilities |
| 82 | + let delivery: AdapterDeliverySemantics |
| 83 | + |
| 84 | + var lifecycleSignals: Set<AdapterLifecycleCapabilities.Signal> { lifecycle.signals } |
| 85 | + var evidence: Set<AdapterEvidence> { lifecycle.evidence } |
| 86 | + var replay: AdapterReplay { delivery.replay } |
| 87 | + var ordering: AdapterOrdering { delivery.ordering } |
| 88 | + var minimumAuthentication: CoreAuthenticationRequirement { delivery.minimumAuthentication } |
| 89 | + |
| 90 | + init( |
| 91 | + transport: AdapterTransport, |
| 92 | + lifecycle: AdapterLifecycleCapabilities, |
| 93 | + delivery: AdapterDeliverySemantics |
| 94 | + ) throws { |
| 95 | + guard (transport == .ownedStandardIO && delivery.minimumAuthentication == .ownedChildPipe) |
| 96 | + || (transport == .authenticatedLocalIPC && delivery.minimumAuthentication == .authenticatedPeer) else { |
| 97 | + throw AdapterProfileError.transportAuthenticationMismatch |
| 98 | + } |
| 99 | + self.transport = transport |
| 100 | + self.lifecycle = lifecycle |
| 101 | + self.delivery = delivery |
| 102 | + } |
| 103 | + |
| 104 | + var deterministicWireValues: [String] { |
| 105 | + (["transport:\(transport.rawValue)", "replay:\(replay.rawValue)", |
| 106 | + "ordering:\(ordering.rawValue)", "authentication:\(minimumAuthentication.rawValue)"] |
| 107 | + + lifecycleSignals.map { "lifecycle:\($0.scope.rawValue):\($0.phase.rawValue)" } |
| 108 | + + evidence.map { "evidence:\($0.rawValue)" }).sorted() |
| 109 | + } |
| 110 | + |
| 111 | + func isSubset(of maximum: Self) -> Bool { |
| 112 | + transport == maximum.transport && lifecycleSignals.isSubset(of: maximum.lifecycleSignals) |
| 113 | + && evidence.isSubset(of: maximum.evidence) |
| 114 | + && replay == maximum.replay && ordering == maximum.ordering |
| 115 | + && minimumAuthentication == maximum.minimumAuthentication |
| 116 | + } |
| 117 | +} |
0 commit comments