Files
hapi/ios/Packages/HapiKit/Sources/HapiProtocol/Models/DecryptedMessage.swift
T

92 lines
3.5 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 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?
public init(
id: String,
seq: Int? = nil,
localId: String? = nil,
content: JSONValue = .null,
createdAt: Int,
invokedAt: Int? = nil,
scheduledAt: Int? = nil
) {
self.id = id
self.seq = seq
self.localId = localId
self.content = content
self.createdAt = createdAt
self.invokedAt = invokedAt
self.scheduledAt = scheduledAt
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
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)
}
}
/// 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
}
}