Skip to content
Open
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
22 changes: 13 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,31 @@ jobs:
set -euo pipefail
for candidate in /Applications/Xcode_26.1.1.app /Applications/Xcode_26.1.app /Applications/Xcode.app; do
if [[ -d "$candidate" ]]; then
sudo xcode-select -s "${candidate}/Contents/Developer"
sudo xcode-select -s "$candidate"
echo "DEVELOPER_DIR=${candidate}/Contents/Developer" >> "$GITHUB_ENV"
break
fi
done
/usr/bin/xcodebuild -version

- name: Swift toolchain version
- name: Install Swift 6.2 toolchain
run: |
set -euo pipefail
swift --version
swift package --version
curl -L https://download.swift.org/swift-6.2-release/xcode/swift-6.2-RELEASE/swift-6.2-RELEASE-osx.pkg -o /tmp/swift.pkg
sudo installer -pkg /tmp/swift.pkg -target /
echo "/Library/Developer/Toolchains/swift-6.2-RELEASE.xctoolchain/usr/bin" >> "$GITHUB_PATH"
echo "TOOLCHAINS=swift-6.2-RELEASE" >> "$GITHUB_ENV"

- name: Install lint tools
run: ./Scripts/install_lint_tools.sh
run: brew install swiftlint swiftformat

- name: SwiftFormat (lint)
run: swiftformat Sources Tests --lint

- name: Lint
run: ./Scripts/lint.sh lint
- name: SwiftLint
run: swiftlint --strict

- name: Swift Test
run: swift test --no-parallel
run: swift test --parallel

build-linux-cli:
strategy:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/upstream-monitor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 0

Expand Down Expand Up @@ -63,7 +63,7 @@ jobs:

