fix(token-meter): bound projection state via logged shadow prices

The contextBreakdown and contextPressure units carried the full priced
surface, so each session's persisted projection checkpoint grew without
bound. A surface replacement is now priced by the shadow-price event
logged directly before it — compact/summary for compaction, the new
compact/prune from tool-result pruning (priced through the injected
token meter) — and the unit states shrink to a fixed handful of numbers.
Regenerate the persistence/cordis/module/config catalogs.
This commit is contained in:
imccyu
2026-08-06 02:50:38 +08:00
parent 6400e521ba
commit 59df683ef1
21 changed files with 403 additions and 81 deletions
@@ -9,6 +9,7 @@ import SessionStore, {
import type { SurfaceEvent } from '@deepseek-ai/dsh-session'
import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant'
import InvariantService from '@deepseek-ai/dsh-invariants'
import TokenMeterService from '@deepseek-ai/dsh-token-meter'
import ToolResultPruneService, {
codePointLength,
DEFAULTS,
@@ -25,9 +26,16 @@ const SMALL: ToolResultPruneConfig = {
}
function service(config: ToolResultPruneConfig = SMALL): ToolResultPruneService {
return new ToolResultPruneService(new Context(), config)
const ctx = new Context()
// Service constructors self-register, so `ctx.tokenMeter` resolves for the
// shadow-price pricing without a full plugin boot.
void new TokenMeterService(ctx)
return new ToolResultPruneService(ctx, config)
}
/** Pricing oracle mirroring the service's estimator for expectations. */
const METER = new TokenMeterService(new Context())
function appendToolStep(
session: Session,
turn: number,
@@ -203,6 +211,18 @@ describe('ToolResultPruneService session transaction', () => {
sourceEventSeqs: [originalSeq],
})
expect(session.surface.nodes).not.toContain(originalSeq)
// Shadow-price protocol: the metering event sits directly before the
// replacement and prices the shadowed node with the shared estimator.
if (original.type !== 'tool/result') throw new Error('original is not a tool/result')
expect(session.events[entry.replacementSeq - 1]).toMatchObject({
type: 'compact/prune',
data: {
shadowedRange: { start: originalSeq, end: originalSeq },
shadowedSeqs: [originalSeq],
shadowedTokenCount: METER.estimateMessage(original.data.message),
},
})
})
it('prunes multiple results, skips short ones, and converges in one pass', () => {
@@ -240,6 +260,7 @@ describe('ToolResultPruneService session transaction', () => {
await ctx.plugin(SessionStore)
await ctx.plugin(InvariantService)
await ctx.plugin(SessionInvariant)
await ctx.plugin(TokenMeterService)
const prune = new ToolResultPruneService(ctx, SMALL)
const session = ctx.sessions.create(SessionId('invariants'))
appendToolStep(session, 1, 'a', [{ type: 'text', text: 'A'.repeat(100) }])