mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 10:14:19 -05:00
* feat(statusline): show token counts in context bar (ctx 74K/200K (37%))
Instead of just percentage, show actual token usage and context window
size with integer K/M prefixes (uppercase, en-US locale).
- BuiltinStatusLine: added contextInputTokens + contextWindow fields
- buildBuiltinStatusSegments: formats "ctx {used}/{window} ({pct}%)"
- format.ts: added formatTokenCount() — integer, uppercase K/M
- All 18 tests pass
* fix(statusline): call getCurrentUsage once and reuse result
* fix(statusline): mark estimated token counts with ~ prefix
When getCurrentUsage() returns is_estimated:true (provider reported
all-zero usage), show "ctx ~74K/200K (37%)" instead of "ctx 74K/200K
(37%)" so built-in statusline preserves the estimate distinction
exposed by the custom-statusline contract.
* fix(openai-shim): send stream_options for non-Ollama local providers
Previously stream_options was disabled for all local URLs (127.0.0.1,
192.168.x.x, etc.), preventing llama-server and other self-hosted
OpenAI-compatible servers from returning usage in SSE streams. Now only
Ollama (localhost:11434) is excluded, since it rejects stream_options.
All other providers including llama-server receive stream_options and
their prompt_tokens/completion_tokens are correctly mapped via
buildAnthropicUsageFromRawUsage.
---------
Co-authored-by: Andrey Bezborodov <andrey@getdataflow.ru>
215 lines
6.7 KiB
TypeScript
215 lines
6.7 KiB
TypeScript
import { describe, expect, it } from 'bun:test'
|
|
import { DEFAULT_GLOBAL_CONFIG } from '../utils/config.js'
|
|
import {
|
|
type BuiltinStatusData,
|
|
buildBuiltinStatusSegments,
|
|
builtinStatusLineShouldDisplay,
|
|
fitSegments,
|
|
} from './BuiltinStatusLine.js'
|
|
|
|
const fullData: BuiltinStatusData = {
|
|
modelName: 'Opus 4.8',
|
|
contextUsedPercent: 37.4,
|
|
contextInputTokens: 74000,
|
|
contextWindow: 200000,
|
|
costUSD: 1.234,
|
|
rateLimit: { label: '5h', usedPercent: 42 },
|
|
}
|
|
|
|
describe('buildBuiltinStatusSegments', () => {
|
|
it('builds all segments when every datum is present', () => {
|
|
const segments = buildBuiltinStatusSegments(fullData)
|
|
expect(segments.map(s => s.key)).toEqual([
|
|
'model',
|
|
'context',
|
|
'cost',
|
|
'rateLimit',
|
|
])
|
|
expect(segments.map(s => s.text)).toEqual([
|
|
'Opus 4.8',
|
|
'ctx 74K/200K (37%)',
|
|
'$1.23',
|
|
'5h 42%',
|
|
])
|
|
})
|
|
|
|
it('omits context before the first assistant turn', () => {
|
|
const segments = buildBuiltinStatusSegments({
|
|
...fullData,
|
|
contextUsedPercent: null,
|
|
contextInputTokens: null,
|
|
contextWindow: null,
|
|
})
|
|
expect(segments.find(s => s.key === 'context')).toBeUndefined()
|
|
})
|
|
|
|
it('omits cost at $0 and rate limit without utilization data', () => {
|
|
const segments = buildBuiltinStatusSegments({
|
|
...fullData,
|
|
costUSD: 0,
|
|
rateLimit: null,
|
|
})
|
|
expect(segments.map(s => s.key)).toEqual(['model', 'context'])
|
|
})
|
|
|
|
it('colors context by usage thresholds', () => {
|
|
const at = (pct: number) =>
|
|
buildBuiltinStatusSegments({ ...fullData, contextUsedPercent: pct }).find(
|
|
s => s.key === 'context',
|
|
)?.color
|
|
expect(at(50)).toBeUndefined()
|
|
expect(at(70)).toBe('warning')
|
|
expect(at(90)).toBe('error')
|
|
})
|
|
|
|
it('shows token counts in context segment', () => {
|
|
const ctx = buildBuiltinStatusSegments({
|
|
...fullData,
|
|
contextUsedPercent: 37.4,
|
|
contextInputTokens: 74000,
|
|
contextWindow: 200000,
|
|
}).find(s => s.key === 'context')
|
|
|
|
expect(ctx?.text).toBe('ctx 74K/200K (37%)')
|
|
expect(ctx?.shortText).toBe('ctx 74K/200K')
|
|
})
|
|
|
|
it('shows sub-one-percent context usage as nonzero', () => {
|
|
const ctx = buildBuiltinStatusSegments({
|
|
...fullData,
|
|
contextUsedPercent: 0.01,
|
|
contextInputTokens: 20,
|
|
contextWindow: 200000,
|
|
}).find(s => s.key === 'context')
|
|
|
|
expect(ctx?.text).toBe('ctx 20/200K (<1%)')
|
|
})
|
|
|
|
it('colors context by the displayed rounded percentage', () => {
|
|
const at = (pct: number) =>
|
|
buildBuiltinStatusSegments({ ...fullData, contextUsedPercent: pct }).find(
|
|
s => s.key === 'context',
|
|
)
|
|
|
|
expect(at(69.6)).toMatchObject({ text: 'ctx 74K/200K (70%)', color: 'warning' })
|
|
expect(at(89.6)).toMatchObject({ text: 'ctx 74K/200K (90%)', color: 'error' })
|
|
})
|
|
|
|
it('colors rate limit by usage thresholds', () => {
|
|
const at = (pct: number) =>
|
|
buildBuiltinStatusSegments({
|
|
...fullData,
|
|
rateLimit: { label: '7d', usedPercent: pct },
|
|
}).find(s => s.key === 'rateLimit')?.color
|
|
expect(at(30)).toBeUndefined()
|
|
expect(at(60)).toBe('warning')
|
|
expect(at(85)).toBe('error')
|
|
})
|
|
|
|
it('prefixes estimated tokens with ~ when contextIsEstimated is true', () => {
|
|
const ctx = buildBuiltinStatusSegments({
|
|
...fullData,
|
|
contextUsedPercent: 37,
|
|
contextInputTokens: 74000,
|
|
contextWindow: 200000,
|
|
contextIsEstimated: true,
|
|
}).find(s => s.key === 'context')
|
|
|
|
expect(ctx?.text).toBe('ctx ~74K/200K (37%)')
|
|
expect(ctx?.shortText).toBe('ctx ~74K/200K')
|
|
})
|
|
|
|
it('does not show ~ when contextIsEstimated is false or absent', () => {
|
|
const ctxWithFlag = buildBuiltinStatusSegments({
|
|
...fullData,
|
|
contextIsEstimated: false,
|
|
}).find(s => s.key === 'context')
|
|
const ctxWithoutFlag = buildBuiltinStatusSegments({
|
|
...fullData,
|
|
}).find(s => s.key === 'context')
|
|
|
|
expect(ctxWithFlag?.text).toBe('ctx 74K/200K (37%)')
|
|
expect(ctxWithFlag?.shortText).toBe('ctx 74K/200K')
|
|
expect(ctxWithoutFlag?.text).toBe('ctx 74K/200K (37%)')
|
|
expect(ctxWithoutFlag?.shortText).toBe('ctx 74K/200K')
|
|
})
|
|
})
|
|
|
|
describe('fitSegments', () => {
|
|
const segments = buildBuiltinStatusSegments(fullData)
|
|
// 'Opus 4.8 · ctx 74K/200K (37%) · $1.23 · 5h 42%' = 46 cols
|
|
|
|
it('keeps everything when the line fits', () => {
|
|
expect(fitSegments(segments, 120)).toHaveLength(4)
|
|
})
|
|
|
|
it('degrades segments to short forms before dropping any', () => {
|
|
// Full: 'Opus 4.8 · ctx 74K/200K (37%) · $1.23 · 5h 42%' = 46 cols
|
|
// Degraded shortText: 'Opus 4.8 · ctx 74K/200K · $1 · 5h 42%' = 39 cols
|
|
const fitted = fitSegments(segments, 40)
|
|
expect(fitted.map(s => s.key)).toEqual([
|
|
'model',
|
|
'context',
|
|
'cost',
|
|
'rateLimit',
|
|
])
|
|
expect(fitted.find(s => s.key === 'context')?.text).toBe('ctx 74K/200K')
|
|
expect(fitted.find(s => s.key === 'cost')?.text).toBe('$1')
|
|
})
|
|
|
|
it('marks dropped segments with a trailing ellipsis', () => {
|
|
// Too narrow for all four even degraded; hidden data must be visible as hidden
|
|
// Full is ~46 cols, degraded shortText is ~39 cols — try width 35
|
|
const fitted = fitSegments(segments, 35)
|
|
expect(fitted.at(-1)?.key).toBe('truncated')
|
|
expect(fitted.at(-1)?.text).toBe('…')
|
|
})
|
|
|
|
it('keeps only the model at very narrow widths, skipping the marker if it will not fit', () => {
|
|
const fitted = fitSegments(segments, 10)
|
|
expect(fitted.map(s => s.key)).toEqual(['model'])
|
|
})
|
|
|
|
it('does not mutate the caller-visible segment text', () => {
|
|
const before = segments.map(s => s.text)
|
|
fitSegments(segments, 22)
|
|
expect(segments.map(s => s.text)).toEqual(before)
|
|
})
|
|
|
|
it('returns empty when even the model does not fit', () => {
|
|
expect(fitSegments(segments, 3)).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('builtinStatusLineShouldDisplay', () => {
|
|
it('yields to a configured custom statusline', () => {
|
|
expect(
|
|
builtinStatusLineShouldDisplay({
|
|
statusLine: { type: 'command', command: 'echo custom' },
|
|
}),
|
|
).toBe(false)
|
|
})
|
|
|
|
it('displays by default when no custom statusline is configured', () => {
|
|
expect(builtinStatusLineShouldDisplay({})).toBe(true)
|
|
})
|
|
|
|
it('hides when defaultStatusLineEnabled is false', () => {
|
|
expect(
|
|
builtinStatusLineShouldDisplay(
|
|
{},
|
|
{ ...DEFAULT_GLOBAL_CONFIG, defaultStatusLineEnabled: false },
|
|
),
|
|
).toBe(false)
|
|
})
|
|
|
|
it('config off still yields to nothing — custom statusline wins regardless', () => {
|
|
expect(
|
|
builtinStatusLineShouldDisplay(
|
|
{ statusLine: { type: 'command', command: 'echo custom' } },
|
|
{ ...DEFAULT_GLOBAL_CONFIG, defaultStatusLineEnabled: false },
|
|
),
|
|
).toBe(false)
|
|
})
|
|
})
|