- name: Create or update issue
if: steps.check.outputs.upstream_commits > 0 || steps.check.outputs.quotio_commits > 0
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
const upstreamCommits = '${{ steps.check.outputs.upstream_commits }}';
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/CostHistoryChartMenuView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ struct CostHistoryChartMenuView: View {
for entry in sorted {
guard let costUSD = entry.costUSD, costUSD > 0 else { continue }
guard let date = self.dateFromDayKey(entry.date) else { continue }
let point = Point(date: date, costUSD: costUSD, totalTokens: entry.totalTokens)
let point = Point(date: date, costUSD: costUSD, totalTokens: entry.processedTokens ?? entry.totalTokens)
points.append(point)
pointsByKey[entry.date] = point
entriesByKey[entry.date] = entry
Expand Down
9 changes: 6 additions & 3 deletions Sources/CodexBar/MenuCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,8 @@ extension UsageMenuCardView.Model {
guard let snapshot else { return nil }

let sessionCost = snapshot.sessionCostUSD.map { UsageFormatter.usdString($0) } ?? "—"
let sessionTokens = snapshot.sessionTokens.map { UsageFormatter.tokenCountString($0) }
let sessionTokensValue = snapshot.sessionProcessedTokens ?? snapshot.sessionTokens
let sessionTokens = sessionTokensValue.map { UsageFormatter.tokenCountString($0) }
let sessionLine: String = {
if let sessionTokens {
return "Today: \(sessionCost) · \(sessionTokens) tokens"
Expand All @@ -910,8 +911,10 @@ extension UsageMenuCardView.Model {
}()

let monthCost = snapshot.last30DaysCostUSD.map { UsageFormatter.usdString($0) } ?? "—"
let fallbackTokens = snapshot.daily.compactMap(\.totalTokens).reduce(0, +)
let monthTokensValue = snapshot.last30DaysTokens ?? (fallbackTokens > 0 ? fallbackTokens : nil)
let fallbackTokens = snapshot.daily.compactMap { $0.processedTokens ?? $0.totalTokens }.reduce(0, +)
let monthTokensValue = snapshot.last30DaysProcessedTokens
?? snapshot.last30DaysTokens
?? (fallbackTokens > 0 ? fallbackTokens : nil)
let monthTokens = monthTokensValue.map { UsageFormatter.tokenCountString($0) }
let monthLine: String = {
if let monthTokens {
Expand Down
10 changes: 6 additions & 4 deletions Sources/CodexBar/UsageStore+WidgetSnapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ extension UsageStore {
let dailyUsage = tokenSnapshot?.daily.map { entry in
WidgetSnapshot.DailyUsagePoint(
dayKey: entry.date,
totalTokens: entry.totalTokens,
totalTokens: entry.processedTokens ?? entry.totalTokens,
costUSD: entry.costUSD)
} ?? []

Expand All @@ -56,11 +56,13 @@ extension UsageStore {
from snapshot: CostUsageTokenSnapshot?) -> WidgetSnapshot.TokenUsageSummary?
{
guard let snapshot else { return nil }
let fallbackTokens = snapshot.daily.compactMap(\.totalTokens).reduce(0, +)
let monthTokensValue = snapshot.last30DaysTokens ?? (fallbackTokens > 0 ? fallbackTokens : nil)
let fallbackTokens = snapshot.daily.compactMap { $0.processedTokens ?? $0.totalTokens }.reduce(0, +)
let monthTokensValue = snapshot.last30DaysProcessedTokens
?? snapshot.last30DaysTokens
?? (fallbackTokens > 0 ? fallbackTokens : nil)
return WidgetSnapshot.TokenUsageSummary(
sessionCostUSD: snapshot.sessionCostUSD,
sessionTokens: snapshot.sessionTokens,
sessionTokens: snapshot.sessionProcessedTokens ?? snapshot.sessionTokens,
last30DaysCostUSD: snapshot.last30DaysCostUSD,
last30DaysTokens: monthTokensValue)
}
Expand Down
6 changes: 6 additions & 0 deletions Sources/CodexBarCore/CostUsageFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,16 @@ public struct CostUsageFetcher: Sendable {
let totalTokensFromEntries = daily.data.compactMap(\.totalTokens).reduce(0, +)
let last30DaysTokens = totalTokensFromSummary ?? (totalTokensFromEntries > 0 ? totalTokensFromEntries : nil)

let processedFromSummary = daily.summary?.processedTokens
let processedFromEntries = daily.data.compactMap(\.processedTokens).reduce(0, +)
let last30DaysProcessedTokens = processedFromSummary ?? (processedFromEntries > 0 ? processedFromEntries : nil)

return CostUsageTokenSnapshot(
sessionTokens: currentDay?.totalTokens,
sessionProcessedTokens: currentDay?.processedTokens,
sessionCostUSD: currentDay?.costUSD,
last30DaysTokens: last30DaysTokens,
last30DaysProcessedTokens: last30DaysProcessedTokens,
last30DaysCostUSD: last30DaysCostUSD,
daily: daily.data,
updatedAt: now)
Expand Down
16 changes: 16 additions & 0 deletions Sources/CodexBarCore/CostUsageModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@ import Foundation

public struct CostUsageTokenSnapshot: Sendable, Equatable {
public let sessionTokens: Int?
public let sessionProcessedTokens: Int?
public let sessionCostUSD: Double?
public let last30DaysTokens: Int?
public let last30DaysProcessedTokens: Int?
public let last30DaysCostUSD: Double?
public let daily: [CostUsageDailyReport.Entry]
public let updatedAt: Date

public init(
sessionTokens: Int?,
sessionProcessedTokens: Int? = nil,
sessionCostUSD: Double?,
last30DaysTokens: Int?,
last30DaysProcessedTokens: Int? = nil,
last30DaysCostUSD: Double?,
daily: [CostUsageDailyReport.Entry],
updatedAt: Date)
{
self.sessionTokens = sessionTokens
self.sessionProcessedTokens = sessionProcessedTokens
self.sessionCostUSD = sessionCostUSD
self.last30DaysTokens = last30DaysTokens
self.last30DaysProcessedTokens = last30DaysProcessedTokens
self.last30DaysCostUSD = last30DaysCostUSD
self.daily = daily
self.updatedAt = updatedAt
Expand Down Expand Up @@ -57,6 +63,7 @@ public struct CostUsageDailyReport: Sendable, Decodable {
public let cacheCreationTokens: Int?
public let outputTokens: Int?
public let totalTokens: Int?
public let processedTokens: Int?
public let costUSD: Double?
public let modelsUsed: [String]?
public let modelBreakdowns: [ModelBreakdown]?
Expand All @@ -70,6 +77,7 @@ public struct CostUsageDailyReport: Sendable, Decodable {
case cacheCreationInputTokens
case outputTokens
case totalTokens
case processedTokens
case costUSD
case totalCost
case modelsUsed
Expand All @@ -89,6 +97,7 @@ public struct CostUsageDailyReport: Sendable, Decodable {
?? container.decodeIfPresent(Int.self, forKey: .cacheCreationInputTokens)
self.outputTokens = try container.decodeIfPresent(Int.self, forKey: .outputTokens)
self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)
self.processedTokens = try container.decodeIfPresent(Int.self, forKey: .processedTokens)
self.costUSD =
try container.decodeIfPresent(Double.self, forKey: .costUSD)
?? container.decodeIfPresent(Double.self, forKey: .totalCost)
Expand All @@ -103,6 +112,7 @@ public struct CostUsageDailyReport: Sendable, Decodable {
cacheReadTokens: Int? = nil,
cacheCreationTokens: Int? = nil,
totalTokens: Int?,
processedTokens: Int? = nil,
costUSD: Double?,
modelsUsed: [String]?,
modelBreakdowns: [ModelBreakdown]?)
Expand All @@ -113,6 +123,7 @@ public struct CostUsageDailyReport: Sendable, Decodable {
self.cacheReadTokens = cacheReadTokens
self.cacheCreationTokens = cacheCreationTokens
self.totalTokens = totalTokens
self.processedTokens = processedTokens
self.costUSD = costUSD
self.modelsUsed = modelsUsed
self.modelBreakdowns = modelBreakdowns
Expand Down Expand Up @@ -142,6 +153,7 @@ public struct CostUsageDailyReport: Sendable, Decodable {
public let cacheReadTokens: Int?
public let cacheCreationTokens: Int?
public let totalTokens: Int?
public let processedTokens: Int?
public let totalCostUSD: Double?

private enum CodingKeys: String, CodingKey {
Expand All @@ -152,6 +164,7 @@ public struct CostUsageDailyReport: Sendable, Decodable {
case totalCacheReadTokens
case totalCacheCreationTokens
case totalTokens
case processedTokens
case totalCostUSD
case totalCost
}
Expand All @@ -162,13 +175,15 @@ public struct CostUsageDailyReport: Sendable, Decodable {
cacheReadTokens: Int? = nil,
cacheCreationTokens: Int? = nil,
totalTokens: Int?,
processedTokens: Int? = nil,
totalCostUSD: Double?)
{
self.totalInputTokens = totalInputTokens
self.totalOutputTokens = totalOutputTokens
self.cacheReadTokens = cacheReadTokens
self.cacheCreationTokens = cacheCreationTokens
self.totalTokens = totalTokens
self.processedTokens = processedTokens
self.totalCostUSD = totalCostUSD
}

Expand All @@ -183,6 +198,7 @@ public struct CostUsageDailyReport: Sendable, Decodable {
try container.decodeIfPresent(Int.self, forKey: .cacheCreationTokens)
?? container.decodeIfPresent(Int.self, forKey: .totalCacheCreationTokens)
self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)
self.processedTokens = try container.decodeIfPresent(Int.self, forKey: .processedTokens)
self.totalCostUSD =
try container.decodeIfPresent(Double.self, forKey: .totalCostUSD)
?? container.decodeIfPresent(Double.self, forKey: .totalCost)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,7 @@ extension CostUsageScanner {
var totalCacheRead = 0
var totalCacheCreate = 0
var totalTokens = 0
var totalProcessedTokens = 0
var totalCost: Double = 0
var costSeen = false
let costScale = 1_000_000_000.0
Expand Down Expand Up @@ -539,6 +540,7 @@ extension CostUsageScanner {
let top = Array(breakdown.prefix(3))

let dayTotal = dayInput + dayCacheRead + dayCacheCreate + dayOutput
let dayProcessed = dayInput + dayCacheCreate + dayOutput
let entryCost = dayCostSeen ? dayCost : nil
entries.append(CostUsageDailyReport.Entry(
date: day,
Expand All @@ -547,6 +549,7 @@ extension CostUsageScanner {
cacheReadTokens: dayCacheRead,
cacheCreationTokens: dayCacheCreate,
totalTokens: dayTotal,
processedTokens: dayProcessed,
costUSD: entryCost,
modelsUsed: modelNames,
modelBreakdowns: top))
Expand All @@ -556,6 +559,7 @@ extension CostUsageScanner {
totalCacheRead += dayCacheRead
totalCacheCreate += dayCacheCreate
totalTokens += dayTotal
totalProcessedTokens += dayProcessed
if let entryCost {
totalCost += entryCost
costSeen = true
Expand All @@ -570,6 +574,7 @@ extension CostUsageScanner {
cacheReadTokens: totalCacheRead,
cacheCreationTokens: totalCacheCreate,
totalTokens: totalTokens,
processedTokens: totalProcessedTokens,
totalCostUSD: costSeen ? totalCost : nil)

return CostUsageDailyReport(data: entries, summary: summary)
Expand Down
4 changes: 4 additions & 0 deletions Tests/CodexBarTests/CostUsageScannerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ struct CostUsageScannerTests {
#expect(report.data[0].cacheReadTokens == 25)
#expect(report.data[0].outputTokens == 80)
#expect(report.data[0].totalTokens == 355)
#expect(report.data[0].processedTokens == 330)
#expect((report.data[0].costUSD ?? 0) > 0)
}

Expand Down Expand Up @@ -439,6 +440,7 @@ struct CostUsageScannerTests {
now: day,
options: options)
#expect(firstReport.data.first?.totalTokens == 355)
#expect(firstReport.data.first?.processedTokens == 330)

let second: [String: Any] = [
"type": "assistant",
Expand All @@ -462,6 +464,7 @@ struct CostUsageScannerTests {
now: day,
options: options)
#expect(secondReport.data.first?.totalTokens == 430)
#expect(secondReport.data.first?.processedTokens == 400)
}

@Test
Expand Down Expand Up @@ -707,6 +710,7 @@ struct CostUsageScannerTests {
#expect(report.data[0].cacheReadTokens == 25)
#expect(report.data[0].outputTokens == 10)
#expect(report.data[0].totalTokens == 185)
#expect(report.data[0].processedTokens == 160)
}

@Test
Expand Down
Loading