mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-07-10 01:29:41 +00:00
fix(vercel-gateway): route network sources through aggregation
The synthetic source path has no on-disk file, so fingerprintFile returned null and parseProviderSources dropped the source before parsing — the provider always reported $0 even with a key set. Add a `network` provider flag; such sources skip the fingerprint gate, re-fetch each run with a synthetic fingerprint, and keep the gateway's reported cost (instead of recomputing from tokens, which would be $0 for any model codeburn can't price). Adds an end-to-end test through parseAllSessions.
This commit is contained in:
parent
f4fd6a1640
commit
e7c0e21846
4 changed files with 68 additions and 1 deletions
|
|
@ -1696,7 +1696,7 @@ function providerCallToCachedCall(call: ParsedProviderCall): CachedCall {
|
|||
webSearchRequests: call.webSearchRequests,
|
||||
cacheCreationOneHourTokens: 0,
|
||||
},
|
||||
costUSD: (call.provider === 'mistral-vibe' || call.provider === 'antigravity' || call.provider === 'devin') ? call.costUSD : undefined,
|
||||
costUSD: (call.provider === 'mistral-vibe' || call.provider === 'antigravity' || call.provider === 'devin' || call.provider === 'vercel-gateway') ? call.costUSD : undefined,
|
||||
speed: call.speed,
|
||||
timestamp: call.timestamp,
|
||||
tools: call.tools,
|
||||
|
|
@ -1931,6 +1931,16 @@ async function parseProviderSources(
|
|||
|
||||
for (const source of sources) {
|
||||
allDiscoveredFiles.add(source.path)
|
||||
|
||||
// Network providers (e.g. Vercel AI Gateway) have no on-disk file — their data
|
||||
// comes from a live API fetch in createSessionParser. There's nothing to
|
||||
// fingerprint or incrementally cache, so re-fetch every run with a synthetic
|
||||
// fingerprint (mtime=now so the date-range filter below never excludes it).
|
||||
if (provider.network) {
|
||||
changedSources.push({ source, fp: { dev: 0, ino: 0, mtimeMs: Date.now(), sizeBytes: 0 } })
|
||||
continue
|
||||
}
|
||||
|
||||
const fp = await fingerprintFile(source.path)
|
||||
if (!fp) continue
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ export type ParsedProviderCall = {
|
|||
export type Provider = {
|
||||
name: string
|
||||
displayName: string
|
||||
// Data comes from a live API fetch (no on-disk file). Such sources can't be
|
||||
// fingerprinted or incrementally cached, so the parser re-fetches every run.
|
||||
network?: boolean
|
||||
modelDisplayName(model: string): string
|
||||
toolDisplayName(rawTool: string): string
|
||||
discoverSessions(): Promise<SessionSource[]>
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ function createParser(
|
|||
export const vercelGateway: Provider = {
|
||||
name: 'vercel-gateway',
|
||||
displayName: 'Vercel AI Gateway',
|
||||
network: true,
|
||||
|
||||
modelDisplayName(model: string): string {
|
||||
const slash = model.indexOf('/')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { mkdtemp, rm } from 'fs/promises'
|
||||
import { tmpdir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { fetchVercelGatewayReport, vercelGateway } from '../../src/providers/vercel-gateway.js'
|
||||
import { parseAllSessions, clearSessionCache } from '../../src/parser.js'
|
||||
|
||||
describe('vercel-gateway provider', () => {
|
||||
const originalFetch = globalThis.fetch
|
||||
|
|
@ -62,3 +66,52 @@ describe('vercel-gateway provider', () => {
|
|||
expect(calls[0]?.model).toBe('anthropic/claude-sonnet-4.6')
|
||||
})
|
||||
})
|
||||
|
||||
describe('vercel-gateway end-to-end (parseAllSessions network path)', () => {
|
||||
const originalFetch = globalThis.fetch
|
||||
const originalKey = process.env.AI_GATEWAY_API_KEY
|
||||
const originalCacheDir = process.env.CODEBURN_CACHE_DIR
|
||||
let cacheDir: string
|
||||
|
||||
beforeEach(async () => {
|
||||
cacheDir = await mkdtemp(join(tmpdir(), 'cb-vercel-cache-'))
|
||||
process.env.CODEBURN_CACHE_DIR = cacheDir
|
||||
process.env.AI_GATEWAY_API_KEY = 'test-key'
|
||||
clearSessionCache()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
globalThis.fetch = originalFetch
|
||||
if (originalKey === undefined) delete process.env.AI_GATEWAY_API_KEY
|
||||
else process.env.AI_GATEWAY_API_KEY = originalKey
|
||||
if (originalCacheDir === undefined) delete process.env.CODEBURN_CACHE_DIR
|
||||
else process.env.CODEBURN_CACHE_DIR = originalCacheDir
|
||||
clearSessionCache()
|
||||
vi.restoreAllMocks()
|
||||
await rm(cacheDir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
// Regression: the synthetic source path `vercel-ai-gateway:report` has no file
|
||||
// on disk, so it was dropped by the fingerprintFile gate in parseProviderSources
|
||||
// and the provider always reported $0. Network providers must survive that gate
|
||||
// and contribute their fetched cost through the real aggregation pipeline.
|
||||
it('network source survives the fingerprint gate and contributes cost', async () => {
|
||||
globalThis.fetch = vi.fn(async () => ({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
results: [
|
||||
{ day: '2026-06-01', model: 'openai/gpt-4o', total_cost: 12.34, input_tokens: 1000, output_tokens: 500, request_count: 3 },
|
||||
],
|
||||
}),
|
||||
})) as typeof fetch
|
||||
|
||||
const range = {
|
||||
start: new Date('2026-05-01T00:00:00.000Z'),
|
||||
end: new Date('2026-06-09T23:59:59.999Z'),
|
||||
}
|
||||
const projects = await parseAllSessions(range, 'vercel-gateway')
|
||||
const total = projects.reduce((sum, p) => sum + p.totalCostUSD, 0)
|
||||
|
||||
expect(total).toBeCloseTo(12.34, 2)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue