mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-04-28 06:59:37 +00:00
Merge pull request #145 from getagentseal/fix-cursor-provider-agentKv
Fix Cursor provider for newer versions
This commit is contained in:
commit
8c2fc4ffe8
3 changed files with 167 additions and 4 deletions
|
|
@ -221,9 +221,12 @@ export function getShortModelName(model: string): string {
|
|||
'gpt-4.1-nano': 'GPT-4.1 Nano',
|
||||
'gpt-4.1-mini': 'GPT-4.1 Mini',
|
||||
'gpt-4.1': 'GPT-4.1',
|
||||
'codex-auto-review': 'Codex Auto Review',
|
||||
'gpt-5.4-mini': 'GPT-5.4 Mini',
|
||||
'gpt-5.4': 'GPT-5.4',
|
||||
'gpt-5.3-codex': 'GPT-5.3 Codex',
|
||||
'gpt-5.2-low': 'GPT-5.2 Low',
|
||||
'gpt-5.2': 'GPT-5.2',
|
||||
'gpt-5-mini': 'GPT-5 Mini',
|
||||
'gpt-5': 'GPT-5',
|
||||
'gemini-2.5-pro': 'Gemini 2.5 Pro',
|
||||
|
|
|
|||
|
|
@ -7,9 +7,12 @@ import { calculateCost } from '../models.js'
|
|||
import type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js'
|
||||
|
||||
const modelDisplayNames: Record<string, string> = {
|
||||
'gpt-5.3-codex': 'GPT-5.3 Codex',
|
||||
'codex-auto-review': 'Codex Auto Review',
|
||||
'gpt-5.4-mini': 'GPT-5.4 Mini',
|
||||
'gpt-5.4': 'GPT-5.4',
|
||||
'gpt-5.3-codex': 'GPT-5.3 Codex',
|
||||
'gpt-5.2-low': 'GPT-5.2 Low',
|
||||
'gpt-5.2': 'GPT-5.2',
|
||||
'gpt-5': 'GPT-5',
|
||||
'gpt-4o-mini': 'GPT-4o Mini',
|
||||
'gpt-4o': 'GPT-4o',
|
||||
|
|
@ -132,7 +135,8 @@ async function discoverSessionsInDir(codexDir: string): Promise<SessionSource[]>
|
|||
}
|
||||
|
||||
function resolveModel(info: CodexEntry['payload'], sessionModel?: string): string {
|
||||
return info?.info?.model
|
||||
return info?.model
|
||||
?? info?.info?.model
|
||||
?? info?.info?.model_name
|
||||
?? sessionModel
|
||||
?? 'gpt-5'
|
||||
|
|
@ -164,7 +168,12 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
|
|||
|
||||
if (entry.type === 'session_meta') {
|
||||
sessionId = entry.payload?.session_id ?? basename(source.path, '.jsonl')
|
||||
sessionModel = entry.payload?.model
|
||||
sessionModel = entry.payload?.model ?? sessionModel
|
||||
continue
|
||||
}
|
||||
|
||||
if (entry.type === 'turn_context' && entry.payload?.model) {
|
||||
sessionModel = entry.payload.model
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ const modelDisplayNames: Record<string, string> = {
|
|||
'composer-1': 'Composer 1',
|
||||
'grok-code-fast-1': 'Grok Code Fast',
|
||||
'gemini-3-pro': 'Gemini 3 Pro',
|
||||
'gpt-5.2-low': 'GPT-5.2 Low',
|
||||
'gpt-5.2': 'GPT-5.2',
|
||||
'gpt-5.1-codex-high': 'GPT-5.1 Codex',
|
||||
'gpt-5': 'GPT-5',
|
||||
'gpt-4.1': 'GPT-4.1',
|
||||
|
|
@ -34,6 +36,27 @@ type BubbleRow = {
|
|||
code_blocks: string | null
|
||||
}
|
||||
|
||||
type AgentKvRow = {
|
||||
key: string
|
||||
role: string | null
|
||||
content: string | null
|
||||
request_id: string | null
|
||||
content_length: number
|
||||
}
|
||||
|
||||
type AgentKvContent = {
|
||||
type?: string
|
||||
text?: string
|
||||
providerOptions?: {
|
||||
cursor?: {
|
||||
modelName?: string
|
||||
requestId?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CHARS_PER_TOKEN = 4
|
||||
|
||||
function getCursorDbPath(): string {
|
||||
if (process.platform === 'darwin') {
|
||||
return join(homedir(), 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'state.vscdb')
|
||||
|
|
@ -87,6 +110,19 @@ const BUBBLE_QUERY_BASE = `
|
|||
AND json_extract(value, '$.tokenCount.inputTokens') > 0
|
||||
`
|
||||
|
||||
const AGENTKV_QUERY = `
|
||||
SELECT
|
||||
key,
|
||||
json_extract(value, '$.role') as role,
|
||||
json_extract(value, '$.content') as content,
|
||||
json_extract(value, '$.providerOptions.cursor.requestId') as request_id,
|
||||
length(value) as content_length
|
||||
FROM cursorDiskKV
|
||||
WHERE key LIKE 'agentKv:blob:%'
|
||||
AND hex(substr(value, 1, 1)) = '7B'
|
||||
ORDER BY ROWID ASC
|
||||
`
|
||||
|
||||
const USER_MESSAGES_QUERY = `
|
||||
SELECT
|
||||
json_extract(value, '$.conversationId') as conversation_id,
|
||||
|
|
@ -207,6 +243,116 @@ function parseBubbles(db: SqliteDatabase, seenKeys: Set<string>): { calls: Parse
|
|||
return { calls: results }
|
||||
}
|
||||
|
||||
function extractModelFromContent(content: AgentKvContent[]): string | null {
|
||||
for (const c of content) {
|
||||
if (c.providerOptions?.cursor?.modelName) {
|
||||
return c.providerOptions.cursor.modelName
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function extractTextLength(content: AgentKvContent[]): number {
|
||||
let total = 0
|
||||
for (const c of content) {
|
||||
if (c.text) total += c.text.length
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
function parseAgentKv(db: SqliteDatabase, seenKeys: Set<string>): { calls: ParsedProviderCall[] } {
|
||||
const results: ParsedProviderCall[] = []
|
||||
|
||||
let rows: AgentKvRow[]
|
||||
try {
|
||||
rows = db.query<AgentKvRow>(AGENTKV_QUERY)
|
||||
} catch {
|
||||
return { calls: results }
|
||||
}
|
||||
|
||||
const sessions: Map<string, { inputChars: number; outputChars: number; model: string | null; userText: string }> = new Map()
|
||||
let currentRequestId = 'unknown'
|
||||
let turnIndex = 0
|
||||
|
||||
for (const row of rows) {
|
||||
if (!row.role || !row.content) continue
|
||||
|
||||
let content: AgentKvContent[]
|
||||
try {
|
||||
content = JSON.parse(row.content)
|
||||
if (!Array.isArray(content)) continue
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
|
||||
const requestId = row.request_id ?? currentRequestId
|
||||
if (requestId !== currentRequestId) {
|
||||
currentRequestId = requestId
|
||||
turnIndex = 0
|
||||
}
|
||||
|
||||
const textLength = extractTextLength(content)
|
||||
const model = extractModelFromContent(content)
|
||||
|
||||
if (row.role === 'user') {
|
||||
const existing = sessions.get(requestId) ?? { inputChars: 0, outputChars: 0, model: null, userText: '' }
|
||||
existing.inputChars += textLength
|
||||
if (!existing.userText && content[0]?.text) {
|
||||
const text = content[0].text
|
||||
const queryMatch = text.match(/<user_query>([\s\S]*?)<\/user_query>/)
|
||||
existing.userText = queryMatch ? queryMatch[1].trim().slice(0, 500) : text.slice(0, 500)
|
||||
}
|
||||
sessions.set(requestId, existing)
|
||||
} else if (row.role === 'assistant') {
|
||||
const existing = sessions.get(requestId) ?? { inputChars: 0, outputChars: 0, model: null, userText: '' }
|
||||
existing.outputChars += textLength
|
||||
if (model) existing.model = model
|
||||
sessions.set(requestId, existing)
|
||||
} else if (row.role === 'tool' || row.role === 'system') {
|
||||
const existing = sessions.get(requestId) ?? { inputChars: 0, outputChars: 0, model: null, userText: '' }
|
||||
existing.inputChars += textLength
|
||||
sessions.set(requestId, existing)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [requestId, session] of sessions) {
|
||||
if (session.inputChars === 0 && session.outputChars === 0) continue
|
||||
|
||||
const inputTokens = Math.ceil(session.inputChars / CHARS_PER_TOKEN)
|
||||
const outputTokens = Math.ceil(session.outputChars / CHARS_PER_TOKEN)
|
||||
const dedupKey = `cursor:agentKv:${requestId}`
|
||||
|
||||
if (seenKeys.has(dedupKey)) continue
|
||||
seenKeys.add(dedupKey)
|
||||
|
||||
const pricingModel = resolveModel(session.model)
|
||||
const displayModel = modelForDisplay(session.model)
|
||||
const costUSD = calculateCost(pricingModel, inputTokens, outputTokens, 0, 0, 0)
|
||||
|
||||
results.push({
|
||||
provider: 'cursor',
|
||||
model: displayModel,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
cacheCreationInputTokens: 0,
|
||||
cacheReadInputTokens: 0,
|
||||
cachedInputTokens: 0,
|
||||
reasoningTokens: 0,
|
||||
webSearchRequests: 0,
|
||||
costUSD,
|
||||
tools: [],
|
||||
bashCommands: [],
|
||||
timestamp: new Date().toISOString(),
|
||||
speed: 'standard',
|
||||
deduplicationKey: dedupKey,
|
||||
userMessage: session.userText,
|
||||
sessionId: requestId,
|
||||
})
|
||||
}
|
||||
|
||||
return { calls: results }
|
||||
}
|
||||
|
||||
function createParser(source: SessionSource, seenKeys: Set<string>): SessionParser {
|
||||
return {
|
||||
async *parse(): AsyncGenerator<ParsedProviderCall> {
|
||||
|
|
@ -239,7 +385,12 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
|
|||
return
|
||||
}
|
||||
|
||||
const { calls } = parseBubbles(db, seenKeys)
|
||||
let { calls } = parseBubbles(db, seenKeys)
|
||||
|
||||
if (calls.length === 0) {
|
||||
const agentKvResult = parseAgentKv(db, seenKeys)
|
||||
calls = agentKvResult.calls
|
||||
}
|
||||
|
||||
await writeCachedResults(source.path, calls)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue