Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a51d819

Browse files
authored
Fix Antigravity provider detection, Codex fork double-counting, and tab ordering (#372)
Antigravity: - Handle ephemeral port (--https_server_port 0) via lsof fallback - Force reparse when cached turns are 0 (server may have been unavailable) - Persist precomputed costUSD in session cache for correct pricing - Map gemini-pro-agent to gemini-3.1-pro pricing - Add display names for gemini-pro-agent and gemini-3.5-flash-low Codex: - Detect forked sessions via forked_from_id in session_meta - Skip replayed parent events within 5s of fork creation time - Use parent session ID in dedup key so parent+fork don't double-count Menubar: - Sort provider tabs by cost descending instead of enum declaration order
1 parent 17eada2 commit a51d819

4 files changed

Lines changed: 65 additions & 7 deletions

File tree

mac/Sources/CodeBurnMenubar/Views/AgentTabStrip.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,19 @@ struct AgentTabStrip: View {
120120
let detectedKeys = Set(
121121
todayAll.current.providers.keys.map { $0.lowercased() }
122122
)
123-
return ProviderFilter.allCases.filter { filter in
123+
let detected = ProviderFilter.allCases.filter { filter in
124124
if filter == .all { return true }
125125
return filter.providerKeys.contains(where: detectedKeys.contains)
126126
}
127+
let costs = Dictionary(uniqueKeysWithValues: detected.map { ($0, cost(for: $0) ?? 0) })
128+
return detected.sorted { a, b in
129+
if a == .all { return true }
130+
if b == .all { return false }
131+
let ca = costs[a, default: 0]
132+
let cb = costs[b, default: 0]
133+
if ca != cb { return ca > cb }
134+
return a.rawValue < b.rawValue
135+
}
127136
}
128137

129138
private func cost(for filter: ProviderFilter) -> Double? {

src/parser.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1531,7 +1531,7 @@ function providerCallToCachedCall(call: ParsedProviderCall): CachedCall {
15311531
webSearchRequests: call.webSearchRequests,
15321532
cacheCreationOneHourTokens: 0,
15331533
},
1534-
costUSD: call.provider === 'mistral-vibe' ? call.costUSD : undefined,
1534+
costUSD: (call.provider === 'mistral-vibe' || call.provider === 'antigravity') ? call.costUSD : undefined,
15351535
speed: call.speed,
15361536
timestamp: call.timestamp,
15371537
tools: call.tools,
@@ -1681,6 +1681,10 @@ function getOrCreateProviderSection(cache: SessionCache, provider: string): Prov
16811681
}
16821682

16831683
function cachedFileNeedsProviderReparse(providerName: string, cached: CachedFile): boolean {
1684+
// Antigravity data comes from the live server, not from the .pb file.
1685+
// A 0-turn cache entry may just mean the server was unavailable last run.
1686+
if (providerName === 'antigravity' && cached.turns.length === 0) return true
1687+
16841688
if (providerName !== 'gemini') return false
16851689

16861690
return cached.turns.some(turn =>

src/providers/antigravity.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ function isLikelyCsrfToken(value: string): boolean {
111111
return value.length >= 16 && /^[A-Za-z0-9._~:/+=-]+$/.test(value)
112112
}
113113

114-
export function parseAntigravityServerInfoFromLine(line: string): ServerInfo | null {
114+
export function parseAntigravityServerInfoFromLine(line: string): ServerInfo | { port: 0; csrfToken: string } | null {
115115
const lower = line.toLowerCase()
116116
if (!lower.includes('language_server') || !lower.includes('antigravity')) return null
117117

@@ -121,7 +121,7 @@ export function parseAntigravityServerInfoFromLine(line: string): ServerInfo | n
121121
if (!isLikelyCsrfToken(csrfToken)) return null
122122

123123
const port = Number(rawPort)
124-
if (!Number.isInteger(port) || port <= 0 || port > 65535) return null
124+
if (!Number.isInteger(port) || port < 0 || port > 65535) return null
125125

126126
return { port, csrfToken }
127127
}
@@ -222,10 +222,36 @@ async function readProcessCommandLines(): Promise<string[]> {
222222
return output.split('\n')
223223
}
224224

225+
async function resolveEphemeralPort(csrfToken: string): Promise<ServerInfo | null> {
226+
if (process.platform === 'win32') return null
227+
try {
228+
const pidOutput = await execFileText('pgrep', ['-f', 'language_server.*antigravity'])
229+
const pid = pidOutput.trim().split('\n')[0]
230+
if (!pid) return null
231+
const lsofOutput = await execFileText('lsof', ['-a', '-i', '-P', '-n', '-p', pid])
232+
for (const line of lsofOutput.split('\n')) {
233+
if (!line.includes('LISTEN')) continue
234+
const match = line.match(/:(\d+)\s+\(LISTEN\)/)
235+
if (match) {
236+
const port = Number(match[1])
237+
if (port > 0) return { port, csrfToken }
238+
}
239+
}
240+
} catch { /* best-effort */ }
241+
return null
242+
}
243+
225244
async function detectServer(): Promise<ServerInfo | null> {
226245
if (cachedServer !== undefined) return cachedServer
227246
try {
228-
cachedServer = parseAntigravityServerInfo(await readProcessCommandLines())
247+
const info = parseAntigravityServerInfo(await readProcessCommandLines())
248+
if (info && info.port > 0) {
249+
cachedServer = info as ServerInfo
250+
} else if (info && info.port === 0) {
251+
cachedServer = await resolveEphemeralPort(info.csrfToken)
252+
} else {
253+
cachedServer = null
254+
}
229255
return cachedServer
230256
} catch { /* process discovery failed or timed out */ }
231257
cachedServer = null
@@ -291,8 +317,13 @@ async function getModelMap(server: ServerInfo): Promise<ModelMap> {
291317
}
292318

293319
// Strip Antigravity-specific suffixes so the pricing DB can match
320+
const PRICING_ALIASES: Record<string, string> = {
321+
'gemini-pro': 'gemini-3.1-pro',
322+
}
323+
294324
function normalizePricingModel(model: string): string {
295-
return model.replace(/-(high|low|agent)$/, '')
325+
const stripped = model.replace(/-(high|low|agent)$/, '')
326+
return PRICING_ALIASES[stripped] ?? stripped
296327
}
297328

298329
async function discoverSessions(): Promise<SessionSource[]> {
@@ -424,11 +455,13 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
424455
}
425456

426457
const modelDisplayNames: Record<string, string> = {
458+
'gemini-pro-agent': 'Gemini Pro',
427459
'gemini-3-pro': 'Gemini 3 Pro',
428460
'gemini-3.1-pro-high': 'Gemini 3.1 Pro',
429461
'gemini-3.1-pro-low': 'Gemini 3.1 Pro (Low)',
430462
'gemini-3-flash': 'Gemini 3 Flash',
431463
'gemini-3-flash-agent': 'Gemini 3 Flash',
464+
'gemini-3.5-flash-low': 'Gemini 3.5 Flash',
432465
'gemini-3.1-flash-image': 'Gemini 3.1 Flash',
433466
'gemini-3.1-flash-lite': 'Gemini 3.1 Flash Lite',
434467
'claude-opus-4-6-thinking': 'Opus 4.6',

src/providers/codex.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type CodexEntry = {
4444
model_provider?: string
4545
originator?: string
4646
session_id?: string
47+
forked_from_id?: string
4748
model?: string
4849
name?: string
4950
content?: Array<{ type?: string; text?: string }>
@@ -224,6 +225,7 @@ function parseCodexLine(line: string | Buffer): CodexEntry | null {
224225
model_provider: getRawJsonStringField(pHead, 'model_provider'),
225226
originator: getRawJsonStringField(pHead, 'originator'),
226227
session_id: getRawJsonStringField(pHead, 'session_id'),
228+
forked_from_id: getRawJsonStringField(pHead, 'forked_from_id'),
227229
model: getRawJsonStringField(pHead, 'model'),
228230
name: getRawJsonStringField(pHead, 'name'),
229231
},
@@ -315,6 +317,8 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
315317

316318
let sessionModel: string | undefined
317319
let sessionId = ''
320+
let forkedFromId = ''
321+
let forkCutoff = ''
318322
// Null sentinel rather than `0` so the FIRST event is never confused
319323
// with a duplicate. A session that only emits last_token_usage (no
320324
// total_token_usage) reports cumulativeTotal=0 on every event; with a
@@ -345,6 +349,10 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
345349

346350
if (entry.type === 'session_meta') {
347351
sessionId = entry.payload?.session_id ?? basename(source.path, '.jsonl')
352+
forkedFromId = entry.payload?.forked_from_id ?? ''
353+
if (forkedFromId && entry.timestamp) {
354+
forkCutoff = new Date(new Date(entry.timestamp).getTime() + 5000).toISOString()
355+
}
348356
sessionModel = entry.payload?.model ?? sessionModel
349357
continue
350358
}
@@ -383,6 +391,10 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
383391
}
384392

385393
if (entry.type === 'event_msg' && entry.payload?.type === 'token_count') {
394+
// Forked sessions replay the parent's entire event history with
395+
// timestamps clustered at the fork creation time. Skip replayed
396+
// events (within 5s of fork) to avoid double-counting.
397+
if (forkCutoff && entry.timestamp && entry.timestamp < forkCutoff) continue
386398
const info = entry.payload.info
387399
if (!info) {
388400
if (pendingOutputChars === 0 && pendingUserMessage.length === 0) continue
@@ -479,7 +491,7 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
479491

480492
const model = resolveModel(entry.payload, sessionModel)
481493
const timestamp = entry.timestamp ?? ''
482-
const dedupKey = `codex:${sessionId}:${timestamp}:${cumulativeTotal}`
494+
const dedupKey = `codex:${forkedFromId || sessionId}:${cumulativeTotal}`
483495

484496
if (seenKeys.has(dedupKey)) continue
485497
seenKeys.add(dedupKey)

0 commit comments

Comments
 (0)