Files
2026-07-13 12:26:02 +08:00

188 lines
6.7 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { mkdtemp, mkdir, writeFile, rm } from 'fs/promises'
import { join } from 'path'
import { tmpdir } from 'os'
import { createGrokProvider } from '../../src/providers/grok.js'
import type { ParsedProviderCall } from '../../src/providers/types.js'
let tmpDir: string
beforeEach(async () => {
tmpDir = await mkdtemp(join(tmpdir(), 'grok-test-'))
})
afterEach(async () => {
await rm(tmpDir, { recursive: true, force: true })
})
// Mirrors the real on-disk layout:
// <sessionsDir>/<url-encoded-cwd>/<uuid>/{summary.json, signals.json, updates.jsonl}
async function writeSession(opts: {
cwdEncoded?: string
uuid?: string
cwd?: string
model?: string
turns?: Array<{ promptId: string; totals: number[] }>
toolCalls?: Array<{ title: string; rawInput: Record<string, unknown> }>
toolsUsed?: string[]
} = {}) {
const cwdEncoded = opts.cwdEncoded ?? '%2FUsers%2Ftest'
const uuid = opts.uuid ?? '019edf9c-0000-7000-8000-000000000001'
const cwd = opts.cwd ?? '/Users/test/myproject'
const model = opts.model ?? 'grok-build'
const dir = join(tmpDir, cwdEncoded, uuid)
await mkdir(dir, { recursive: true })
await writeFile(join(dir, 'summary.json'), JSON.stringify({
info: { id: uuid, cwd },
created_at: '2026-06-19T11:20:40.686261Z',
updated_at: '2026-06-19T11:31:12.282793Z',
last_active_at: '2026-06-19T11:31:12.222328Z',
num_messages: 42,
current_model_id: model,
session_summary: 'User asks about the repo',
generated_title: 'User asks about the repo',
}))
await writeFile(join(dir, 'signals.json'), JSON.stringify({
primaryModelId: model,
modelsUsed: [model],
toolsUsed: opts.toolsUsed ?? ['read_file', 'run_terminal_command', 'grep'],
contextTokensUsed: 40000,
contextWindowTokens: 512000,
}))
const turns = opts.turns ?? [
{ promptId: 'p1', totals: [20000, 25000] },
{ promptId: 'p2', totals: [30000, 35000] },
{ promptId: 'p3', totals: [40000, 45000] },
]
const lines: string[] = []
for (const turn of turns) {
for (const total of turn.totals) {
lines.push(JSON.stringify({
timestamp: '2026-06-19T11:30:00.000Z',
method: 'session/update',
params: {
sessionId: uuid,
update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: 'hi' } },
_meta: { totalTokens: total, promptId: turn.promptId, updateType: 'AgentMessageChunk', modelId: model },
},
}))
}
}
for (const tc of opts.toolCalls ?? [
{ title: 'read_file', rawInput: { target_directory: '.' } },
{ title: 'grep', rawInput: { pattern: 'x' } },
{ title: 'run_terminal_command', rawInput: { command: 'git status' } },
{ title: 'spawn_subagent', rawInput: { subagent_type: 'general-purpose', prompt: 'x' } },
]) {
lines.push(JSON.stringify({
timestamp: '2026-06-19T11:30:05.000Z',
method: 'session/update',
params: { sessionId: uuid, update: { sessionUpdate: 'tool_call', toolCallId: 'c1', title: tc.title, rawInput: tc.rawInput } },
}))
}
await writeFile(join(dir, 'updates.jsonl'), lines.join('\n') + '\n')
return { dir, uuid }
}
describe('grok provider - discovery', () => {
it('discovers each session dir and derives project from cwd', async () => {
await writeSession({ cwd: '/Users/test/myproject' })
const sessions = await createGrokProvider(tmpDir).discoverSessions()
expect(sessions).toHaveLength(1)
expect(sessions[0]!.provider).toBe('grok')
expect(sessions[0]!.project).toBe('myproject')
expect(sessions[0]!.path).toMatch(/updates\.jsonl$/)
})
it('returns empty for a non-existent sessions dir', async () => {
const sessions = await createGrokProvider('/nope/does/not/exist').discoverSessions()
expect(sessions).toEqual([])
})
it('skips directories without a summary.json', async () => {
await mkdir(join(tmpDir, '%2Ftmp', 'not-a-session'), { recursive: true })
const sessions = await createGrokProvider(tmpDir).discoverSessions()
expect(sessions).toEqual([])
})
})
describe('grok provider - parsing', () => {
async function parse(seen = new Set<string>()) {
const provider = createGrokProvider(tmpDir)
const [source] = await provider.discoverSessions()
const calls: ParsedProviderCall[] = []
if (!source) return calls
for await (const call of provider.createSessionParser(source, seen).parse()) {
calls.push(call)
}
return calls
}
it('emits one estimated call per session from the totalTokens curve', async () => {
await writeSession()
const calls = await parse()
expect(calls).toHaveLength(1)
const call = calls[0]!
expect(call.model).toBe('grok-build')
// input = peak context (max totalTokens across the session)
expect(call.inputTokens).toBe(45000)
// cache reads = re-sent context (sum of per-turn starts 90000 minus peak 45000)
expect(call.cacheReadInputTokens).toBe(45000)
// output = sum of per-turn growth (3 turns x 5000)
expect(call.outputTokens).toBe(15000)
expect(call.costIsEstimated).toBe(true)
expect(call.costUSD).toBeGreaterThan(0)
expect(call.tools).toEqual(['Read', 'Grep', 'Bash', 'Agent'])
expect(call.bashCommands).toContain('git')
expect(call.subagentTypes).toEqual(['general-purpose'])
expect(call.project).toBe('myproject')
expect(call.deduplicationKey).toContain('grok:')
})
it('skips a session with no token growth', async () => {
await writeSession({ turns: [{ promptId: 'p1', totals: [0, 0] }] })
expect(await parse()).toHaveLength(0)
})
it('deduplicates across repeated parses', async () => {
await writeSession()
const seen = new Set<string>()
expect(await parse(seen)).toHaveLength(1)
expect(await parse(seen)).toHaveLength(0)
})
it('sums fresh input across a compaction instead of only the last peak', async () => {
await writeSession({ turns: [
{ promptId: 'p1', totals: [100000, 400000] },
{ promptId: 'p2', totals: [20000, 50000] },
] })
const calls = await parse()
expect(calls).toHaveLength(1)
// 400k (segment 1 peak) + 50k (post-compaction segment), not just the 400k global peak
expect(calls[0]!.inputTokens).toBe(450000)
})
})
describe('grok provider - display names', () => {
const provider = createGrokProvider('/tmp')
it('has the right name and displayName', () => {
expect(provider.name).toBe('grok')
expect(provider.displayName).toBe('Grok Build')
})
it('labels grok-build', () => {
expect(provider.modelDisplayName('grok-build')).toBe('Grok Build')
})
it('normalizes tool names', () => {
expect(provider.toolDisplayName('run_terminal_command')).toBe('Bash')
expect(provider.toolDisplayName('mystery_tool')).toBe('mystery_tool')
})
})