188 lines
6.7 KiB
TypeScript
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')
|
|
})
|
|
})
|