forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLocalProjectionFinalization.swift
More file actions
93 lines (86 loc) · 2.58 KB
/
Copy pathLocalProjectionFinalization.swift
File metadata and controls
93 lines (86 loc) · 2.58 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
import Foundation
/// Summarizer seam used by conversation-end attach. Production is
/// `ConversationChunkSummarizer`; tests inject a counter or fixture.
protocol ConversationSummarizing: Sendable {
func summarize(
sessionId: Int64,
segments: [TranscriptHash.Segment],
startedAt: Date
) async throws -> StoredClientProjection
func peekStored(sessionId: Int64) async throws -> StoredClientProjection?
}
extension ConversationChunkSummarizer: ConversationSummarizing {
func peekStored(sessionId: Int64) async throws -> StoredClientProjection? {
try await store.load(sessionId: sessionId)
}
}
/// When S11 attaches the S10 stored `client_processing` blob.
///
/// Identified basic (`planGated`) is the only plan that attaches. Paid,
/// unknown, and BYOK fail open so the server still decides. Thermal
/// `.serious+` defers generation but will send a hash-matching stored
/// blob so a retry does not drop a finished local summary.
enum LocalProjectionFinalization {
enum Decision: Equatable, Sendable {
case skip
case generate
case storedOnly
}
static func decision(
flagEnabled: Bool,
entitlement: SubscriptionEntitlementDecision,
thermalState: ProcessInfo.ThermalState
) -> Decision {
guard flagEnabled, entitlement == .planGated else { return .skip }
switch thermalState {
case .serious, .critical:
return .storedOnly
case .nominal, .fair:
return .generate
@unknown default:
return .generate
}
}
static func projection(
decision: Decision,
sessionId: Int64,
segments: [TranscriptHash.Segment],
startedAt: Date,
summarizer: (any ConversationSummarizing)?
) async -> Data? {
guard decision != .skip, let summarizer else { return nil }
do {
switch decision {
case .skip:
return nil
case .storedOnly:
guard let stored = try await summarizer.peekStored(sessionId: sessionId) else {
return nil
}
let digest = TranscriptHash.sha256(segments: segments)
guard stored.transcriptSha256 == digest else { return nil }
return stored.json
case .generate:
let stored = try await summarizer.summarize(
sessionId: sessionId,
segments: segments,
startedAt: startedAt
)
return stored.json
}
} catch {
return nil
}
}
}
extension APIClient.UploadSegment {
var hashSegment: TranscriptHash.Segment {
TranscriptHash.Segment(
speaker: speaker,
speakerId: speaker_id,
isUser: is_user,
personId: person_id,
text: text
)
}
}