forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathChatFirstDeferralOutboxDriver.swift
More file actions
169 lines (154 loc) · 5.86 KB
/
Copy pathChatFirstDeferralOutboxDriver.swift
File metadata and controls
169 lines (154 loc) · 5.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import Foundation
/// Physical transport for the T08 kernel-owned deferral outbox. It deliberately
/// has no relationship to desktop message reconciliation: the backend receiver
/// persists task-intelligence state only and never writes a Chat row.
struct ChatFirstDeferralDeliveryRequest: Sendable, Equatable {
struct Subject: Codable, Sendable, Equatable {
let kind: String
let id: String
}
struct Option: Codable, Sendable, Equatable {
let optionID: String
let label: String
let preparedAnswer: String
let isDeferred: Bool
enum CodingKeys: String, CodingKey {
case optionID = "option_id"
case label
case preparedAnswer = "prepared_answer"
case isDeferred = "defer"
}
}
struct Question: Codable, Sendable, Equatable {
let type: String = "questionCard"
let questionID: String
let text: String
let subject: Subject
let options: [Option]
enum CodingKeys: String, CodingKey {
case type
case questionID = "question_id"
case text, subject, options
}
}
let ownerID: String
let continuityKey: String
let controlGeneration: Int
let subject: Subject
let question: Question
let attemptCount: Int
let deliveryGeneration: Int
let payloadHash: String
init?(payload: [String: Any]) {
guard
let ownerID = payload["ownerId"] as? String, !ownerID.isEmpty,
let continuityKey = payload["continuityKey"] as? String, !continuityKey.isEmpty,
let controlGeneration = payload["controlGeneration"] as? Int, controlGeneration >= 0,
let subjectPayload = payload["subject"] as? [String: Any],
let subjectKind = subjectPayload["kind"] as? String,
["task", "goal", "capture"].contains(subjectKind),
let subjectID = subjectPayload["id"] as? String, !subjectID.isEmpty,
let questionPayload = payload["question"] as? [String: Any],
let questionID = questionPayload["questionId"] as? String, !questionID.isEmpty,
let questionText = questionPayload["text"] as? String,
!questionText.isEmpty, questionText.count <= 300,
let questionSubject = questionPayload["subject"] as? [String: Any],
let questionSubjectKind = questionSubject["kind"] as? String,
questionSubjectKind == subjectKind,
let questionSubjectID = questionSubject["id"] as? String,
questionSubjectID == subjectID,
let optionsPayload = questionPayload["options"] as? [[String: Any]],
(1...4).contains(optionsPayload.count),
let attemptCount = payload["attemptCount"] as? Int, attemptCount > 0,
let deliveryGeneration = payload["deliveryGeneration"] as? Int, deliveryGeneration > 0,
let payloadHash = payload["payloadHash"] as? String, !payloadHash.isEmpty
else { return nil }
let options = optionsPayload.compactMap { option -> Option? in
guard
let optionID = option["optionId"] as? String, !optionID.isEmpty,
let label = option["label"] as? String, !label.isEmpty, label.count <= 80,
let preparedAnswer = option["preparedAnswer"] as? String,
!preparedAnswer.isEmpty, preparedAnswer.count <= 500
else { return nil }
return Option(
optionID: optionID,
label: label,
preparedAnswer: preparedAnswer,
isDeferred: option["defer"] as? Bool ?? false
)
}
guard options.count == optionsPayload.count,
Set(options.map(\.optionID)).count == options.count,
options.filter(\.isDeferred).count <= 1
else { return nil }
self.ownerID = ownerID
self.continuityKey = continuityKey
self.controlGeneration = controlGeneration
self.subject = Subject(kind: subjectKind, id: subjectID)
self.question = Question(
questionID: questionID,
text: questionText,
subject: Subject(kind: questionSubjectKind, id: questionSubjectID),
options: options
)
self.attemptCount = attemptCount
self.deliveryGeneration = deliveryGeneration
self.payloadHash = payloadHash
}
}
private struct ChatFirstDeferralCreateBody: Encodable {
let sourceSurface: String = "main_chat"
let controlGeneration: Int
let ownerFence: String
let continuityKey: String
let subject: ChatFirstDeferralDeliveryRequest.Subject
let question: ChatFirstDeferralDeliveryRequest.Question
enum CodingKeys: String, CodingKey {
case sourceSurface = "source_surface"
case controlGeneration = "control_generation"
case ownerFence = "owner_fence"
case continuityKey = "continuity_key"
case subject, question
}
}
private struct ChatFirstDeferralReceipt: Decodable {
let deferralID: String
enum CodingKeys: String, CodingKey {
case deferralID = "deferral_id"
}
}
extension APIClient {
func recordChatFirstDeferral(
_ request: ChatFirstDeferralDeliveryRequest
) async throws {
guard await AccountCutoverOfflineUploadAdmission.allowsUploadOffMainActor() else {
throw APIError.accountCutoverOfflineQueueBlocked
}
let body = ChatFirstDeferralCreateBody(
controlGeneration: request.controlGeneration,
ownerFence: request.ownerID,
continuityKey: request.continuityKey,
subject: request.subject,
question: request.question
)
let receipt: ChatFirstDeferralReceipt = try await post(
"v1/chat/deferrals",
body: body,
expectedOwnerId: request.ownerID
)
guard !receipt.deferralID.isEmpty else { throw APIError.invalidResponse }
}
}
extension AgentRuntimeProcess {
nonisolated static func boundedChatFirstDeferralErrorCode(for error: Error) -> String {
if let authError = error as? AuthError, case .userChangedDuringRequest = authError {
return "chat_first_deferral_owner_changed"
}
if case APIError.httpError(let statusCode, _) = error {
return [408, 425, 429].contains(statusCode) || (500...599).contains(statusCode)
? "chat_first_deferral_retryable"
: "chat_first_deferral_4xx"
}
return "chat_first_deferral_failed"
}
}