mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-07-10 01:29:41 +00:00
feat(antigravity-ide): added support for antigravity IDE (#418)
This commit is contained in:
parent
cf35854b09
commit
35a8518518
5 changed files with 244 additions and 33 deletions
|
|
@ -136,7 +136,7 @@ The `--provider` flag filters any command to a single provider: `codeburn report
|
|||
|
||||
**Gemini CLI** stores sessions as single JSON files. Each session embeds real token counts (input, output, cached, thoughts) per message, so no estimation is needed. Gemini reports input tokens inclusive of cached; CodeBurn subtracts cached from input before pricing to avoid double charging.
|
||||
|
||||
**Antigravity CLI** exposes exact usage through a short-lived local process while `agy` is running. Install the optional live hook with `codeburn antigravity-hook install` to capture short CLI sessions even when the menubar's 30-second refresh misses that window. The hook stores sanitized usage totals only, not prompts or local working-directory paths. Remove it with `codeburn antigravity-hook uninstall`; if `--force` replaced an existing statusLine command, uninstall restores that previous command.
|
||||
**Antigravity CLI and IDE** automatically discover conversation session files on disk (stored under `.gemini/` folders) and query the running language server process to extract granular trajectory and pricing information. For the CLI, which runs as a short-lived process, you can optionally install a status line hook using `codeburn antigravity-hook install` to capture usage even when the menubar's 30-second refresh misses it. The IDE is detected automatically via the `--app-data-dir antigravity-ide` flag on Windows.
|
||||
|
||||
**Mistral Vibe** stores sessions as folders under `~/.vibe/logs/session/` (or `$VIBE_HOME/logs/session/`). CodeBurn reads cumulative prompt/completion totals and model pricing from `meta.json`, then reads `messages.jsonl` for the first user prompt and assistant tool calls. Subagent sessions under `agents/` are counted as separate Vibe sessions.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Antigravity
|
||||
|
||||
Google Antigravity. The only provider that does not read files off disk: it speaks to a local language-server RPC endpoint instead.
|
||||
Google Antigravity (CLI and IDE). CodeBurn discovers session files on disk and queries the local language-server RPC endpoint to parse them.
|
||||
|
||||
- **Source:** `src/providers/antigravity.ts`
|
||||
- **Loading:** lazy via `src/providers/index.ts`. Lazy because the protobuf dependency is heavy.
|
||||
|
|
@ -8,21 +8,21 @@ Google Antigravity. The only provider that does not read files off disk: it spea
|
|||
|
||||
## Where it reads from
|
||||
|
||||
A local HTTPS RPC endpoint exposed by Antigravity's language server. The parser:
|
||||
CodeBurn discovers Antigravity sessions from local directories on disk, then queries the live language-server process (if running) to fetch detailed trajectory generator metadata:
|
||||
|
||||
1. Locates the running language-server process via `ps` on POSIX or
|
||||
`Get-CimInstance Win32_Process` on Windows.
|
||||
2. Reads its port and CSRF token from process metadata.
|
||||
3. Calls `GetCascadeTrajectoryGeneratorMetadata` over HTTPS.
|
||||
4. Validates the response (capped at 16 MB).
|
||||
1. **Session Discovery:** It scans the following folders for `.pb` or `.db` files:
|
||||
- **Antigravity CLI:** `%USERPROFILE%\.gemini\antigravity-cli\conversations` (and `implicit`)
|
||||
- **Antigravity App/older path:** `%USERPROFILE%\.gemini\antigravity\conversations`
|
||||
- **Antigravity IDE (VSCode-based):** `%USERPROFILE%\.gemini\antigravity-ide\conversations` (and `implicit`)
|
||||
2. **Language Server RPC Query:** It locates the active language-server process via `ps` on POSIX or `Get-CimInstance Win32_Process` on Windows. It extracts the port and CSRF token from the process arguments, and queries the local HTTPS RPC endpoint `GetCascadeTrajectoryGeneratorMetadata` to parse the session.
|
||||
3. **Cache Fallback:** If the language server is not running, it falls back to the local results cache.
|
||||
|
||||
Antigravity exposes slightly different process flags across platforms:
|
||||
POSIX builds have used `--https_server_port` and `--csrf_token`; Windows
|
||||
builds can expose `--extension_server_port` and
|
||||
`--extension_server_csrf_token`. Both space-separated and `--flag=value`
|
||||
forms are supported.
|
||||
forms are supported. The parser identifies the target app type using the `--app-data-dir` flag (e.g., `antigravity`, `antigravity-cli`, or `antigravity-ide`).
|
||||
|
||||
If the language server is not running, the parser falls back to the cached results file.
|
||||
For Antigravity CLI (`agy`), CodeBurn can also install an opt-in status line
|
||||
hook with `codeburn antigravity-hook install`. The hook records the CLI's
|
||||
sanitized `context_window.current_usage` payload while `agy` is still alive,
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ import { execFile } from 'child_process'
|
|||
import { randomBytes } from 'crypto'
|
||||
import { basename, join } from 'path'
|
||||
import { homedir } from 'os'
|
||||
import { fileURLToPath } from 'url'
|
||||
import https from 'https'
|
||||
|
||||
import { calculateCost } from '../models.js'
|
||||
import { isSqliteAvailable, openDatabase } from '../sqlite.js'
|
||||
import type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js'
|
||||
|
||||
type AntigravityConversationRoot = {
|
||||
|
|
@ -30,6 +32,16 @@ const CONVERSATION_ROOTS: readonly AntigravityConversationRoot[] = [
|
|||
project: 'antigravity-cli',
|
||||
extensions: ['.pb'],
|
||||
},
|
||||
{
|
||||
dir: join(homedir(), '.gemini', 'antigravity-ide', 'conversations'),
|
||||
project: 'antigravity-ide',
|
||||
extensions: ['.pb', '.db'],
|
||||
},
|
||||
{
|
||||
dir: join(homedir(), '.gemini', 'antigravity-ide', 'implicit'),
|
||||
project: 'antigravity-ide',
|
||||
extensions: ['.pb'],
|
||||
},
|
||||
] as const
|
||||
const CACHE_VERSION = 2
|
||||
|
||||
|
|
@ -42,7 +54,7 @@ export type ServerInfo = {
|
|||
}
|
||||
|
||||
type ServerCandidate = ServerInfo & {
|
||||
appDataDir?: 'antigravity' | 'antigravity-cli'
|
||||
appDataDir?: 'antigravity' | 'antigravity-cli' | 'antigravity-ide'
|
||||
}
|
||||
|
||||
type ModelMap = Record<string, string>
|
||||
|
|
@ -69,9 +81,9 @@ export type GeneratorMetadata = {
|
|||
}
|
||||
|
||||
type ModelMapResponse = {
|
||||
models?: Record<string, { model?: string }>
|
||||
models?: Record<string, { model?: string; displayName?: string }>
|
||||
response?: {
|
||||
models?: Record<string, { model?: string }>
|
||||
models?: Record<string, { model?: string; displayName?: string }>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,9 +143,9 @@ let memCache: AntigravityCache | null = null
|
|||
let cacheDirty = false
|
||||
let httpsAgent: https.Agent | undefined
|
||||
|
||||
const SERVER_PORT_FLAGS = ['https_server_port', 'extension_server_port']
|
||||
const CSRF_TOKEN_FLAGS = ['csrf_token', 'extension_server_csrf_token']
|
||||
const APP_DATA_DIR_FLAGS = ['app_data_dir']
|
||||
const SERVER_PORT_FLAGS = ['https_server_port', 'extension_server_port', 'https-server-port', 'extension-server-port']
|
||||
const CSRF_TOKEN_FLAGS = ['csrf_token', 'extension_server_csrf_token', 'csrf-token', 'extension-server-csrf-token']
|
||||
const APP_DATA_DIR_FLAGS = ['app_data_dir', 'app-data-dir']
|
||||
|
||||
function getAgent(): https.Agent {
|
||||
if (!httpsAgent) httpsAgent = new https.Agent({ rejectUnauthorized: false })
|
||||
|
|
@ -174,15 +186,16 @@ function isLikelyCsrfToken(value: string): boolean {
|
|||
return value.length >= 16 && /^[A-Za-z0-9._~:/+=-]+$/.test(value)
|
||||
}
|
||||
|
||||
function normalizeAppDataDir(value: string | null): 'antigravity' | 'antigravity-cli' | undefined {
|
||||
function normalizeAppDataDir(value: string | null): 'antigravity' | 'antigravity-cli' | 'antigravity-ide' | undefined {
|
||||
if (!value) return undefined
|
||||
const normalized = value.replace(/\\/g, '/').toLowerCase()
|
||||
if (normalized.includes('antigravity-ide')) return 'antigravity-ide'
|
||||
if (normalized.includes('antigravity-cli')) return 'antigravity-cli'
|
||||
if (normalized.includes('antigravity')) return 'antigravity'
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function extractAntigravityAppDataDirFromLine(line: string): 'antigravity' | 'antigravity-cli' | undefined {
|
||||
export function extractAntigravityAppDataDirFromLine(line: string): 'antigravity' | 'antigravity-cli' | 'antigravity-ide' | undefined {
|
||||
return normalizeAppDataDir(getFlagValue(line, APP_DATA_DIR_FLAGS))
|
||||
}
|
||||
|
||||
|
|
@ -224,6 +237,35 @@ function parseAntigravityServerCandidates(lines: string[]): ServerCandidate[] {
|
|||
.filter((server): server is ServerCandidate => server !== null)
|
||||
}
|
||||
|
||||
function getCanonicalModelId(key: string, displayName?: string): string {
|
||||
if (displayName) {
|
||||
const lower = displayName.toLowerCase()
|
||||
if (lower.includes('3.5 flash')) {
|
||||
if (lower.includes('high')) return 'gemini-3.5-flash-high'
|
||||
if (lower.includes('medium')) return 'gemini-3.5-flash-medium'
|
||||
if (lower.includes('low')) return 'gemini-3.5-flash-low'
|
||||
return 'gemini-3.5-flash'
|
||||
}
|
||||
if (lower.includes('3.1 pro')) {
|
||||
if (lower.includes('high')) return 'gemini-3.1-pro-high'
|
||||
if (lower.includes('low')) return 'gemini-3.1-pro-low'
|
||||
return 'gemini-3.1-pro'
|
||||
}
|
||||
if (lower.includes('3.1 flash')) {
|
||||
if (lower.includes('image')) return 'gemini-3.1-flash-image'
|
||||
if (lower.includes('lite')) return 'gemini-3.1-flash-lite'
|
||||
return 'gemini-3.1-flash'
|
||||
}
|
||||
if (lower.includes('3 flash')) {
|
||||
return 'gemini-3-flash'
|
||||
}
|
||||
if (lower.includes('3 pro')) {
|
||||
return 'gemini-3-pro'
|
||||
}
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
export function extractAntigravityModelMap(resp: unknown): ModelMap {
|
||||
if (!resp || typeof resp !== 'object') return {}
|
||||
const data = resp as ModelMapResponse
|
||||
|
|
@ -232,7 +274,8 @@ export function extractAntigravityModelMap(resp: unknown): ModelMap {
|
|||
if (!models) return map
|
||||
for (const [key, info] of Object.entries(models)) {
|
||||
if (info && typeof info === 'object' && typeof info.model === 'string') {
|
||||
map[info.model] = key
|
||||
const canonicalKey = getCanonicalModelId(key, info.displayName)
|
||||
map[info.model] = canonicalKey
|
||||
}
|
||||
}
|
||||
return map
|
||||
|
|
@ -312,8 +355,70 @@ async function readProcessCommandLines(): Promise<string[]> {
|
|||
return output.split('\n')
|
||||
}
|
||||
|
||||
async function resolveEphemeralPort(csrfToken: string, appDataDir?: 'antigravity' | 'antigravity-cli'): Promise<ServerInfo | null> {
|
||||
if (process.platform === 'win32') return null
|
||||
async function resolveEphemeralPort(csrfToken: string, appDataDir?: 'antigravity' | 'antigravity-cli' | 'antigravity-ide'): Promise<ServerInfo | null> {
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
const script = [
|
||||
"$ErrorActionPreference = 'SilentlyContinue'",
|
||||
'[Console]::OutputEncoding = [System.Text.Encoding]::UTF8',
|
||||
"Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and $_.CommandLine -like '*language_server*' -and $_.CommandLine -like '*antigravity*' } | ForEach-Object { @{ PID = $_.ProcessId; Cmd = $_.CommandLine } | ConvertTo-Json -Compress }"
|
||||
].join('; ')
|
||||
const output = await execFileText('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script], 5000)
|
||||
|
||||
let targetPid = 0
|
||||
for (const line of output.split(/\r?\n/)) {
|
||||
if (!line.trim()) continue
|
||||
try {
|
||||
const proc = JSON.parse(line) as { PID: number; Cmd: string }
|
||||
const candidate = parseAntigravityServerCandidateFromLine(proc.Cmd)
|
||||
if (candidate && candidate.csrfToken === csrfToken) {
|
||||
if (!appDataDir || !candidate.appDataDir || candidate.appDataDir === appDataDir) {
|
||||
targetPid = proc.PID
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch { /* skip invalid parse */ }
|
||||
}
|
||||
|
||||
if (targetPid === 0) return null
|
||||
|
||||
const portScript = `Get-NetTCPConnection -State Listen -OwningProcess ${targetPid} | Select-Object -ExpandProperty LocalPort`
|
||||
const portOutput = await execFileText('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', portScript], 5000)
|
||||
const ports = portOutput.split(/\r?\n/)
|
||||
.map(p => Number(p.trim()))
|
||||
.filter(p => Number.isInteger(p) && p > 0)
|
||||
|
||||
for (const port of ports) {
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
const req = https.request({
|
||||
hostname: '127.0.0.1',
|
||||
port: port,
|
||||
path: '/exa.language_server_pb.LanguageServerService/GetAvailableModels',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Connect-Protocol-Version': '1',
|
||||
'X-Codeium-Csrf-Token': csrfToken,
|
||||
'Content-Length': 2,
|
||||
},
|
||||
agent: getAgent(),
|
||||
timeout: 1000,
|
||||
}, (res) => {
|
||||
if (res.statusCode === 200) resolve(true)
|
||||
else reject(new Error())
|
||||
})
|
||||
req.on('error', reject)
|
||||
req.write('{}')
|
||||
req.end()
|
||||
})
|
||||
return { port, csrfToken }
|
||||
} catch { /* try next port */ }
|
||||
}
|
||||
} catch { /* best-effort */ }
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const processOutput = await execFileText('ps', ['-ww', '-eo', 'pid=,args='])
|
||||
let pid = ''
|
||||
|
|
@ -341,22 +446,23 @@ async function resolveEphemeralPort(csrfToken: string, appDataDir?: 'antigravity
|
|||
return null
|
||||
}
|
||||
|
||||
export function antigravityAppDataDirFromSourcePath(path: string): 'antigravity' | 'antigravity-cli' {
|
||||
return path.replace(/\\/g, '/').toLowerCase().includes('/.gemini/antigravity-cli/')
|
||||
? 'antigravity-cli'
|
||||
: 'antigravity'
|
||||
export function antigravityAppDataDirFromSourcePath(path: string): 'antigravity' | 'antigravity-cli' | 'antigravity-ide' {
|
||||
const lower = path.replace(/\\/g, '/').toLowerCase()
|
||||
if (lower.includes('/.gemini/antigravity-ide/')) return 'antigravity-ide'
|
||||
if (lower.includes('/.gemini/antigravity-cli/')) return 'antigravity-cli'
|
||||
return 'antigravity'
|
||||
}
|
||||
|
||||
async function detectServer(appDataDir: 'antigravity' | 'antigravity-cli' = 'antigravity'): Promise<ServerInfo | null> {
|
||||
async function detectServer(appDataDir: 'antigravity' | 'antigravity-cli' | 'antigravity-ide' = 'antigravity'): Promise<ServerInfo | null> {
|
||||
if (cachedServers.has(appDataDir)) return cachedServers.get(appDataDir)!
|
||||
try {
|
||||
const candidates = parseAntigravityServerCandidates(await readProcessCommandLines())
|
||||
const info = candidates.find(candidate => candidate.appDataDir === appDataDir)
|
||||
?? (appDataDir === 'antigravity' ? candidates.find(candidate => candidate.appDataDir === undefined) : undefined)
|
||||
?? null
|
||||
if (info && info.port > 0) {
|
||||
if (info && info.port > 0 && appDataDir !== 'antigravity-ide') {
|
||||
cachedServers.set(appDataDir, { port: info.port, csrfToken: info.csrfToken })
|
||||
} else if (info && info.port === 0) {
|
||||
} else if (info) {
|
||||
cachedServers.set(appDataDir, await resolveEphemeralPort(info.csrfToken, appDataDir))
|
||||
} else {
|
||||
cachedServers.set(appDataDir, null)
|
||||
|
|
@ -768,10 +874,11 @@ export function shouldReparseAntigravitySource(path: string, cachedTurnCount: nu
|
|||
|
||||
async function findCascadeSource(cascadeId: string): Promise<SessionSource | null> {
|
||||
const sources = await discoverAntigravitySessionSources()
|
||||
return sources.find(source =>
|
||||
source.path.replace(/\\/g, '/').toLowerCase().includes('/.gemini/antigravity-cli/') &&
|
||||
antigravityCascadeIdFromPath(source.path) === cascadeId
|
||||
) ?? null
|
||||
return sources.find(source => {
|
||||
const lower = source.path.replace(/\\/g, '/').toLowerCase()
|
||||
return (lower.includes('/.gemini/antigravity-cli/') || lower.includes('/.gemini/antigravity-ide/')) &&
|
||||
antigravityCascadeIdFromPath(source.path) === cascadeId
|
||||
}) ?? null
|
||||
}
|
||||
|
||||
export async function snapshotAntigravityStatusLinePayload(input: unknown): Promise<boolean> {
|
||||
|
|
@ -791,7 +898,7 @@ export async function snapshotAntigravityStatusLinePayload(input: unknown): Prom
|
|||
return true
|
||||
}
|
||||
|
||||
const server = await detectServer('antigravity-cli')
|
||||
const server = await detectServer(antigravityAppDataDirFromSourcePath(source.path))
|
||||
if (!server) return false
|
||||
|
||||
let metadata: GeneratorMetadata[]
|
||||
|
|
@ -813,6 +920,40 @@ export async function snapshotAntigravityStatusLinePayload(input: unknown): Prom
|
|||
}
|
||||
}
|
||||
|
||||
async function extractWorkspacePath(filePath: string): Promise<string | undefined> {
|
||||
let text = ''
|
||||
if (filePath.endsWith('.db') && isSqliteAvailable()) {
|
||||
try {
|
||||
const db = openDatabase(filePath)
|
||||
const rows = db.query<{ data: Uint8Array }>('SELECT data FROM trajectory_metadata_blob')
|
||||
db.close()
|
||||
const textDecoder = new TextDecoder('utf-8', { fatal: false })
|
||||
text = rows.map(r => textDecoder.decode(r.data)).join(' ')
|
||||
} catch { /* ignore and fallback */ }
|
||||
}
|
||||
|
||||
if (!text) {
|
||||
try {
|
||||
text = await readFile(filePath, 'utf-8')
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
const match = text.match(/file:\/\/\/[^\x00-\x1F\x7F"'\s]+/i)
|
||||
if (!match) return undefined
|
||||
|
||||
try {
|
||||
return fileURLToPath(match[0])
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeProject(path: string): string {
|
||||
return basename(path.replace(/\\/g, '/'))
|
||||
}
|
||||
|
||||
function createParser(source: SessionSource, seenKeys: Set<string>): SessionParser {
|
||||
return {
|
||||
async *parse(): AsyncGenerator<ParsedProviderCall> {
|
||||
|
|
@ -830,9 +971,15 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
|
|||
const s = await stat(source.path).catch(() => null)
|
||||
if (!s) return
|
||||
|
||||
const projectPath = await extractWorkspacePath(source.path)
|
||||
|
||||
const cached = cache.cascades[cascadeId]
|
||||
if (cached && cached.mtimeMs === s.mtimeMs && cached.sizeBytes === s.size) {
|
||||
for (const call of cached.calls) {
|
||||
if (projectPath) {
|
||||
call.projectPath = projectPath
|
||||
call.project = sanitizeProject(projectPath)
|
||||
}
|
||||
if (seenKeys.has(call.deduplicationKey)) continue
|
||||
seenKeys.add(call.deduplicationKey)
|
||||
yield call
|
||||
|
|
@ -844,6 +991,10 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
|
|||
if (!server) {
|
||||
if (cached) {
|
||||
for (const call of cached.calls) {
|
||||
if (projectPath) {
|
||||
call.projectPath = projectPath
|
||||
call.project = sanitizeProject(projectPath)
|
||||
}
|
||||
if (seenKeys.has(call.deduplicationKey)) continue
|
||||
seenKeys.add(call.deduplicationKey)
|
||||
yield call
|
||||
|
|
@ -862,6 +1013,10 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
|
|||
} catch {
|
||||
if (cached) {
|
||||
for (const call of cached.calls) {
|
||||
if (projectPath) {
|
||||
call.projectPath = projectPath
|
||||
call.project = sanitizeProject(projectPath)
|
||||
}
|
||||
if (seenKeys.has(call.deduplicationKey)) continue
|
||||
seenKeys.add(call.deduplicationKey)
|
||||
yield call
|
||||
|
|
@ -871,6 +1026,13 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
|
|||
}
|
||||
|
||||
const results = buildCallsFromGeneratorMetadata(cascadeId, metadata, modelMap)
|
||||
if (projectPath) {
|
||||
const projectName = sanitizeProject(projectPath)
|
||||
for (const call of results) {
|
||||
call.projectPath = projectPath
|
||||
call.project = projectName
|
||||
}
|
||||
}
|
||||
|
||||
cache.cascades[cascadeId] = {
|
||||
mtimeMs: s.mtimeMs,
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ const PROVIDER_PARSE_VERSIONS: Record<string, string> = {
|
|||
'kilo-code': 'worktree-project-grouping-v1',
|
||||
'roo-code': 'worktree-project-grouping-v1',
|
||||
warp: 'worktree-project-grouping-v1',
|
||||
antigravity: 'worktree-project-grouping-v3',
|
||||
}
|
||||
|
||||
// ── Cache Dir ──────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ describe('antigravity provider helpers', () => {
|
|||
expect(extractAntigravityAppDataDirFromLine(
|
||||
'language_server.exe --app_data_dir "C:\\Users\\Admin\\.gemini\\antigravity-cli" --extension_server_port 62225 --extension_server_csrf_token abcdef01-2345-6789-abcd-ef0123456789',
|
||||
)).toBe('antigravity-cli')
|
||||
|
||||
expect(extractAntigravityAppDataDirFromLine(
|
||||
'language_server_windows_x64.exe --app_data_dir antigravity-ide --extension_server_port 8720 --extension_server_csrf_token 39800f1b-343a-40b0-8eb5-850702450346',
|
||||
)).toBe('antigravity-ide')
|
||||
})
|
||||
|
||||
it('accepts Antigravity 2 ephemeral port zero', () => {
|
||||
|
|
@ -135,6 +139,9 @@ describe('antigravity provider helpers', () => {
|
|||
expect(extractAntigravityModelMap({
|
||||
models: { bad: null, good: { model: 'MODEL_PLACEHOLDER_M9' } },
|
||||
})).toEqual({ MODEL_PLACEHOLDER_M9: 'good' })
|
||||
expect(extractAntigravityModelMap({
|
||||
models: { 'gemini-3-flash-agent': { model: 'MODEL_PLACEHOLDER_M133', displayName: 'Gemini 3.5 Flash (High)' } },
|
||||
})).toEqual({ MODEL_PLACEHOLDER_M133: 'gemini-3.5-flash-high' })
|
||||
expect(extractAntigravityModelMap(null)).toEqual({})
|
||||
})
|
||||
|
||||
|
|
@ -175,6 +182,14 @@ describe('antigravity provider helpers', () => {
|
|||
expect(antigravityAppDataDirFromSourcePath(
|
||||
'C:\\Users\\Admin\\.gemini\\antigravity-cli\\implicit\\session.pb',
|
||||
)).toBe('antigravity-cli')
|
||||
|
||||
expect(antigravityAppDataDirFromSourcePath(
|
||||
'/Users/dev/.gemini/antigravity-ide/conversations/session.db',
|
||||
)).toBe('antigravity-ide')
|
||||
|
||||
expect(antigravityAppDataDirFromSourcePath(
|
||||
'C:\\Users\\Admin\\.gemini\\antigravity-ide\\implicit\\session.pb',
|
||||
)).toBe('antigravity-ide')
|
||||
})
|
||||
|
||||
it('discovers legacy .pb files and Antigravity 2 .db files only', async () => {
|
||||
|
|
@ -203,6 +218,39 @@ describe('antigravity provider helpers', () => {
|
|||
}
|
||||
})
|
||||
|
||||
it('discovers antigravity-ide conversation and implicit files', async () => {
|
||||
const tempHome = await mkdtemp(join(tmpdir(), 'codeburn-home-'))
|
||||
const conversationsDir = join(tempHome, '.gemini', 'antigravity-ide', 'conversations')
|
||||
const implicitDir = join(tempHome, '.gemini', 'antigravity-ide', 'implicit')
|
||||
|
||||
await mkdir(conversationsDir, { recursive: true })
|
||||
await mkdir(implicitDir, { recursive: true })
|
||||
|
||||
await writeFile(join(conversationsDir, 'session1.db'), '')
|
||||
await writeFile(join(implicitDir, 'session2.pb'), '')
|
||||
|
||||
const roots = [
|
||||
{
|
||||
dir: conversationsDir,
|
||||
project: 'antigravity-ide',
|
||||
extensions: ['.pb', '.db'] as const,
|
||||
},
|
||||
{
|
||||
dir: implicitDir,
|
||||
project: 'antigravity-ide',
|
||||
extensions: ['.pb'] as const,
|
||||
},
|
||||
]
|
||||
|
||||
const sources = await discoverAntigravitySessionSources(roots)
|
||||
expect(sources).toEqual([
|
||||
{ path: join(conversationsDir, 'session1.db'), project: 'antigravity-ide', provider: 'antigravity' },
|
||||
{ path: join(implicitDir, 'session2.pb'), project: 'antigravity-ide', provider: 'antigravity' },
|
||||
])
|
||||
|
||||
await rm(tempHome, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
it('displays Gemini 3.5 Flash thinking variants as the base model', () => {
|
||||
const provider = createAntigravityProvider()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue