codeburn/packages/core/tests/providers/warp-decode.test.ts
iamtoruk 4ef24e9fcd refactor(core): tail migrations — hermes, warp, cursor-agent, quickdesk, devin (phase 8, sqlite batch 2)
Move the pure record decode for five sqlite-backed providers into
@codeburn/core, keeping discovery, sqlite I/O, pricing, and bash
base-name extraction CLI-side behind the dual-registry bridge.

sqlite variant: the host opens the database, runs the SQL, textualizes
BLOBs (warp stylized_command, quickdesk content/tool_names), probes
sqlite_master table variants (quickdesk), and re-throws SQLITE_BUSY so a
transient lock on a live database retries instead of being cached as an
empty result (hermes). The core decoders receive already-normalized rows.

Validator fixes over the drafted migration (original behavior is the
authority in every case):

- warp: restored the `run_command` tool-name check as an identity
  comparison. The draft had rewritten it as a bare Record lookup, so
  'constructor'/'toString'/'__proto__' resolved to inherited Object
  members instead of passing through. Added a regression test.
- warp: restored `safeNumber` to the host's semantics (positive finite
  numbers only). The draft's rewrite accepted numeric strings and
  negative values, changing token budgets and dominant-model selection
  for untrusted conversation_data.
- warp: `estimateTokensFromChars` now mirrors the host helper exactly.
- devin: model DISPLAY names move back CLI-side. The draft shipped a
  7-line regex standing in for the host's `getShortModelName`, which
  mis-rendered every non-Claude model id. The decoder now emits the raw
  generation_model / model_name ids and the CLI formats them.
- devin: dropped the added `costBasis: 'measured'`; the pre-migration
  call carried `costUSD` with no basis marker. Golden corrected.
- quickdesk: fixed an inverted source-variant predicate that routed
  `sourceId: 'metrics'` on a sessions.db path (and `sourceId:
  'sessions-db'` on a metrics path) to the wrong parser.
- cursor-agent: the "unrecognized transcript format" warning is now
  re-emitted from the real decode result via a decode wrapper. The draft
  re-derived `recognized` host-side as `hasUser && hasAssistant`, which
  silently dropped the warning whenever a jsonl transcript had both roles
  but produced no turns.
- cursor-agent: restored the per-conversation summary cache, so the
  attribution database is opened once per conversation id, not per source.
- cursor-agent/devin: the session id is derived once, host-side, and
  passed through, removing a second derivation that could disagree with
  the host's on Windows-style paths.
- hermes: `browser_vision` maps to Vision, matching the original table.

Goldens re-verified by running each bridge fixture through the
pre-migration provider implementation: all five reproduce field-for-field.
2026-07-26 20:26:18 -07:00

201 lines
8.4 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { decodeWarp, toObservations } from '../../src/providers/warp/index.js'
import { ObservationEnvelope } from '../../src/observations.js'
import { OBSERVATION_SCHEMA_VERSION } from '../../src/schema.js'
import type { DecodeContext } from '../../src/contracts.js'
import type { WarpBlockRow, WarpConversationRow, WarpQueryRow } from '../../src/providers/warp/types.js'
const context: DecodeContext = { privacyKey: 'k', providerId: 'warp', sourceRef: 'ref' }
function makeComposite(
conversationId: string,
conversation: WarpConversationRow,
exchanges: WarpQueryRow[],
blocks: WarpBlockRow[] = [],
sourceProject = 'warp',
) {
return { conversationId, conversation, exchanges, blocks, sourceProject }
}
const BASE_CONVERSATION: WarpConversationRow = {
conversation_id: 'conv-a',
conversation_data: JSON.stringify({
conversation_usage_metadata: {
token_usage: [
{
model_id: 'GPT-5.3 Codex (medium reasoning)',
warp_tokens: 300,
byok_tokens: 0,
warp_token_usage_by_category: { primary_agent: 300 },
byok_token_usage_by_category: {},
},
],
},
}),
last_modified_at: '2026-05-18 10:10:00',
}
function makeExchange(id: string, overrides: Partial<WarpQueryRow> = {}): WarpQueryRow {
return {
exchange_id: id,
conversation_id: 'conv-a',
start_ts: '2026-05-18 10:00:00.000000',
input: JSON.stringify([{ Query: { text: 'hello warp' } }]),
working_directory: '/Users/me/projects/codeburn',
output_status: '"Completed"',
model_id: 'auto-efficient',
planning_model_id: '',
coding_model_id: '',
...overrides,
}
}
describe('warp rich decode (moved to @codeburn/core)', () => {
it('decodes a conversation + exchanges into cost-free rich calls', () => {
const exchanges: WarpQueryRow[] = [
makeExchange('ex-1'),
makeExchange('ex-2', { input: JSON.stringify([{ Query: { text: 'a much longer prompt for weighting purposes' } }]) }),
]
const { calls } = decodeWarp({ records: [makeComposite('conv-a', BASE_CONVERSATION, exchanges)], context })
expect(calls).toHaveLength(2)
expect(calls[0]!.provider).toBe('warp')
expect(calls[0]!.sessionId).toBe('conv-a')
expect(calls[0]!.model).toBe('gpt-5.3-codex')
expect(calls[0]!.inputTokens + calls[1]!.inputTokens).toBe(300)
expect(calls[0]!.userMessage).toBe('hello warp')
expect(calls[0]!.deduplicationKey).toBe('warp:conv-a:ex-1')
expect(calls[0]!.project).toBe('Users-me-projects-codeburn')
expect(calls[0]!.projectPath).toBe('/Users/me/projects/codeburn')
expect(calls[0]!).not.toHaveProperty('costBasis')
expect(calls[0]!).not.toHaveProperty('costUSD')
})
it('threads a live seenKeys set so a repeated exchange drops', () => {
const exchanges: WarpQueryRow[] = [makeExchange('ex-1')]
const seen = new Set<string>()
const first = decodeWarp({
records: [makeComposite('conv-a', BASE_CONVERSATION, exchanges)],
context,
seenKeys: seen,
}).calls
expect(first).toHaveLength(1)
const again = decodeWarp({
records: [makeComposite('conv-a', BASE_CONVERSATION, exchanges)],
context,
seenKeys: seen,
}).calls
expect(again).toEqual([])
})
it('skips non-final exchanges', () => {
const exchanges: WarpQueryRow[] = [
makeExchange('ex-final'),
makeExchange('ex-pending', { output_status: '"Pending"' }),
]
const { calls } = decodeWarp({ records: [makeComposite('conv-a', BASE_CONVERSATION, exchanges)], context })
expect(calls).toHaveLength(1)
expect(calls[0]!.deduplicationKey).toBe('warp:conv-a:ex-final')
})
it('skips exchanges with invalid timestamps and does not poison seenKeys', () => {
const exchanges: WarpQueryRow[] = [
makeExchange('ex-bad-ts', { start_ts: 'not-a-timestamp' }),
makeExchange('ex-ok'),
]
const seen = new Set<string>()
const { calls } = decodeWarp({
records: [makeComposite('conv-a', BASE_CONVERSATION, exchanges)],
context,
seenKeys: seen,
})
expect(calls).toHaveLength(1)
expect(calls[0]!.deduplicationKey).toBe('warp:conv-a:ex-ok')
expect(seen.has('warp:conv-a:ex-bad-ts')).toBe(false)
})
it('allocates tokens proportionally by estimated weight', () => {
const exchanges: WarpQueryRow[] = [
makeExchange('ex-short', { input: JSON.stringify([{ Query: { text: 'hi' } }]) }),
makeExchange('ex-long', { input: JSON.stringify([{ Query: { text: 'this is a substantially longer user prompt for weighting' } }]) }),
]
const { calls } = decodeWarp({ records: [makeComposite('conv-a', BASE_CONVERSATION, exchanges)], context })
expect(calls).toHaveLength(2)
expect(calls[0]!.inputTokens + calls[1]!.inputTokens).toBe(300)
expect(calls[1]!.inputTokens).toBeGreaterThan(calls[0]!.inputTokens)
})
it('attributes command blocks to the nearest preceding exchange and emits raw commands', () => {
const exchanges: WarpQueryRow[] = [
makeExchange('ex-a', { start_ts: '2026-05-18 11:00:00.000000' }),
makeExchange('ex-b', { start_ts: '2026-05-18 11:05:00.000000' }),
]
const blocks: WarpBlockRow[] = [
{ block_id: 'block-1', start_ts: '2026-05-18 11:01:00.000000', stylized_command: 'npm test && git status' },
]
const { calls } = decodeWarp({ records: [makeComposite('conv-a', BASE_CONVERSATION, exchanges, blocks)], context })
const callA = calls.find(c => c.deduplicationKey === 'warp:conv-a:ex-a')
const callB = calls.find(c => c.deduplicationKey === 'warp:conv-a:ex-b')
expect(callA).toBeDefined()
expect(callA!.tools).toEqual(['Bash'])
expect(callA!.rawBashCommands).toEqual(['npm test && git status'])
expect(callB!.tools).toEqual([])
expect(callB!.rawBashCommands).toEqual([])
})
it('maps run_command blocks to the canonical Bash tool name', () => {
const exchanges: WarpQueryRow[] = [makeExchange('ex-a')]
const blocks: WarpBlockRow[] = [
{ block_id: 'block-1', start_ts: '2026-05-18 10:00:01.000000', stylized_command: 'ls -la' },
]
const { calls } = decodeWarp({ records: [makeComposite('conv-a', BASE_CONVERSATION, exchanges, blocks)], context })
expect(calls[0]!.tools).toEqual(['Bash'])
})
it('falls back to warp-auto-efficient when no model is available', () => {
const conversation: WarpConversationRow = {
...BASE_CONVERSATION,
conversation_data: JSON.stringify({ conversation_usage_metadata: { token_usage: [] } }),
}
const exchanges: WarpQueryRow[] = [
makeExchange('ex-1', { model_id: '', input: JSON.stringify([{ Query: { text: 'hello' } }]) }),
]
const { calls } = decodeWarp({ records: [makeComposite('conv-a', conversation, exchanges)], context })
expect(calls[0]!.model).toBe('warp-auto-efficient')
})
it('resolves auto-efficient to the dominant model when present', () => {
const exchanges: WarpQueryRow[] = [makeExchange('ex-1', { model_id: 'auto-efficient' })]
const { calls } = decodeWarp({ records: [makeComposite('conv-a', BASE_CONVERSATION, exchanges)], context })
expect(calls[0]!.model).toBe('gpt-5.3-codex')
})
it('uses the fallback token budget when conversation usage is absent', () => {
const conversation: WarpConversationRow = {
...BASE_CONVERSATION,
conversation_data: JSON.stringify({ conversation_usage_metadata: { token_usage: [] } }),
}
const exchanges: WarpQueryRow[] = [makeExchange('ex-1')]
const { calls } = decodeWarp({ records: [makeComposite('conv-a', conversation, exchanges)], context })
expect(calls[0]!.inputTokens).toBeGreaterThan(0)
})
it('toObservations produces a schema-valid, content-free envelope', () => {
const exchanges: WarpQueryRow[] = [makeExchange('ex-1')]
const blocks: WarpBlockRow[] = [
{ block_id: 'block-1', start_ts: '2026-05-18 10:00:01.000000', stylized_command: 'npm test' },
]
const { calls } = decodeWarp({ records: [makeComposite('conv-a', BASE_CONVERSATION, exchanges, blocks)], context })
const { sessions } = toObservations(
{ sessionId: 'conv-a', projectPath: '/Users/me/projects/codeburn', calls },
{ privacyKey: 'test-privacy-key', provider: 'warp' },
)
const envelope = {
schemaVersion: OBSERVATION_SCHEMA_VERSION,
generator: { name: '@codeburn/core', version: '0.0.0-test' },
sessions,
}
expect(ObservationEnvelope.safeParse(envelope).success).toBe(true)
expect(sessions[0]!.calls[0]!.toolNames).toEqual(['Bash'])
})
})