Files
bowenliang123 c4f5500de7 refactor(host): split the fold module into state, metering, and wire siblings
fold.ts held the persisted state shape, the retention bounds, the untrusted-usage
sanitizer with its accumulators, the surface projection, and the served-value
builders at 1234 lines. The state, the metering accumulators, and the wire builders
move into flat siblings; applyTimeline and the surface projection stay together
because the clone contract they enforce spans both.

The metering extraction also unifies BilledUsage with the shared CostBucketTotals:
one four-bucket vocabulary, so the accumulator input is spelled `uncached` like the
totals it feeds. The persisted and wire shapes are unchanged (both already carried
`uncached`), so no stateVersion bump and no cached-row invalidation.

Production callers import the owning sibling; fold.ts keeps re-exporting the
pre-split surface for the specs.
2026-10-10 01:19:59 +08:00

243 lines
12 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// The split wire generation (src/host/foldWire.ts buildTimelineHead / buildTimelineDetail + the fold's detailRev ledger): the slim head carries the
// counters and headline anchor, the detail payload the heavy collections, and the revision bumps exactly on detail-mutating folds. Also
// pins the equivalence: inline view = head + detail, so the two generations can never drift apart.
import assert from 'node:assert/strict'
import { describe, test } from 'vitest'
import { buildTimelineDetail, buildTimelineHead, skillNameOf } from '../../src/host/fold'
import type { TimelineEvent } from '../../src/host/fold'
import { resolveBounds } from '../../src/host/config'
import {
assistantMessage,
compaction,
header,
planMode,
requestContext,
stepEnd,
stepStart,
systemMessage,
toolCall,
toolResult,
userMessage,
} from './helpers/events'
import { assertPlainJson, driveTimeline, timelineDef } from './helpers/projection'
/** A session log touching every envelope family the fold serves (mirrors the compat driver's canonical log). */
function canonicalLog(): TimelineEvent[] {
return [
header(1, {
tools: [{ name: 'bash', description: 'run a command' }],
model: 'deepseek-v4-flash',
provider: 'deepseek',
}),
systemMessage(2),
requestContext(3, { contextWindow: 128000 }),
stepStart(4),
userMessage(5, [{ type: 'text', text: 'hello there' }], { kind: 'user' }),
assistantMessage(6, { turn: 1, step: 0, usage: { inputTokens: 10, outputTokens: 5, cacheReadTokens: 3 } }),
toolCall(7, { callId: 'c1', name: 'bash' }),
toolResult(8, { callId: 'c1', content: [{ type: 'text', text: 'ok' }] }),
stepEnd(9),
assistantMessage(10, { turn: 1, step: 1, usage: { inputTokens: 20, outputTokens: 8 } }),
compaction(11, 'summary', { shadowedTokenCount: 12, shadowedSeqs: [5] }),
planMode(12, { active: true }),
]
}
describe('the detailRev ledger', () => {
test('bumps exactly on the detail-mutating folds of the canonical log', () => {
// Mutations: userMessage(5), assistantMessage(6), toolResult(8),
// stepEnd(9, the strip's span flush), assistantMessage(10), compaction(11), planMode(12) — seven in total.
const { state } = driveTimeline(canonicalLog())
assert.equal(state.detailRev, 7)
})
test('the working-slot and envelope folds do NOT bump (nothing to refetch)', () => {
const { def, state } = driveTimeline(canonicalLog())
// step/start (arms the step slot), tool/call (arms the call ledger),
// request/context (capacity metadata), and a header with an UNCHANGED model all leave the detail collections as-is.
const follow: TimelineEvent[] = [
requestContext(13, { contextWindow: 256000 }),
header(14, { model: 'deepseek-v4-flash', provider: 'deepseek', reason: 'change' }),
stepStart(15),
toolCall(16, { callId: 'c2', name: 'bash' }),
]
let st = state
for (const ev of follow) st = def.apply(st, ev)
assert.equal(st.detailRev, 7, 'no detail mutation, no bump')
// The capacity metadata and the header DID fold (state changed) — only the rev stayed.
assert.notEqual(st, state)
})
test('a real model switch bumps (the events collection gains a row)', () => {
const { def, state } = driveTimeline(canonicalLog())
const switched = def.apply(state, header(13, { model: 'deepseek-v4-pro', provider: 'deepseek', reason: 'change' }))
assert.equal(switched.detailRev, 8)
})
test('the rev is plain JSON and survives the state gate', () => {
const { states } = driveTimeline(canonicalLog())
for (const state of states) assertPlainJson(state)
})
})
describe('buildTimelineHead', () => {
test('serves the counters, the headline anchor, and the revision — with empty collections', () => {
const { state } = driveTimeline(canonicalLog())
const head = buildTimelineHead(state)
assert.equal(head.ok, true)
assert.equal(head.model, 'deepseek-v4-flash')
assert.equal(head.provider, 'deepseek')
assert.equal(head.contextWindow, 128000)
assert.ok(head.current.total > 0)
// The retained records tally: one turn, two steps, the compaction event.
assert.deepEqual(head.counts, { turns: 1, steps: 2, injects: 0, compactions: 1, prunes: 0, skills: 0 })
// The whole-session tally rides the head too: the canonical log's one user message (injections excluded).
assert.equal(head.humanInputs, 1)
// …and both its textual assistant replies (a step counts as an answer only when the message carried text).
assert.equal(head.answers, 2)
// The card preview rides the head: the canonical log's own user message.
assert.equal(head.lastUser, 'hello there')
// The headline anchor is the newest retained request's billing summary.
const lastReq = state.requests.at(-1)!
assert.deepEqual(head.last, { seq: lastReq.seq, total: lastReq.total, prompt: 20 })
assert.equal(head.detailRev, 7)
// The collections stay empty on the head — the detail channel serves them.
assert.deepEqual(head.requests, [])
assert.deepEqual(head.events, [])
assert.deepEqual(head.nodes, [])
assert.equal(head.droppedNodes, 0)
assert.deepEqual(head.archive, [])
assertPlainJson(head)
})
test('the counters tally injects/prunes, and a turn-less record folds as turn 0', () => {
const { state } = driveTimeline([
userMessage(1, [{ type: 'text', text: 'ctx' }], { kind: 'plugin', form: 'context', plugin: 'dsh-test' }),
compaction(2, 'prune', { shadowedTokenCount: 3 }),
assistantMessage(3, {}), // no turn: the replay shape folds as turn 0
])
const head = buildTimelineHead(state)
assert.deepEqual(head.counts, { turns: 1, steps: 1, injects: 1, compactions: 0, prunes: 1, skills: 0 })
})
test('the skills counter tallies DISTINCT loaded-skill names — the catalog digest never counts', () => { const { state } = driveTimeline([
// A `/name` invocation and a `skill`-tool load both load a skill; the
// available-skills catalog digest is the directory listing, not a load.
userMessage(1, [{ type: 'text', text: 'run' }], { kind: 'skill-invocation', form: 'skill', name: 'grilling' }),
toolCall(2, { callId: 'c1', name: 'skill' }),
toolResult(3, { callId: 'c1', content: [{ type: 'text', text: '<skill_content name="grilling">instructions</skill_content>' }] }),
userMessage(4, [{ type: 'text', text: 'catalog' }], { kind: 'skill-catalog', form: 'catalog' }),
userMessage(5, [{ type: 'text', text: 'again' }], { kind: 'skill-invocation', form: 'skill', name: 'ponytail' }),
userMessage(6, [{ type: 'text', text: 'once more' }], { kind: 'skill-invocation', form: 'skill', name: 'grilling' }),
])
const head = buildTimelineHead(state)
// 'grilling' loaded twice counts once; the catalog digest rides an
// untagged inject event — five injects, two distinct skills.
assert.deepEqual(head.counts, { turns: 0, steps: 0, injects: 5, compactions: 0, prunes: 0, skills: 2 })
assertPlainJson(head)
})
test('skillNameOf narrows hostile messages to no name (the activity fold’s reuse)', () => {
assert.equal(skillNameOf(null), '')
assert.equal(skillNameOf('text'), '')
assert.equal(skillNameOf(42), '')
assert.equal(skillNameOf({}), '')
assert.equal(skillNameOf({ content: [{ type: 'text', text: '<skill_content name="tdd">…' }] }), 'tdd')
})
test('a fresh state serves zeroed counters and no last/detailRev payload keys beyond the marker', () => {
const head = buildTimelineHead(timelineDef().init())
assert.deepEqual(head.counts, { turns: 0, steps: 0, injects: 0, compactions: 0, prunes: 0, skills: 0 })
assert.equal(head.humanInputs, 0, 'the tally defaults to zero on the wire')
assert.equal(head.answers, 0, 'the answers tally defaults to zero on the wire too')
assert.equal(head.lastUser, undefined, 'no user message yet — the preview stays absent')
assert.ok(!('lastUser' in head), 'no own key on the wire')
assert.equal(head.last, undefined, 'no request yet — the anchor stays absent')
assert.equal(head.detailRev, 0)
assert.equal(head.current.total, 0)
})
test('the newest request without a usage sample anchors without prompt', () => {
const { state } = driveTimeline([
userMessage(1, [{ type: 'text', text: 'hi' }], { kind: 'user' }),
assistantMessage(2, { turn: 1, step: 1 }),
])
const head = buildTimelineHead(state)
assert.deepEqual(head.last, { seq: 2, total: state.requests[0].total })
assert.ok(head.last !== undefined && !('prompt' in head.last), 'no undefined-valued prompt key')
})
})
describe('buildTimelineDetail', () => {
test('serves the same collections the inline view serves, plus the revision and the head', () => {
const { state, view } = driveTimeline(canonicalLog())
const detail = buildTimelineDetail(state, resolveBounds({}))
assert.equal(detail.rev, 7)
assert.deepEqual(detail.requests, view.requests)
assert.deepEqual(detail.events, view.events)
assert.deepEqual(detail.nodes, view.nodes)
assert.equal(detail.droppedNodes, view.droppedNodes)
assert.deepEqual(detail.archive, view.archive)
assert.equal(detail.surfaceFloor, view.surfaceFloor)
assert.equal(detail.archiveFloor, view.archiveFloor)
// The slim head rides at the SAME cut: the composition the Agent network's cold-node ring fetch renders from.
assert.ok(detail.head !== undefined)
assert.deepEqual(detail.head.current, view.current)
assert.deepEqual(detail.head.counts, buildTimelineHead(state).counts)
assertPlainJson(detail)
// The detail never aliases the persisted state.
assert.notEqual(detail.requests[0], state.requests[0])
assert.notEqual(detail.archive, state.archived)
})
test('the served window and floors follow the retention bounds (same rule as the inline view)', () => {
const events: TimelineEvent[] = [
userMessage(1, [{ type: 'text', text: 'injected' }], { kind: 'plugin', form: 'context', plugin: 'dsh-test' }),
userMessage(2, [{ type: 'text', text: 'x1' }]),
userMessage(3, [{ type: 'text', text: 'x2' }]),
userMessage(4, [{ type: 'text', text: 'x3' }]),
userMessage(5, [{ type: 'text', text: 'x4' }]),
]
const { state } = driveTimeline(events)
const detail = buildTimelineDetail(state, resolveBounds({ maxNodes: 2 }))
assert.deepEqual(detail.nodes.map(n => n.seq), [1, 4, 5], 'the out-of-window inject is pinned ahead of the tail')
assert.equal(detail.droppedNodes, 2)
assert.equal(detail.surfaceFloor, 3)
assert.equal(detail.rev, 5)
})
})
describe('the split wire generation (definition slim flag)', () => {
test('the slim view validates against the wire schema and carries the marker', () => {
const slim = timelineDef({}, true)
const { state } = driveTimeline(canonicalLog())
const view = slim.wire.view(state)
assert.equal(slim.wire.viewSchema.safeParse(view).success, true, 'the slim head validates')
assert.equal(typeof view.detailRev, 'number', 'the split marker is present')
assert.ok(view.counts !== undefined)
assert.deepEqual(view.nodes, [], 'collections stay off the wire value')
})
test('the inline view carries the collections and NO split markers', () => {
const inline = timelineDef({}, false)
const { state } = driveTimeline(canonicalLog())
const view = inline.wire.view(state)
assert.equal(inline.wire.viewSchema.safeParse(view).success, true, 'the inline value validates on the same schema')
assert.equal(view.detailRev, undefined)
assert.equal(view.counts, undefined)
assert.ok(view.nodes.length > 0)
assert.ok(view.requests.length > 0)
})
test('equivalence: the inline view is exactly the head plus the detail (minus the markers)', () => {
const { state } = driveTimeline(canonicalLog())
const head = buildTimelineHead(state)
const detail = buildTimelineDetail(state, resolveBounds({}))
const inline = timelineDef({}, false).wire.view(state)
const { counts: _counts, last: _last, detailRev: _rev, ...headScalars } = head
const { rev: _detailRev, head: _detailHead, ...collections } = detail
assert.deepEqual(inline, { ...headScalars, ...collections })
})
})