mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-07 01:21:37 +00:00
Track readable content revisions and durable read cursors in the hub. Acknowledge only rendered foreground tails, persist offline read operations, and preserve newer manual unread choices across devices. Migrate existing history to a read baseline and cover pagination receipts with shared native conformance fixtures.
101 lines
4.0 KiB
Swift
101 lines
4.0 KiB
Swift
import Foundation
|
|
|
|
/// One chat message as returned by `GET /api/sessions/:id/messages` and the
|
|
/// `message-received` SSE event.
|
|
///
|
|
/// Mirrors `DecryptedMessageSchema` (`shared/src/schemas.ts`). `content` is
|
|
/// deliberately left as raw ``JSONValue``: it is the role-wrapped envelope
|
|
/// whose decode tree (`docs/api/client-contract/messages.md`) is ported in the
|
|
/// M2 chat milestones. Decoding a message is total — an absent or `null`
|
|
/// `content` degrades to `.null` instead of throwing.
|
|
public struct DecryptedMessage: Codable, Equatable, Sendable {
|
|
/// Server uuid. Optimistic rows use the client `localId` until echoed.
|
|
public var contentRevision: Int?
|
|
public var id: String
|
|
/// Per-session insert counter; `null` on optimistic rows.
|
|
public var seq: Int?
|
|
/// Client-generated id for optimistic reconciliation.
|
|
public var localId: String?
|
|
/// Role-wrapped envelope, kept wire-verbatim (decoded by the chat pipeline).
|
|
public var content: JSONValue
|
|
/// Hub receive time (epoch ms).
|
|
public var createdAt: Int
|
|
/// When the agent consumed the message (epoch ms). `nil` covers both
|
|
/// wire-`null` (still queued) and an absent key (pre-V8 hubs stamped
|
|
/// rows as already invoked without the field). Current hubs always send
|
|
/// the key, so treating `nil` as "queued" is correct against them; if
|
|
/// pre-V8 hub support is ever needed, the M2 window store must
|
|
/// distinguish explicit null (see pagination.md "Queued semantics").
|
|
public var invokedAt: Int?
|
|
/// Future-scheduled send time (epoch ms), when set.
|
|
public var scheduledAt: Int?
|
|
/// `indeterminate` means a steer outcome is unknown and needs explicit resolution.
|
|
public var deliveryState: String?
|
|
|
|
public init(
|
|
contentRevision: Int? = nil,
|
|
id: String,
|
|
seq: Int? = nil,
|
|
localId: String? = nil,
|
|
content: JSONValue = .null,
|
|
createdAt: Int,
|
|
invokedAt: Int? = nil,
|
|
scheduledAt: Int? = nil,
|
|
deliveryState: String? = nil
|
|
) {
|
|
self.contentRevision = contentRevision
|
|
self.id = id
|
|
self.seq = seq
|
|
self.localId = localId
|
|
self.content = content
|
|
self.createdAt = createdAt
|
|
self.invokedAt = invokedAt
|
|
self.scheduledAt = scheduledAt
|
|
self.deliveryState = deliveryState
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
contentRevision = try container.decodeIfPresent(Int.self, forKey: .contentRevision)
|
|
id = try container.decode(String.self, forKey: .id)
|
|
seq = try container.decodeIfPresent(Int.self, forKey: .seq)
|
|
localId = try container.decodeIfPresent(String.self, forKey: .localId)
|
|
content = try container.decodeIfPresent(JSONValue.self, forKey: .content) ?? .null
|
|
createdAt = try container.decode(Int.self, forKey: .createdAt)
|
|
invokedAt = try container.decodeIfPresent(Int.self, forKey: .invokedAt)
|
|
scheduledAt = try container.decodeIfPresent(Int.self, forKey: .scheduledAt)
|
|
deliveryState = try container.decodeIfPresent(String.self, forKey: .deliveryState)
|
|
}
|
|
}
|
|
|
|
/// Metadata describing an uploaded message attachment.
|
|
///
|
|
/// Mirrors `AttachmentMetadataSchema` (`shared/src/schemas.ts`). Used inside
|
|
/// user-message payloads and in the send-message request body.
|
|
public struct AttachmentMetadata: Codable, Equatable, Sendable {
|
|
public var id: String
|
|
public var filename: String
|
|
public var mimeType: String
|
|
/// Size in bytes.
|
|
public var size: Int
|
|
public var path: String
|
|
/// Web-serving detail; present on wire payloads, absent from fixtures.
|
|
public var previewUrl: String?
|
|
|
|
public init(
|
|
id: String,
|
|
filename: String,
|
|
mimeType: String,
|
|
size: Int,
|
|
path: String,
|
|
previewUrl: String? = nil
|
|
) {
|
|
self.id = id
|
|
self.filename = filename
|
|
self.mimeType = mimeType
|
|
self.size = size
|
|
self.path = path
|
|
self.previewUrl = previewUrl
|
|
}
|
|
}
|