mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-13 04:03:30 +00:00
fix(token-meter): invalidate header-based breakdown caches
Address ds-review-bot thread 3921300994 (PRRT_kwDOS3Pfcs6eyLsu). contextBreakdown now folds system/message instead of request/header.system, so version-2 checkpoints have different semantics even though their numeric fields still pass the current schema. Bump the lower projection to stateVersion 3 so cache-only reads omit old values and restore replays the full log; derived caches are not covered by the Session-log persistence compatibility waiver. No migration or fallback is added. Regression seeds a schema-valid v2 row at the current watermark with stale system/message prices. Before the bump, cache views returned it, restoreFloor selected seq 2 rather than 0, and restore retained system=0/message=17 instead of system=8/message=9. After the bump, the 13-test owning suite passes and exact src/breakdown-projection.ts coverage is 100% statements, branches, functions, and lines; refreshed rows equal a fresh fold at version 3. Existing token-meter README prose documents the current system/message fold without a version literal, so it needs no edit. Propagation requirement: the upper layer already uses version 3 for a different compact cache representation. Advance that layer to version 4 when propagating this fix to avoid assigning one version to two schemas.
This commit is contained in:
@@ -61,7 +61,7 @@ const breakdownSchema = z.object({
|
||||
*/
|
||||
export const contextBreakdownProjectionDefinition = {
|
||||
key: 'contextBreakdown',
|
||||
stateVersion: 2,
|
||||
stateVersion: 3,
|
||||
stateSchema: contextBreakdownStateSchema,
|
||||
init: () => ({ systemTokens: 0, toolsTokens: 0, messageTokens: 0 }),
|
||||
apply: (state, event) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { describe, expect, it } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { createMessage, createSystemMessage, createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import type { ContentBlock, ToolSchema } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { SessionSeq } from '@deepseek-ai/dsh-session'
|
||||
import SessionStore, { SessionLogOffset, SessionSeq } from '@deepseek-ai/dsh-session'
|
||||
import type { Session, SessionEvent, SessionSeq as SessionSeqType } from '@deepseek-ai/dsh-session'
|
||||
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
|
||||
import TokenMeter from '@deepseek-ai/dsh-token-meter'
|
||||
@@ -311,6 +311,35 @@ describe('contextBreakdown session projection', () => {
|
||||
.toBe(ctx.tokenMeter.measure(session).surfaceTokens)
|
||||
})
|
||||
|
||||
it('discards version-2 cache values and refolds system messages from the full log', async () => {
|
||||
const { ctx, session } = await harness()
|
||||
try {
|
||||
appendSystem(session, 'You are terse.')
|
||||
session.append('request/header', { header: { config: CONFIG, tools: TOOLS }, reason: 'initial' })
|
||||
appendUser(session, 'abcd')
|
||||
const current = ctx.sessionProjections.checkpoint(session)
|
||||
// The old fold's fields still validate, but its header-based system price is not reusable.
|
||||
const staleValue = { systemTokens: 0, toolsTokens: estimateToolsTokens({ config: CONFIG, tools: TOOLS }), messageTokens: 17 }
|
||||
expect(contextBreakdownProjectionDefinition.stateSchema.parse(staleValue)).toEqual(staleValue)
|
||||
const checkpoint = {
|
||||
...current,
|
||||
contextBreakdown: { ver: 2, seq: SessionSeq(session.seq - 1), val: staleValue },
|
||||
}
|
||||
expect.soft(ctx.sessionProjections.viewCheckpoint(checkpoint)).not.toHaveProperty('contextBreakdown')
|
||||
expect.soft(ctx.sessionProjections.restoreFloor(checkpoint)).toBe(0)
|
||||
const restored = ctx.sessionProjections.restore(
|
||||
checkpoint, session.snapshotEvents(), SessionLogOffset(0), session.header, session.inheritedEventCount,
|
||||
)
|
||||
expect(restored.snapshot.values.contextBreakdown).toEqual({
|
||||
systemTokens: 8, toolsTokens: staleValue.toolsTokens, messageTokens: 9,
|
||||
})
|
||||
expect(restored.checkpoint).toEqual(current)
|
||||
expect(restored.checkpoint['contextBreakdown']?.ver).toBe(3)
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it('restores from a JSON checkpoint and unregisters with the token-meter fiber', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
|
||||
Reference in New Issue
Block a user