fix(fixture): mirror context breakdown projection

This commit is contained in:
imccyu
2026-08-06 00:21:17 +08:00
parent b0d1d9445e
commit e94d539de1
3 changed files with 120 additions and 10 deletions

View File

@@ -121,6 +121,21 @@ it('boots the built plugin graph and renders a fixture session end to end', asyn
expect(document.querySelector('[data-sample="bash"]')).not.toBeNull() expect(document.querySelector('[data-sample="bash"]')).not.toBeNull()
}, { timeout: 10_000 }) }, { timeout: 10_000 })
// Resolve the resident approval so the ordinary composer bar (which owns
// ContextMeter) resumes without replacing the session shell. This minimal
// boot graph intentionally does not mount the separate question UI plugin.
fireEvent.click(await screen.findByRole('button', { name: 'Allow once' }))
// The fixture mirrors all three token-meter projections, so the assembled
// ContextMeter reaches its composition panel instead of only the occupancy
// fallback path.
const contextTrigger = await screen.findByRole('button', { name: /of context used/ })
fireEvent.click(contextTrigger)
const contextPanel = await screen.findByRole('dialog', { name: 'of context used' })
within(contextPanel).getByText('System prompt')
within(contextPanel).getByText('Tools')
within(contextPanel).getByText('Messages')
// The write/edit turns render a real diff card through the assembled graph // The write/edit turns render a real diff card through the assembled graph
// (the keyed FileMutationRow composing ToolRow + DiffBlock), not just the // (the keyed FileMutationRow composing ToolRow + DiffBlock), not just the
// fixture's raw text. The card is collapsed by default, so expand each edit/ // fixture's raw text. The card is collapsed by default, so expand each edit/

View File

@@ -27,7 +27,7 @@ import type {
// Type-only: the brand constructor is host-side; the fixture casts at its // Type-only: the brand constructor is host-side; the fixture casts at its
// wire-fabrication boundary (the schema layer's one-cast-point posture). // wire-fabrication boundary (the schema layer's one-cast-point posture).
import type { CommandId } from '@deepseek-ai/dsh-commands/brand' import type { CommandId } from '@deepseek-ai/dsh-commands/brand'
import { foldSurface } from '@deepseek-ai/dsh-session/surface' import { deriveEventMessage, foldSurface } from '@deepseek-ai/dsh-session/surface'
import type { import type {
ApiProxy, ClientRequest, ClientResponse, HistoryEntry, HostFrame, MuxFrame, RpcReceipt, ApiProxy, ClientRequest, ClientResponse, HistoryEntry, HostFrame, MuxFrame, RpcReceipt,
ModelProviderGroup, ModelTarget, RpcRequest, RpcResponse, RpcResult, ServerRequest, ServerResponse, SessionSummary, ModelProviderGroup, ModelTarget, RpcRequest, RpcResponse, RpcResult, ServerRequest, ServerResponse, SessionSummary,
@@ -358,6 +358,12 @@ function buildAlphaLog(): SessionEvent[] {
events.push({ seq, time: (time += 800), ...authored }) events.push({ seq, time: (time += 800), ...authored })
return seq return seq
} }
// This resident history represents completed model requests, so retain the
// route capacity that accompanied them just as the live prompt path does.
push({
type: 'request/context',
data: { provider: 'deepseek-official', model: 'deepseek-v4-flash', contextWindow: 128_000 },
})
for (let turn = 0; turn < 60; turn++) { for (let turn = 0; turn < 60; turn++) {
push({ type: 'turn/start', data: { turn } }) push({ type: 'turn/start', data: { turn } })
const userSeq = push({ const userSeq = push({
@@ -819,6 +825,65 @@ interface FixtureRequestContext {
contextWindow?: number contextWindow?: number
} }
interface FixtureContextBreakdownProjection {
systemTokens: number
toolsTokens: number
messageTokens: number
}
/** Fixed token-meter heuristic constants mirrored by this client-only fixture. */
const CHARS_PER_TOKEN = 4
const BLOCK_OVERHEAD = 4
const ROLE_OVERHEAD = 4
/** Price fixture content with token-meter's fixed-density heuristic. */
function estimateFixtureContent(blocks: readonly ContentBlock[]): number {
let tokens = 0
for (const block of blocks) {
switch (block.type) {
case 'text':
case 'reasoning':
tokens += Math.ceil(block.text.length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD
break
case 'tool-call':
tokens += Math.ceil(block.name.length / CHARS_PER_TOKEN)
+ Math.ceil(block.arguments.length / CHARS_PER_TOKEN)
+ BLOCK_OVERHEAD
break
case 'tool-result':
tokens += estimateFixtureContent(block.content) + BLOCK_OVERHEAD
break
default:
tokens += BLOCK_OVERHEAD + Math.ceil(JSON.stringify(block).length / CHARS_PER_TOKEN)
}
}
return tokens
}
/** Fixture parallel of token-meter's heuristic context-composition projection. */
function contextBreakdownOf(log: readonly SessionEvent[]): FixtureContextBreakdownProjection {
const headerEvent = log.findLast(event => event.type === 'request/header')
const header = headerEvent === undefined
? undefined
: headerEvent.data.header
let messageTokens = 0
for (const seq of foldSurface(log).nodes) {
const event = log[seq]
if (event === undefined) continue
const message = deriveEventMessage(event)
if (message !== null) messageTokens += estimateFixtureContent(message.content) + ROLE_OVERHEAD
}
return {
systemTokens: header?.system === undefined
? 0
: Math.ceil(header.system.length / CHARS_PER_TOKEN) + ROLE_OVERHEAD,
toolsTokens: header?.tools === undefined || header.tools.length === 0
? 0
: Math.ceil(JSON.stringify(header.tools).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD,
messageTokens,
}
}
/** Latest log-only route context, or undefined before any request ran. */ /** Latest log-only route context, or undefined before any request ran. */
function lastRequestContext( function lastRequestContext(
log: readonly SessionEvent[], log: readonly SessionEvent[],
@@ -874,28 +939,44 @@ function projectionValuesOf(log: readonly SessionEvent[]): Record<string, unknow
values['tokenUsage'] = tokenUsageOf(log) values['tokenUsage'] = tokenUsageOf(log)
// Always present (token-meter composed): last request pressure and capacity. // Always present (token-meter composed): last request pressure and capacity.
values['contextPressure'] = contextPressureOf(log) values['contextPressure'] = contextPressureOf(log)
// Always present (token-meter composed): heuristic request composition.
values['contextBreakdown'] = contextBreakdownOf(log)
return values return values
} }
/** Host push-frame parallel: emit one session/projection frame per key the given event advanced. */ /** Host push-frame parallel: emit one session/projection frame per key the given event advanced. */
function projectionFramesOf(id: SessionId, log: readonly SessionEvent[], event: SessionEvent): Extract<MuxFrame, { type: 'session/projection' }>[] { function projectionFramesOf(id: SessionId, log: readonly SessionEvent[], event: SessionEvent): Extract<MuxFrame, { type: 'session/projection' }>[] {
const type = (event as { type: string }).type const type = (event as { type: string }).type
const frames: Extract<MuxFrame, { type: 'session/projection' }>[] = []
// One usage sample advances both token-meter units. // One usage sample advances both token-meter units.
if (usageSampleOf(event) !== undefined) { if (usageSampleOf(event) !== undefined) {
return [ frames.push(
{ type: 'session/projection', sessionId: id, key: 'tokenUsage', value: tokenUsageOf(log), seq: event.seq }, { type: 'session/projection', sessionId: id, key: 'tokenUsage', value: tokenUsageOf(log), seq: event.seq },
{ type: 'session/projection', sessionId: id, key: 'contextPressure', value: contextPressureOf(log), seq: event.seq }, { type: 'session/projection', sessionId: id, key: 'contextPressure', value: contextPressureOf(log), seq: event.seq },
] )
} }
if (type === 'request/context') { if (type === 'request/context') {
return [{ frames.push({
type: 'session/projection', type: 'session/projection',
sessionId: id, sessionId: id,
key: 'contextPressure', key: 'contextPressure',
value: contextPressureOf(log), value: contextPressureOf(log),
seq: event.seq, seq: event.seq,
}] })
} }
if (type === 'request/header'
|| type === 'user/message'
|| type === 'assistant/message'
|| type === 'tool/result') {
frames.push({
type: 'session/projection',
sessionId: id,
key: 'contextBreakdown',
value: contextBreakdownOf(log),
seq: event.seq,
})
}
if (frames.length > 0) return frames
if (type === 'session/title') { if (type === 'session/title') {
const values = projectionValuesOf(log) const values = projectionValuesOf(log)
/* v8 ignore next -- the advancing title event is in the log, so the key is present. */ /* v8 ignore next -- the advancing title event is in the log, so the key is present. */

View File

@@ -159,6 +159,11 @@ describe('createFixtureApi', () => {
}, },
// No request ran, so neither pressure nor capacity is known yet. // No request ran, so neither pressure nor capacity is known yet.
contextPressure: {}, contextPressure: {},
contextBreakdown: {
systemTokens: 0,
toolsTokens: 0,
messageTokens: 0,
},
} }, } },
}) })
}) })
@@ -304,6 +309,10 @@ describe('createFixtureApi', () => {
frame.type === 'session/projection' frame.type === 'session/projection'
&& frame.key === 'contextPressure' && frame.key === 'contextPressure'
&& (frame.value as { contextWindow?: number }).contextWindow === 128_000)).toBe(true) && (frame.value as { contextWindow?: number }).contextWindow === 128_000)).toBe(true)
expect(frames.some(frame =>
frame.type === 'session/projection'
&& frame.key === 'contextBreakdown'
&& (frame.value as { messageTokens?: number }).messageTokens! > 0)).toBe(true)
const finalize = frames.find((f): f is Extract<MuxFrame, { type: 'session/event' }> => f.type === 'session/event' && f.event.type === 'assistant/message') const finalize = frames.find((f): f is Extract<MuxFrame, { type: 'session/event' }> => f.type === 'session/event' && f.event.type === 'assistant/message')
expect(JSON.stringify(finalize?.event.data)).toContain('(已中断)') expect(JSON.stringify(finalize?.event.data)).toContain('(已中断)')
// Idle cancel: no replay in flight, must not explode; running flips false. // Idle cancel: no replay in flight, must not explode; running flips false.
@@ -335,7 +344,7 @@ describe('createFixtureApi', () => {
const envelopes: RpcRequest<MuxFrame>[] = [] const envelopes: RpcRequest<MuxFrame>[] = []
for await (const envelope of api.events.mux(req({}), abort.signal)) { for await (const envelope of api.events.mux(req({}), abort.signal)) {
envelopes.push(envelope) envelopes.push(envelope)
if (envelopes.length >= 10) abort.abort() if (envelopes.length >= 11) abort.abort()
} }
return envelopes return envelopes
} }
@@ -351,10 +360,15 @@ describe('createFixtureApi', () => {
expect(first[5]?.payload).toMatchObject({ type: 'session/projection', sessionId: 'fx-alpha', key: 'goal', value: null }) expect(first[5]?.payload).toMatchObject({ type: 'session/projection', sessionId: 'fx-alpha', key: 'goal', value: null })
expect(first[6]?.payload).toMatchObject({ type: 'session/projection', sessionId: 'fx-alpha', key: 'tokenUsage' }) expect(first[6]?.payload).toMatchObject({ type: 'session/projection', sessionId: 'fx-alpha', key: 'tokenUsage' })
expect(first[7]?.payload).toMatchObject({ type: 'session/projection', sessionId: 'fx-alpha', key: 'contextPressure' }) expect(first[7]?.payload).toMatchObject({ type: 'session/projection', sessionId: 'fx-alpha', key: 'contextPressure' })
expect(first[8]?.payload).toMatchObject({ type: 'approval/requested', toolName: 'dangerous_tool' }) expect(first[8]?.payload).toMatchObject({
expect(second[8]?.rpcId).toBe(first[8]?.rpcId) // stable rpcId across replays (host replay semantics) type: 'session/projection', sessionId: 'fx-alpha', key: 'contextBreakdown',
expect(first[9]?.payload).toMatchObject({ type: 'question/requested', sessionId: 'fx-alpha' }) value: { systemTokens: 0, toolsTokens: 0 },
expect(second[9]?.rpcId).toBe(first[9]?.rpcId) })
expect((first[8]?.payload as { value: { messageTokens: number } }).value.messageTokens).toBeGreaterThan(0)
expect(first[9]?.payload).toMatchObject({ type: 'approval/requested', toolName: 'dangerous_tool' })
expect(second[9]?.rpcId).toBe(first[9]?.rpcId) // stable rpcId across replays (host replay semantics)
expect(first[10]?.payload).toMatchObject({ type: 'question/requested', sessionId: 'fx-alpha' })
expect(second[10]?.rpcId).toBe(first[10]?.rpcId)
}) })
it('steer with no replay in flight falls through to a fresh queued turn; non-text blocks stringify empty', async () => { it('steer with no replay in flight falls through to a fresh queued turn; non-text blocks stringify empty', async () => {