Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions Sources/CodexLimits/CodexClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ enum CodexClientError: LocalizedError {

final class CodexAppServerConnection: @unchecked Sendable {
let input: FileHandle
let output: FileHandle
let isRunning: () -> Bool
let stop: () -> Void
private let outputDescriptor: Int32
private var bufferedOutput = Data()

init(
Expand All @@ -82,9 +82,15 @@ final class CodexAppServerConnection: @unchecked Sendable {
stop: @escaping () -> Void
) {
self.input = input
self.output = output
self.isRunning = isRunning
self.stop = stop
outputDescriptor = Darwin.dup(output.fileDescriptor)
}

deinit {
if outputDescriptor >= 0 {
Darwin.close(outputDescriptor)
}
}

func readLine() async -> Data? {
Expand All @@ -94,10 +100,21 @@ final class CodexAppServerConnection: @unchecked Sendable {
bufferedOutput.removeSubrange(...newline)
return Data(line)
}
let chunk = await Task.detached { [output] in
output.availableData
let chunk = await Task.detached {
[outputDescriptor] () -> Data? in
var data = Data(count: 64 * 1_024)
let count = data.withUnsafeMutableBytes {
Darwin.read(
outputDescriptor,
$0.baseAddress,
$0.count
)
}
guard count > 0 else { return nil }
data.count = count
return data
}.value
guard !chunk.isEmpty else {
guard let chunk else {
guard !bufferedOutput.isEmpty else { return nil }
defer { bufferedOutput.removeAll() }
return bufferedOutput
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexLimits/MenuContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2316,7 +2316,7 @@ private struct UsageRemainingChart: View {
@ChartContentBuilder
private var observedMarks: some ChartContent {
ForEach(
Array(chart.allObservedSegments.enumerated()),
Array(chart.observedSegments(within: visibleRange).enumerated()),
id: \.offset
) { segmentIndex, segment in
ForEach(segment) { point in
Expand Down
14 changes: 14 additions & 0 deletions Sources/CodexLimits/UsageIntelligenceEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,20 @@ struct UsageChartSnapshot: Equatable, Sendable {
allObservedSegments.flatMap { $0 }
}

func observedSegments(
within range: DateInterval
) -> [[UsageChartPoint]] {
allowanceWindows
.filter { $0.resetsAt > range.start }
.flatMap(\.observedSegments)
.compactMap { segment in
let visible = segment.filter {
$0.date >= range.start && $0.date <= range.end
}
return visible.isEmpty ? nil : visible
}
}

var historicalProjection: [UsageChartPoint] {
reference?.source == .accountHistory ? reference?.points ?? [] : []
}
Expand Down
39 changes: 39 additions & 0 deletions Tests/CodexLimitsTests/AnalyticsWorkspaceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,45 @@ final class AnalyticsWorkspaceTests: XCTestCase {
)
}

func testObservedSegmentsWithinCurrentWindowExcludePriorWindow() {
let currentWindow = DateInterval(
start: Date(timeIntervalSince1970: 4_000),
end: Date(timeIntervalSince1970: 8_000)
)
let currentPoint = UsageChartPoint(
date: Date(timeIntervalSince1970: 5_000),
remaining: 70
)
let chart = UsageChartSnapshot(
observedSource: .account,
target: [],
currentProjection: [],
currentAllowanceReset: currentWindow.end,
allowanceWindows: [
UsageAllowanceWindowSeries(
resetsAt: currentWindow.start,
observedSegments: [[
UsageChartPoint(
date: currentWindow.start,
remaining: 40
)
]]
),
UsageAllowanceWindowSeries(
resetsAt: currentWindow.end,
observedSegments: [[currentPoint]]
)
],
currentRunsFaster: false,
accessibilityValue: "Observed usage"
)

XCTAssertEqual(
chart.observedSegments(within: currentWindow),
[[currentPoint]]
)
}

func testHistoricalUsagePresetsReachBeyondTheCurrentWindow() {
let suite = "AnalyticsWorkspaceTests.historicalUsagePresets"
let defaults = UserDefaults(suiteName: suite)!
Expand Down
34 changes: 34 additions & 0 deletions Tests/CodexLimitsTests/CodexClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,39 @@ final class CodexClientTests: XCTestCase {
XCTAssertEqual(server.initializationCount, 1)
}

func testClosedServerOutputReportsConnectionLost() async {
let client = CodexClient(
makeConnection: {
let requests = Pipe()
let responses = Pipe()
let connection = CodexAppServerConnection(
input: requests.fileHandleForWriting,
output: responses.fileHandleForReading,
isRunning: { true },
stop: {
try? requests.fileHandleForWriting.close()
try? responses.fileHandleForWriting.close()
}
)
try responses.fileHandleForReading.close()
try responses.fileHandleForWriting.close()
return connection
},
timeout: 1
)

do {
_ = try await client.fetch(
fetchedAt: Date(timeIntervalSince1970: 1_900_000)
)
XCTFail("Expected the closed connection to fail")
} catch CodexClientError.connectionLost {
// Expected.
} catch {
XCTFail("Expected connectionLost, got \(error)")
}
}

func testThreadProjectionReadsReuseTheInitializedAccountSession() async throws {
let server = PersistentAppServerFixture()
let client = CodexClient(
Expand Down Expand Up @@ -1288,6 +1321,7 @@ private final class PersistentAppServerFixture: @unchecked Sendable {
isRunning: { true },
stop: {
try? requests.fileHandleForWriting.close()
try? responses.fileHandleForReading.close()
try? responses.fileHandleForWriting.close()
}
)
Expand Down