@@ -8,7 +8,29 @@ import https from 'https'
88import { calculateCost } from '../models.js'
99import type { Provider , SessionSource , SessionParser , ParsedProviderCall } from './types.js'
1010
11- const CONVERSATIONS_DIR = join ( homedir ( ) , '.gemini' , 'antigravity' , 'conversations' )
11+ type AntigravityConversationRoot = {
12+ dir : string
13+ project : string
14+ extensions : readonly string [ ]
15+ }
16+
17+ const CONVERSATION_ROOTS : readonly AntigravityConversationRoot [ ] = [
18+ {
19+ dir : join ( homedir ( ) , '.gemini' , 'antigravity' , 'conversations' ) ,
20+ project : 'antigravity' ,
21+ extensions : [ '.pb' , '.db' ] ,
22+ } ,
23+ {
24+ dir : join ( homedir ( ) , '.gemini' , 'antigravity-cli' , 'conversations' ) ,
25+ project : 'antigravity-cli' ,
26+ extensions : [ '.pb' ] ,
27+ } ,
28+ {
29+ dir : join ( homedir ( ) , '.gemini' , 'antigravity-cli' , 'implicit' ) ,
30+ project : 'antigravity-cli' ,
31+ extensions : [ '.pb' ] ,
32+ } ,
33+ ] as const
1234const CACHE_VERSION = 2
1335
1436const RPC_TIMEOUT_MS = 5000
@@ -19,6 +41,10 @@ export type ServerInfo = {
1941 csrfToken : string
2042}
2143
44+ type ServerCandidate = ServerInfo & {
45+ appDataDir ?: 'antigravity' | 'antigravity-cli'
46+ }
47+
2248type ModelMap = Record < string , string >
2349
2450type UsageEntry = {
@@ -67,14 +93,15 @@ type AntigravityCache = {
6793 cascades : Record < string , CachedCascade >
6894}
6995
70- let cachedServer : ServerInfo | null | undefined
71- let cachedModelMap : ModelMap | undefined
96+ const cachedServers = new Map < string , ServerInfo | null > ( )
97+ const cachedModelMaps = new Map < string , ModelMap > ( )
7298let memCache : AntigravityCache | null = null
7399let cacheDirty = false
74100let httpsAgent : https . Agent | undefined
75101
76102const SERVER_PORT_FLAGS = [ 'https_server_port' , 'extension_server_port' ]
77103const CSRF_TOKEN_FLAGS = [ 'csrf_token' , 'extension_server_csrf_token' ]
104+ const APP_DATA_DIR_FLAGS = [ 'app_data_dir' ]
78105
79106function getAgent ( ) : https . Agent {
80107 if ( ! httpsAgent ) httpsAgent = new https . Agent ( { rejectUnauthorized : false } )
@@ -111,7 +138,19 @@ function isLikelyCsrfToken(value: string): boolean {
111138 return value . length >= 16 && / ^ [ A - Z a - z 0 - 9 . _ ~ : / + = - ] + $ / . test ( value )
112139}
113140
114- export function parseAntigravityServerInfoFromLine ( line : string ) : ServerInfo | { port : 0 ; csrfToken : string } | null {
141+ function normalizeAppDataDir ( value : string | null ) : 'antigravity' | 'antigravity-cli' | undefined {
142+ if ( ! value ) return undefined
143+ const normalized = value . replace ( / \\ / g, '/' ) . toLowerCase ( )
144+ if ( normalized . includes ( 'antigravity-cli' ) ) return 'antigravity-cli'
145+ if ( normalized . includes ( 'antigravity' ) ) return 'antigravity'
146+ return undefined
147+ }
148+
149+ export function extractAntigravityAppDataDirFromLine ( line : string ) : 'antigravity' | 'antigravity-cli' | undefined {
150+ return normalizeAppDataDir ( getFlagValue ( line , APP_DATA_DIR_FLAGS ) )
151+ }
152+
153+ function parseAntigravityServerCandidateFromLine ( line : string ) : ServerCandidate | null {
115154 const lower = line . toLowerCase ( )
116155 if ( ! lower . includes ( 'language_server' ) || ! lower . includes ( 'antigravity' ) ) return null
117156
@@ -123,7 +162,16 @@ export function parseAntigravityServerInfoFromLine(line: string): ServerInfo | {
123162 const port = Number ( rawPort )
124163 if ( ! Number . isInteger ( port ) || port < 0 || port > 65535 ) return null
125164
126- return { port, csrfToken }
165+ return {
166+ port,
167+ csrfToken,
168+ appDataDir : extractAntigravityAppDataDirFromLine ( line ) ,
169+ }
170+ }
171+
172+ export function parseAntigravityServerInfoFromLine ( line : string ) : ServerInfo | { port : 0 ; csrfToken : string } | null {
173+ const candidate = parseAntigravityServerCandidateFromLine ( line )
174+ return candidate ? { port : candidate . port , csrfToken : candidate . csrfToken } : null
127175}
128176
129177export function parseAntigravityServerInfo ( lines : string [ ] ) : ServerInfo | null {
@@ -134,6 +182,12 @@ export function parseAntigravityServerInfo(lines: string[]): ServerInfo | null {
134182 return null
135183}
136184
185+ function parseAntigravityServerCandidates ( lines : string [ ] ) : ServerCandidate [ ] {
186+ return lines
187+ . map ( parseAntigravityServerCandidateFromLine )
188+ . filter ( ( server ) : server is ServerCandidate => server !== null )
189+ }
190+
137191export function extractAntigravityModelMap ( resp : unknown ) : ModelMap {
138192 if ( ! resp || typeof resp !== 'object' ) return { }
139193 const data = resp as ModelMapResponse
@@ -222,11 +276,21 @@ async function readProcessCommandLines(): Promise<string[]> {
222276 return output . split ( '\n' )
223277}
224278
225- async function resolveEphemeralPort ( csrfToken : string ) : Promise < ServerInfo | null > {
279+ async function resolveEphemeralPort ( csrfToken : string , appDataDir ?: 'antigravity' | 'antigravity-cli' ) : Promise < ServerInfo | null > {
226280 if ( process . platform === 'win32' ) return null
227281 try {
228- const pidOutput = await execFileText ( 'pgrep' , [ '-f' , 'language_server.*antigravity' ] )
229- const pid = pidOutput . trim ( ) . split ( '\n' ) [ 0 ]
282+ const processOutput = await execFileText ( 'ps' , [ '-ww' , '-eo' , 'pid=,args=' ] )
283+ let pid = ''
284+ for ( const line of processOutput . split ( '\n' ) ) {
285+ const match = line . trim ( ) . match ( / ^ ( \d + ) \s + ( .+ ) $ / )
286+ if ( ! match ) continue
287+ const candidate = parseAntigravityServerCandidateFromLine ( match [ 2 ] ! )
288+ if ( ! candidate ) continue
289+ if ( candidate . csrfToken !== csrfToken ) continue
290+ if ( appDataDir && candidate . appDataDir && candidate . appDataDir !== appDataDir ) continue
291+ pid = match [ 1 ] !
292+ break
293+ }
230294 if ( ! pid ) return null
231295 const lsofOutput = await execFileText ( 'lsof' , [ '-a' , '-i' , '-P' , '-n' , '-p' , pid ] )
232296 for ( const line of lsofOutput . split ( '\n' ) ) {
@@ -241,20 +305,29 @@ async function resolveEphemeralPort(csrfToken: string): Promise<ServerInfo | nul
241305 return null
242306}
243307
244- async function detectServer ( ) : Promise < ServerInfo | null > {
245- if ( cachedServer !== undefined ) return cachedServer
308+ export function antigravityAppDataDirFromSourcePath ( path : string ) : 'antigravity' | 'antigravity-cli' {
309+ return path . replace ( / \\ / g, '/' ) . toLowerCase ( ) . includes ( '/.gemini/antigravity-cli/' )
310+ ? 'antigravity-cli'
311+ : 'antigravity'
312+ }
313+
314+ async function detectServer ( appDataDir : 'antigravity' | 'antigravity-cli' = 'antigravity' ) : Promise < ServerInfo | null > {
315+ if ( cachedServers . has ( appDataDir ) ) return cachedServers . get ( appDataDir ) !
246316 try {
247- const info = parseAntigravityServerInfo ( await readProcessCommandLines ( ) )
317+ const candidates = parseAntigravityServerCandidates ( await readProcessCommandLines ( ) )
318+ const info = candidates . find ( candidate => candidate . appDataDir === appDataDir )
319+ ?? ( appDataDir === 'antigravity' ? candidates . find ( candidate => candidate . appDataDir === undefined ) : undefined )
320+ ?? null
248321 if ( info && info . port > 0 ) {
249- cachedServer = info as ServerInfo
322+ cachedServers . set ( appDataDir , { port : info . port , csrfToken : info . csrfToken } )
250323 } else if ( info && info . port === 0 ) {
251- cachedServer = await resolveEphemeralPort ( info . csrfToken )
324+ cachedServers . set ( appDataDir , await resolveEphemeralPort ( info . csrfToken , appDataDir ) )
252325 } else {
253- cachedServer = null
326+ cachedServers . set ( appDataDir , null )
254327 }
255- return cachedServer
328+ return cachedServers . get ( appDataDir ) !
256329 } catch { /* process discovery failed or timed out */ }
257- cachedServer = null
330+ cachedServers . set ( appDataDir , null )
258331 return null
259332}
260333
@@ -307,13 +380,16 @@ async function rpc(server: ServerInfo, method: string, body: Record<string, unkn
307380}
308381
309382async function getModelMap ( server : ServerInfo ) : Promise < ModelMap > {
383+ const cacheKey = `${ server . port } :${ server . csrfToken } `
384+ const cachedModelMap = cachedModelMaps . get ( cacheKey )
310385 if ( cachedModelMap ) return cachedModelMap
311386 try {
312- cachedModelMap = extractAntigravityModelMap ( await rpc ( server , 'GetAvailableModels' ) )
313- return cachedModelMap
387+ const modelMap = extractAntigravityModelMap ( await rpc ( server , 'GetAvailableModels' ) )
388+ cachedModelMaps . set ( cacheKey , modelMap )
389+ return modelMap
314390 } catch { /* best-effort */ }
315- cachedModelMap = { }
316- return cachedModelMap
391+ cachedModelMaps . set ( cacheKey , { } )
392+ return { }
317393}
318394
319395// Strip Antigravity-specific suffixes so the pricing DB can match
@@ -322,34 +398,50 @@ const PRICING_ALIASES: Record<string, string> = {
322398}
323399
324400function normalizePricingModel ( model : string ) : string {
325- const stripped = model . replace ( / - ( h i g h | l o w | a g e n t ) $ / , '' )
401+ const stripped = model . replace ( / - ( h i g h | m e d i u m | l o w | a g e n t ) $ / , '' )
326402 return PRICING_ALIASES [ stripped ] ?? stripped
327403}
328404
329- async function discoverSessions ( ) : Promise < SessionSource [ ] > {
405+ export function antigravityCascadeIdFromPath ( path : string ) : string {
406+ return basename ( path ) . replace ( / \. ( p b | d b ) $ / i, '' )
407+ }
408+
409+ function isConversationFile ( file : string , extensions : readonly string [ ] ) : boolean {
410+ const lowerFile = file . toLowerCase ( )
411+ return extensions . some ( ext => lowerFile . endsWith ( ext ) )
412+ }
413+
414+ export async function discoverAntigravitySessionSources (
415+ roots : readonly AntigravityConversationRoot [ ] = CONVERSATION_ROOTS ,
416+ ) : Promise < SessionSource [ ] > {
330417 const sources : SessionSource [ ] = [ ]
331- let files : string [ ]
332- try {
333- files = await readdir ( CONVERSATIONS_DIR )
334- } catch {
335- return sources
336- }
418+ for ( const root of roots ) {
419+ let files : string [ ]
420+ try {
421+ files = await readdir ( root . dir )
422+ } catch {
423+ continue
424+ }
337425
338- for ( const file of files ) {
339- if ( ! file . endsWith ( '.pb' ) ) continue
340- sources . push ( {
341- path : join ( CONVERSATIONS_DIR , file ) ,
342- project : 'antigravity' ,
343- provider : 'antigravity' ,
344- } )
426+ for ( const file of files . sort ( ) ) {
427+ if ( ! isConversationFile ( file , root . extensions ) ) continue
428+ const path = join ( root . dir , file )
429+ const s = await stat ( path ) . catch ( ( ) => null )
430+ if ( ! s ?. isFile ( ) ) continue
431+ sources . push ( {
432+ path,
433+ project : root . project ,
434+ provider : 'antigravity' ,
435+ } )
436+ }
345437 }
346438 return sources
347439}
348440
349441function createParser ( source : SessionSource , seenKeys : Set < string > ) : SessionParser {
350442 return {
351443 async * parse ( ) : AsyncGenerator < ParsedProviderCall > {
352- const cascadeId = basename ( source . path , '.pb' )
444+ const cascadeId = antigravityCascadeIdFromPath ( source . path )
353445 const cache = await loadCache ( )
354446
355447 const s = await stat ( source . path ) . catch ( ( ) => null )
@@ -365,7 +457,7 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
365457 return
366458 }
367459
368- const server = await detectServer ( )
460+ const server = await detectServer ( antigravityAppDataDirFromSourcePath ( source . path ) )
369461 if ( ! server ) {
370462 if ( cached ) {
371463 for ( const call of cached . calls ) {
@@ -461,6 +553,9 @@ const modelDisplayNames: Record<string, string> = {
461553 'gemini-3.1-pro-low' : 'Gemini 3.1 Pro (Low)' ,
462554 'gemini-3-flash' : 'Gemini 3 Flash' ,
463555 'gemini-3-flash-agent' : 'Gemini 3 Flash' ,
556+ 'gemini-3.5-flash' : 'Gemini 3.5 Flash' ,
557+ 'gemini-3.5-flash-high' : 'Gemini 3.5 Flash' ,
558+ 'gemini-3.5-flash-medium' : 'Gemini 3.5 Flash' ,
464559 'gemini-3.5-flash-low' : 'Gemini 3.5 Flash' ,
465560 'gemini-3.1-flash-image' : 'Gemini 3.1 Flash' ,
466561 'gemini-3.1-flash-lite' : 'Gemini 3.1 Flash Lite' ,
@@ -482,7 +577,7 @@ export function createAntigravityProvider(): Provider {
482577 } ,
483578
484579 async discoverSessions ( ) : Promise < SessionSource [ ] > {
485- return discoverSessions ( )
580+ return discoverAntigravitySessionSources ( )
486581 } ,
487582
488583 createSessionParser ( source : SessionSource , seenKeys : Set < string > ) : SessionParser {
0 commit comments