feat: show per-step OpenRouter cost on the Trajectory tab

Extend the openRouterCost projection with a per-step priced-cost map
keyed by turn:step so surfaces can render spend without re-pricing, and
have the Trajectory view read it through the framework useProjection seat:
each priced assistant step shows its USD cost on the request boundary chip
and in the inspector summary, while unpriced steps stay blank. Also fix a
pre-existing exactOptionalPropertyTypes error in extractCacheRates that the
host typecheck surfaced.
This commit is contained in:
2026-08-20 22:14:09 +07:00
parent d2ad46b8aa
commit 99b63abde6
20 changed files with 234 additions and 29 deletions

View File

@@ -871,4 +871,46 @@ describe('TrajectoryTable', () => {
expect(screen.getByRole('row', { name: /TOOL/ }).getAttribute('aria-selected')).toBe('false')
expect(onInspectApplied).not.toHaveBeenCalled()
})
it('shows a host-priced step cost on the request boundary and in the summary', () => {
render(
<TrajectoryTable
turns={TURNS}
requestNumbers={[{
turn: 1, step: 1, group: 'Step 1', number: 1, seq: 1,
stepCostUsd: 0.0028,
}]}
{...FOLD_PROPS}
/>,
)
const chip = screen.getByRole('button', { name: 'Request #1 · $0.0028' })
expect(chip).toBeTruthy()
fireEvent.click(chip)
expect(screen.getByText('Cost')).toBeTruthy()
expect(screen.getByText('$0.0028')).toBeTruthy()
})
it('formats step costs above a cent with the currency format', () => {
render(
<TrajectoryTable
turns={TURNS}
requestNumbers={[{
turn: 1, step: 1, group: 'Step 1', number: 1, seq: 1,
stepCostUsd: 1.234,
}]}
{...FOLD_PROPS}
/>,
)
const chip = screen.getByRole('button', { name: 'Request #1 · $1.23' })
expect(chip).toBeTruthy()
fireEvent.click(chip)
expect(screen.getByText('Cost')).toBeTruthy()
expect(screen.getByText('$1.23')).toBeTruthy()
})
it('omits the cost row when the step was not priced', () => {
render(<TrajectoryTable turns={TURNS} {...FOLD_PROPS} />)
fireEvent.click(screen.getByRole('button', { name: 'Request #1' }))
expect(screen.queryByText('Cost')).toBeNull()
})
})