Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
65ed68e
fix(grok): report real token usage and list-price cost from CLI sessi…
olddonkey Aug 22, 2026
96af763
feat(grok): count OpenCodex xAI traffic toward the Grok spend row
olddonkey Aug 22, 2026
61d3984
fix(grok): fetch the models.dev catalog on Grok-only installs
olddonkey Aug 22, 2026
8fd6914
test: show cost, provenance and priced-day coverage in the gated Grok…
olddonkey Aug 22, 2026
06e11e0
test: prove the Grok fallback survives repeated probe failures
olddonkey Aug 22, 2026
3ac0ac2
fix(grok): attribute OpenCodex xAI usage only when it is OAuth-backed
olddonkey Aug 22, 2026
a4d4902
Address Grok usage review findings
olddonkey Aug 23, 2026
2869f78
Bound Grok session log scanning
olddonkey Aug 23, 2026
630b7cf
Fix Grok scanner Linux build
olddonkey Aug 23, 2026
ef2fcfc
Document Grok usage pricing
olddonkey Aug 23, 2026
a70e803
Deduplicate compatible parser hash
olddonkey Aug 23, 2026
8ba132b
Fix rebased Grok test integration
olddonkey Aug 24, 2026
f7cdead
Move Grok changelog to 0.55.1
olddonkey Aug 24, 2026
2924c46
Restore Grok menu fallback
olddonkey Aug 24, 2026
eb9b15e
Rescan empty Grok fallback
olddonkey Aug 24, 2026
4e9f3b4
Address Grok review findings
olddonkey Aug 25, 2026
a11f47f
Refresh Grok fallback after preserved failures
olddonkey Aug 25, 2026
cdcfcf9
Use fresh Grok data in spend dashboard
olddonkey Aug 25, 2026
b8bfe9a
Fix Grok rebase integration
olddonkey Aug 25, 2026
be567ee
Restore the Grok usage changelog entry
olddonkey Aug 28, 2026
74dd816
Move the Grok changelog entry into the 0.56.1 section
olddonkey Aug 30, 2026
7eecb4e
Address the ClawSweeper P2 findings on the Grok scan
olddonkey Aug 30, 2026
cb33abb
Redo the parser-hash bookkeeping for the rebased main
olddonkey Aug 30, 2026
fc8dbe6
Price Grok turns from the spend the CLI recorded
olddonkey Sep 1, 2026
a2d85f7
Derive narrowed Grok windows from the days they keep
olddonkey Sep 2, 2026
4f0150b
Preserve Grok cost sources in live history windows
olddonkey Sep 4, 2026
7917383
Preserve Grok cost sources after dashboard filtering
olddonkey Sep 4, 2026
3c33da0
Honor Grok turn-level recorded costs
olddonkey Sep 4, 2026
b9486a6
Refresh Grok integration after main changes
olddonkey Sep 5, 2026
9aba5e0
Include recorded OpenCodex Grok OAuth usage in spend
olddonkey Sep 5, 2026
231714c
Preserve standalone prices and cancel Grok scans promptly
olddonkey Sep 5, 2026
81f1e2e
Preserve OpenCodex custom price overrides
olddonkey Sep 5, 2026
c3919a2
Merge main and preserve compatible cost caches
olddonkey Sep 6, 2026
0874450
Merge main and retain Grok and Poe release notes
olddonkey Sep 6, 2026
6f4a7d1
Merge main and retain Claude warning fixes alongside Grok usage
olddonkey Sep 6, 2026
c35734a
Merge main and preserve Grok cache compatibility
olddonkey Sep 8, 2026
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
### Performance
- Codex local costs: reuse model pricing resolution across daily, project, and session report rows without changing token accounting, tariffs, or refresh cadence (#3476). Thanks @brzvsk!

### Usage & Spend
- Grok: count completed-turn usage from bounded local CLI session-log scans instead of context-window occupancy, and price it from the spend the CLI recorded, falling back to clearly labeled public xAI list prices where it recorded none; standalone OpenCodex xAI history remains token-only unless the user supplies explicit custom prices (#3135, #3345). Thanks @olddonkey and @initH271!
- Usage & Spend: include reported OpenCodex Grok OAuth attempts when log import is enabled and the log records request-time credential provenance. Keep API-key and historic traffic excluded, and label OpenCodex dollars as list-price estimates (#3135). Thanks @olddonkey!

### Fixed
- Widgets: remove redundant outer padding from Usage, Switcher, History, and Metric views so WidgetKit alone controls their content margins (extracted from #3137). Thanks @iamenahs!
- Copilot: resolve Enterprise sign-in identities on the configured host, keep equal user IDs on different hosts distinct, and skip public GitHub budget enrichment for Enterprise accounts (#3341). Thanks @Fletcher-Alderton!
Expand Down
82 changes: 82 additions & 0 deletions Scripts/capture_grok_opencodex_proof.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Capture real OpenCodex usage-writer output using its isolated upstream fixtures."""

import argparse
import hashlib
import json
import pathlib
import re
import subprocess


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--opencodex-root", required=True, type=pathlib.Path)
parser.add_argument("--output-dir", required=True, type=pathlib.Path)
args = parser.parse_args()
root = args.opencodex_root.resolve()
expected = "146ed679c9633e5d68726217fcadc8e0b107339b"
head = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=root, text=True).strip()
dirty = subprocess.check_output(["git", "status", "--porcelain"], cwd=root, text=True).strip()
if head != expected or dirty:
parser.error("Use a clean OpenCodex checkout at " + expected)
output = args.output_dir.resolve()
output.mkdir(parents=True, exist_ok=False)
ledger = output / "usage.jsonl"
source = root / "tests/server/server-xai-oauth-401-replay.test.ts"
original = source.read_text()
captured_test = original.replace(
"mkdtempSync, readFileSync}",
"mkdtempSync, readFileSync, existsSync, appendFileSync}",
1,
).replace(
"afterEach(() => {",
"afterEach(() => {\n if (existsSync(usageLogPath())) appendFileSync("
+ json.dumps(str(ledger)) + ", readFileSync(usageLogPath()));",
1,
).replace(
"return originalFetch(input, init);",
'throw new Error("Unexpected upstream request in isolated producer proof");',
)
captured_test = re.sub(
r'from "(\.[^"]+)"',
lambda match: "from " + json.dumps(str((source.parent / match[1]).resolve())),
captured_test,
)
test_file = output / "producer-proof.test.ts"
test_file.write_text(captured_test)
bun = root / "node_modules/.bin/bun"
version = subprocess.check_output([str(bun), "--version"], cwd=root, text=True).strip()
if version != "1.4.0":
parser.error("Use repository-pinned Bun 1.4.0")
with (output / "terminal.log").open("w") as terminal:
subprocess.run(
[str(bun), "test", str(test_file), "--test-name-pattern",
"401 then 200 performs one refresh and one replay|native Chat records canonical API-key provenance"],
cwd=root, stdout=terminal, stderr=subprocess.STDOUT, check=True,
)
raw = ledger.read_bytes()
rows = [json.loads(line) for line in raw.splitlines()]
assert len(rows) == 2
assert all(secret not in raw for secret in [
b"rejected-access", b"fresh-access", b"initial-refresh", b"xai-test-account", b"Bearer ",
])
attempts = [attempt for row in rows for attempt in row["attempts"]]
assert {row["credentialSource"] for row in attempts} == {"grok-oauth", "xai-api-key"}
result = {
"producerRepository": "https://github.com/lidge-jun/opencodex",
"producerCommit": head,
"bunVersion": version,
"upstream": "isolated fixtures; no live provider requests",
"sha256": hashlib.sha256(raw).hexdigest(),
"rows": len(rows),
"attempts": [{key: row[key] for key in [
"credentialSource", "adapter", "sendCount", "totalTokens",
]} for row in attempts],
}
(output / "capture.json").write_text(json.dumps(result, indent=2) + "\n")
print(json.dumps(result, indent=2))


if __name__ == "__main__":
main()
10 changes: 9 additions & 1 deletion Sources/CodexBar/PreferencesSpendDashboardPane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,15 @@ private struct SpendProviderPanel: View {
.foregroundStyle(.tertiary)
.frame(width: 26, alignment: .leading)
SpendProviderIcon(provider: row.provider, sourceKind: row.sourceKind)
Text(row.displayName).lineLimit(1)
VStack(alignment: .leading, spacing: 2) {
Text(row.displayName).lineLimit(1)
if let disclaimer = row.costDisclaimer {
Text(disclaimer)
.font(.caption2)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
Spacer()
Text(
row.totalCost == nil && row.totalTokens == nil
Expand Down
36 changes: 34 additions & 2 deletions Sources/CodexBar/Providers/Grok/UsageStore+GrokLocalSessions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,23 @@ extension UsageStore {
guard !daily.isEmpty else { return nil }
let tokens = daily.compactMap(\.totalTokens)
let requests = daily.compactMap(\.requestCount)
// Tokens and requests are recomputed from the retained days, so the cost has to be too. Copying the
// published total would render the full 365-day amount beside a 30-day token count.
let costs = daily.compactMap(\.costUSD)

return CostUsageTokenSnapshot(
sessionTokens: published.sessionTokens,
sessionCostUSD: published.sessionCostUSD,
sessionRequests: published.sessionRequests,
last30DaysTokens: tokens.isEmpty ? nil : tokens.reduce(0, +),
last30DaysCostUSD: published.last30DaysCostUSD,
last30DaysCostUSD: costs.isEmpty ? nil : costs.reduce(0, +),
last30DaysRequests: requests.isEmpty ? nil : requests.reduce(0, +),
currencyCode: published.currencyCode,
historyDays: days,
historyCoverageIsEstablished: published.historyCoverageIsEstablished && published.historyDays >= days,
historyLabel: published.historyLabel,
meteredCostUSD: published.meteredCostUSD,
costProvenance: published.costProvenance,
costProvenance: GrokLocalSessionSummary.costProvenance(for: daily, fallback: published.costProvenance),
credentialScopeFingerprint: published.credentialScopeFingerprint,
daily: daily,
projects: published.projects,
Expand All @@ -50,6 +53,35 @@ extension UsageStore {
return summary.toCostUsageTokenSnapshot(historyDays: historyDays)
}

/// Generic window math does not know that Grok's priced rows are CLI-recorded spend. Apply the same
/// source-aware disclosure to live publications and scan results as to the remote-backed projection.
func narrowedGrokTokenSnapshot(_ published: CostUsageTokenSnapshot, historyDays: Int) -> CostUsageTokenSnapshot {
let narrowed = published.narrowed(
toHistoryDays: historyDays,
calendar: self.settings.costUsageBucketCalendar)
return CostUsageTokenSnapshot(
sessionTokens: narrowed.sessionTokens,
sessionCostUSD: narrowed.sessionCostUSD,
sessionRequests: narrowed.sessionRequests,
last30DaysTokens: narrowed.last30DaysTokens,
last30DaysCostUSD: narrowed.last30DaysCostUSD,
last30DaysRequests: narrowed.last30DaysRequests,
currencyCode: narrowed.currencyCode,
historyDays: narrowed.historyDays,
historyCoverageIsEstablished: narrowed.historyCoverageIsEstablished,
historyLabel: narrowed.historyLabel,
meteredCostUSD: narrowed.meteredCostUSD,
costProvenance: GrokLocalSessionSummary.costProvenance(
for: narrowed.daily,
fallback: published.costProvenance),
credentialScopeFingerprint: narrowed.credentialScopeFingerprint,
daily: narrowed.daily,
projects: narrowed.projects,
sessions: narrowed.sessions,
hourly: narrowed.hourly,
updatedAt: narrowed.updatedAt)
}

private static func grokLocalDayKey(for date: Date, calendar: Calendar) -> String? {
let parts = calendar.dateComponents([.year, .month, .day], from: date)
guard let year = parts.year, let month = parts.month, let day = parts.day else { return nil }
Expand Down
23 changes: 14 additions & 9 deletions Sources/CodexBar/SpendDashboardController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -293,15 +293,21 @@ enum SpendDashboardSource {
// Provider-specific by design: Grok local session tokens are independent of the
// remote billing snapshot, so a failed probe still publishes readable logs.
if provider == .grok {
if let snapshot = store.tokenSnapshot(
fromProviderSnapshot: store.snapshot(for: .grok),
provider: .grok,
historyDays: Self.scanDays)
{
let remote = store.snapshot(for: .grok)
let publication = store.tokenSnapshotPublicationForCurrentProviderConfig(for: .grok)
let grokSnapshot = if remote != nil || publication != nil {
store.tokenSnapshotForLiveProviderConsumer(
fromProviderSnapshot: remote,
provider: .grok,
historyDays: Self.scanDays)
} else {
await store.scanAndPublishGrokLocalTokenSnapshot(historyDays: Self.scanDays)
}
if let grokSnapshot {
inputs.append(SpendDashboardModel.ProviderInput(
provider: .grok,
displayName: store.metadata(for: .grok).displayName,
snapshot: snapshot))
snapshot: grokSnapshot))
} else {
confirmedEmptySourceIDs.insert(UsageProvider.grok.rawValue)
}
Expand Down Expand Up @@ -868,10 +874,9 @@ enum SpendDashboardSource {
provider: UsageProvider,
publication: CurrentProviderConfigTokenPublication) -> CostUsageTokenSnapshot?
{
// Provider-specific by design: Grok's catalog input is the local session scan, even when
// the remote billing snapshot is missing.
// Provider-specific by design: a failed Grok probe publishes its detached local scan.
if provider == .grok {
return store.tokenSnapshot(
return store.tokenSnapshotForLiveProviderConsumer(
fromProviderSnapshot: store.snapshot(for: .grok),
provider: .grok,
historyDays: self.scanDays)
Expand Down
16 changes: 15 additions & 1 deletion Sources/CodexBar/SpendDashboardModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ struct SpendDashboardModel: Equatable, Sendable {
let coveredDayCount: Int
let sourceKind: SourceKind

var costDisclaimer: String? {
// Provider-specific by design: Grok local-session dollars are list-price estimates, not billed spend.
guard self.provider == .grok, self.totalCost != nil else { return nil }
return UsageFormatter.costEstimateHint(provider: self.provider)
}

init(
id: String,
rank: Int,
Expand Down Expand Up @@ -489,7 +495,15 @@ struct SpendDashboardModel: Equatable, Sendable {
metered = (metered ?? 0) + meteredCost * summary.costMultiplier
}
if summary.totalCost != nil {
switch summary.input.snapshot.costProvenance {
// Provider-specific by design: Grok owns the recorded-versus-estimated meaning of its row coverage.
let provenance = if summary.input.provider == .grok {
GrokLocalSessionSummary.costProvenance(
for: summary.entries.map(\.entry),
fallback: summary.input.snapshot.costProvenance)
} else {
summary.input.snapshot.costProvenance
}
switch provenance {
case .vendorMetered:
sawVendorMeteredProvenance = true
case .listPriceEstimate:
Expand Down
6 changes: 4 additions & 2 deletions Sources/CodexBar/SpendDashboardSource+OpenCodex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ extension SpendDashboardSource {
_ inputs: [SpendDashboardModel.ProviderInput],
request: SpendDashboardLoadRequest,
environment: [String: String] = ProcessInfo.processInfo.environment,
cacheRoot: URL? = nil,
entryLoader: ((URL) throws -> [OpenCodexUsageEntry])? = nil) -> (
inputs: [SpendDashboardModel.ProviderInput],
observation: SpendDashboardLoadResult.OpenCodexObservation)
Expand All @@ -27,7 +28,7 @@ extension SpendDashboardSource {
guard let logURL = OpenCodexUsageLog.usageLogURL(environment: environment) else {
return (inputs.filter { $0.id != SpendDashboardModel.openCodexSourceID }, .unavailable)
}
let store = OpenCodexUsageStore(cacheRoot: OpenCodexUsageLog.cacheRoot())
let store = OpenCodexUsageStore(cacheRoot: cacheRoot ?? OpenCodexUsageLog.cacheRoot())
let entries: [OpenCodexUsageEntry]
do {
entries = try entryLoader?(logURL) ?? store.loadEntries(logURL: logURL)
Expand Down Expand Up @@ -111,7 +112,8 @@ extension SpendDashboardSource {
supplement,
now: request.now,
historyDays: self.scanDays,
calendar: request.configuration.bucketCalendar),
calendar: request.configuration.bucketCalendar,
provider: input.provider),
tokenActivityCache: input.tokenActivityCache,
sourceKind: input.sourceKind)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/StatusItemController+Menu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1545,7 +1545,7 @@ extension StatusItemController {
}

func tokenSnapshotForCostHistorySubmenu(provider: UsageProvider) -> CostUsageTokenSnapshot? {
let projected = self.store.tokenSnapshot(
let projected = self.store.tokenSnapshotForLiveProviderConsumer(
fromProviderSnapshot: self.store.snapshot(for: provider.instanceID),
provider: provider)
if UsageStore.tokenCostRequiresProviderSnapshot(provider) {
Expand Down
6 changes: 5 additions & 1 deletion Sources/CodexBar/StatusItemController+MenuCardModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ extension StatusItemController {
provider: target,
surface: surface,
override: snapshotOverride)
let projectedTokenSnapshot = self.store.tokenSnapshot(fromProviderSnapshot: snapshot, provider: target)
let projectedTokenSnapshot = if surface == .liveCard {
self.store.tokenSnapshotForLiveProviderConsumer(fromProviderSnapshot: snapshot, provider: target)
} else {
self.store.tokenSnapshot(fromProviderSnapshot: snapshot, provider: target)
}
let storedTokenSnapshot = UsageStore.tokenCostRequiresProviderSnapshot(target)
? nil
: self.store.tokenSnapshot(for: target)
Expand Down
17 changes: 17 additions & 0 deletions Sources/CodexBar/UsageStore+QuotaWarnings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ extension UsageStore {
let displayName: String?
}

func postQuotaWarning(_ event: QuotaWarningEvent, provider: UsageProvider) {
self.sessionQuotaNotifier.postQuotaWarning(
event: event,
provider: provider,
soundEnabled: self.settings.quotaWarningSoundEnabled,
onScreenAlertEnabled: self.settings.quotaWarningOnScreenAlertEnabled)
}

func postPredictivePaceWarning(_ event: PredictivePaceWarningEvent, provider: UsageProvider, now: Date) {
self.sessionQuotaNotifier.postPredictivePaceWarning(
event: event,
provider: provider,
soundEnabled: self.settings.quotaWarningSoundEnabled,
onScreenAlertEnabled: self.settings.quotaWarningOnScreenAlertEnabled,
now: now)
}

func handleQuotaWarningTransitions(
provider: UsageProvider,
snapshot: UsageSnapshot,
Expand Down
26 changes: 11 additions & 15 deletions Sources/CodexBar/UsageStore+Refresh.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1336,17 +1336,20 @@ extension UsageStore {
attempts: [ProviderFetchAttempt],
context: ProviderRefreshOutcomeContext) async
{
// Provider-specific by design: Grok's local fallback scans off the main thread when remote billing fails.
let grokLocalFallback: CostUsageTokenSnapshot? = if provider == .grok {
try? await self.loadGrokLocalTokenSnapshot(historyDays: SpendDashboardSource.scanDays)
} else {
nil
}
guard !Task.isCancelled else { return }
let shouldNotifyPermissionPrompt = Self.isPermissionPromptWaiting(error)
await MainActor.run {
guard self.isCurrentProviderRefreshGeneration(provider, generation: context.generation) else { return }
self.diagnostics[provider.instanceID] = nil
// Provider-specific by design: local ~/.grok/sessions tokens remain readable and
// continue advancing after every remote billing failure, including transient failures
// that preserve the prior provider snapshot.
if provider == .grok {
Task { @MainActor [weak self] in
await self?.scanAndPublishGrokLocalTokenSnapshot(
historyDays: GrokLocalSessionScanner.maximumLookbackDays)
}
}
let restoredClaudeHistory = self.prepareClaudeHistoryFallback(
provider: provider,
usesConsumerAutoPipeline: context.claudeUsesConsumerAutoPipeline,
Expand Down Expand Up @@ -1462,15 +1465,8 @@ extension UsageStore {
self.errors[provider.instanceID] = error.localizedDescription
if !preservesPriorData, !preservesClaudeWebSessionFailure {
self.snapshots.removeValue(forKey: provider.instanceID)
// Provider-specific by design: local ~/.grok/sessions tokens remain readable
// when the remote billing probe fails.
if provider == .grok {
if let local = grokLocalFallback {
self.publishTokenSnapshot(local, for: provider)
} else {
self.clearTokenSnapshot(for: provider)
}
} else if Self.tokenCostRequiresProviderSnapshot(provider) {
// Provider-specific by design: Grok already published its independent local scan above.
if provider != .grok, Self.tokenCostRequiresProviderSnapshot(provider) {
self.clearTokenSnapshot(for: provider)
}
}
Expand Down
Loading
Loading