Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## 0.43.1 — Unreleased

### Fixed
- Cost usage: retain an incomplete trailing JSONL record for the next scan so active Codex, Claude, and Pi sessions do not lose newly appended token usage.

## 0.43.0 — 2026-07-14

### Added
Expand Down
15 changes: 13 additions & 2 deletions Sources/CodexBarCore/Vendored/CostUsage/CostUsageJsonl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ enum CostUsageJsonl {
var lineBytes = 0
var truncated = false
var bytesRead: Int64 = 0
var committedOffset = startOffset

func appendSegment(_ bytes: UnsafePointer<UInt8>, count: Int) {
guard count > 0 else { return }
Expand All @@ -71,17 +72,26 @@ enum CostUsageJsonl {
truncated = false
}

func hasCompleteJSONTail() -> Bool {
guard !truncated, lineBytes == current.count else { return false }
return (try? JSONSerialization.jsonObject(with: current, options: [.fragmentsAllowed])) != nil
}

while true {
try checkCancellation?()
let reachedEOF = try autoreleasepool {
let chunk = try handle.read(upToCount: 256 * 1024) ?? Data()
if chunk.isEmpty {
flushLine()
if hasCompleteJSONTail() {
flushLine()
committedOffset = startOffset + bytesRead
}
return true
}

try checkCancellation?()
bytesRead += Int64(chunk.count)
let chunkStartOffset = startOffset + bytesRead - Int64(chunk.count)
chunk.withUnsafeBytes { rawBuffer in
guard let base = rawBuffer.bindMemory(to: UInt8.self).baseAddress else { return }
var segmentStart = 0
Expand All @@ -90,6 +100,7 @@ enum CostUsageJsonl {
if base[index] == 0x0A {
appendSegment(base.advanced(by: segmentStart), count: index - segmentStart)
flushLine()
committedOffset = chunkStartOffset + Int64(index + 1)
segmentStart = index + 1
}
index += 1
Expand All @@ -104,6 +115,6 @@ enum CostUsageJsonl {
try checkCancellation?()
}

return startOffset + bytesRead
return committedOffset
}
}
63 changes: 63 additions & 0 deletions Tests/CodexBarTests/CostUsageJsonlScannerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,69 @@ struct CostUsageJsonlScannerTests {
#expect(scanned[1].wasTruncated == true)
}

@Test
func `jsonl scanner retries an incomplete final record after append`() throws {
let root = try self.makeTemporaryRoot()
defer { try? FileManager.default.removeItem(at: root) }

let fileURL = root.appendingPathComponent("appending.jsonl", isDirectory: false)
let initial = #"{"type":"message","id":"partial"#
try initial.write(to: fileURL, atomically: true, encoding: .utf8)

var firstPass: [String] = []
let resumeOffset = try CostUsageJsonl.scan(
fileURL: fileURL,
maxLineBytes: 1024,
prefixBytes: 1024)
{ line in
firstPass.append(String(decoding: line.bytes, as: UTF8.self))
}

#expect(firstPass.isEmpty)
#expect(resumeOffset == 0)

let completion = #""}"# + "\n"
let handle = try FileHandle(forWritingTo: fileURL)
defer { try? handle.close() }
try handle.seekToEnd()
try handle.write(contentsOf: Data(completion.utf8))

var secondPass: [String] = []
let endOffset = try CostUsageJsonl.scan(
fileURL: fileURL,
offset: resumeOffset,
maxLineBytes: 1024,
prefixBytes: 1024)
{ line in
secondPass.append(String(decoding: line.bytes, as: UTF8.self))
}

#expect(secondPass == [initial + String(completion.dropLast())])
#expect(endOffset == Int64(Data((initial + completion).utf8).count))
}

@Test
func `jsonl scanner accepts a complete final record without newline`() throws {
let root = try self.makeTemporaryRoot()
defer { try? FileManager.default.removeItem(at: root) }

let fileURL = root.appendingPathComponent("final-record.jsonl", isDirectory: false)
let record = #"{"type":"message","id":"complete"}"#
try record.write(to: fileURL, atomically: true, encoding: .utf8)

var scanned: [String] = []
let endOffset = try CostUsageJsonl.scan(
fileURL: fileURL,
maxLineBytes: 1024,
prefixBytes: 1024)
{ line in
scanned.append(String(decoding: line.bytes, as: UTF8.self))
}

#expect(scanned == [record])
#expect(endOffset == Int64(Data(record.utf8).count))
}

private func makeTemporaryRoot() throws -> URL {
let root = FileManager.default.temporaryDirectory.appendingPathComponent(
"codexbar-cost-usage-jsonl-\(UUID().uuidString)",
Expand Down