fix(cursor): avoid duplicating aggregated agent tools

This commit is contained in:
ozymandiashh 2026-06-30 12:16:13 +03:00
parent 4d21fadd18
commit b40c545a65
2 changed files with 15 additions and 5 deletions

View file

@ -480,9 +480,7 @@ function loadComposerInputTokens(db: SqliteDatabase): Map<string, number> {
try {
const rows = db.query<{ composer_id: string; used: number | null; ctx: number | null }>(COMPOSER_TOKENS_QUERY)
for (const r of rows) {
// Distinguish null (field absent) from 0 (valid token count).
// `used ?? ctx` would fall through on 0, suppressing a real zero.
const tokens = r.used !== null ? r.used : (r.ctx ?? 0)
const tokens = r.used ?? r.ctx ?? 0
if (r.composer_id && tokens > 0) map.set(r.composer_id, tokens)
}
} catch {
@ -641,6 +639,9 @@ function parseBubbles(
let inputTokens = row.input_tokens ?? 0
let outputTokens = row.output_tokens ?? 0
// The conversation's tools/bash attach to the single call that carries its
// real input (its first turn), so they are counted exactly once.
let creditedHere = false
// Current Cursor leaves tokenCount at {0,0}. Use the conversation's real
// context size (promptTokenBreakdown) for input, credited once per
@ -656,6 +657,7 @@ function parseBubbles(
} else {
inputTokens = real
creditedComposers.add(conversationId)
creditedHere = true
}
} else {
inputTokens = Math.ceil(textLen / CHARS_PER_TOKEN)
@ -692,7 +694,7 @@ function parseBubbles(
const languages = extractLanguages(blobToText(row.code_blocks))
const hasCode = languages.length > 0
const agentTurn = agentTools.get(conversationId)
const agentTurn = creditedHere ? agentTools.get(conversationId) : undefined
const cursorTools: string[] = [
...(hasCode ? ['cursor:edit', ...languages.map(l => `lang:${l}`)] : []),
...(agentTurn?.tools ?? []),

View file

@ -198,13 +198,15 @@ describe.skipIf(skipReason !== null)('cursor real context tokens (#575)', () =>
expect(credited).toBeDefined()
})
it('attributes tools and bash commands from agentKv to the correct conversation', async () => {
it('attributes aggregated agentKv tools once in a multi-bubble conversation', async () => {
const composerId = 'eeee1111-2222-3333-4444-555566667777'
const requestId = 'req-001'
const dbPath = buildDb((db) => {
insertComposerData(db, { composerId, totalUsedTokens: 10000 })
insertBubble(db, { composerId, bubbleUuid: 'b1', type: 1, text: 'do stuff', requestId })
insertBubble(db, { composerId, bubbleUuid: 'b2', type: 2, text: 'doing stuff', model: 'gpt-5' })
insertBubble(db, { composerId, bubbleUuid: 'b3', type: 1, text: 'do more stuff', requestId: 'req-002' })
insertBubble(db, { composerId, bubbleUuid: 'b4', type: 2, text: 'doing more stuff', model: 'gpt-5' })
// agentKv with tool calls
insertAgentKv(db, {
blobId: 'akv-1', role: 'user',
@ -228,6 +230,12 @@ describe.skipIf(skipReason !== null)('cursor real context tokens (#575)', () =>
expect(callWithTools!.tools).toContain('Read')
expect(callWithTools!.tools).toContain('Shell')
expect(callWithTools!.bashCommands).toContain('npm test')
const allTools = calls.flatMap(c => c.tools)
const allBashCommands = calls.flatMap(c => c.bashCommands)
expect(allTools.filter(t => t === 'Read').length).toBe(1)
expect(allTools.filter(t => t === 'Shell').length).toBe(1)
expect(allBashCommands.filter(cmd => cmd === 'npm test').length).toBe(1)
})
it('uses conversation model for pricing when input is on a user bubble', async () => {