fix(session): ignore balanced no-step turns

This commit is contained in:
_Kerman
2026-08-04 14:29:18 +08:00
parent 5356bd972d
commit 8ed6a22940
5 changed files with 39 additions and 12 deletions

View File

@@ -31,14 +31,24 @@ export { foldSurface, isAppendSurfaceEvent, isReplacementSurfaceEvent, isSurface
export { canonicalHeader, foldRequestHeader, headerEquals } from './request-header.ts'
/**
* Find the latest completed model turn in an event sequence.
* Find the latest closed turn that entered at least one model step, ignoring
* balanced no-step turns produced by rejection, empty input, or cancellation.
* @param events - session events, or an owned suffix, to inspect.
* @returns the latest turn end, or `undefined`.
* @returns the latest matching turn end, or `undefined`.
*/
export function findLastMessageTurnEnd(
events: readonly SessionEvent[],
): SessionEvent<'turn/end'> | undefined {
return events.findLast(event => event.type === 'turn/end')
const steppedTurns = new Set<number>()
let latest: SessionEvent<'turn/end'> | undefined
for (const event of events) {
if (event.type === 'step/start') {
steppedTurns.add(event.data.turn)
continue
}
if (event.type === 'turn/end' && steppedTurns.delete(event.data.turn)) latest = event
}
return latest
}
declare module 'cordis' {

View File

@@ -6,11 +6,28 @@ import SessionStore, {
Session,
SessionEvent,
SessionId,
findLastMessageTurnEnd,
snapshotSessionEvent,
} from '@deepseek-ai/dsh-session'
import type { CreateSessionOptions, SessionEventType, SessionHeader, SessionSurface, TodoItem } from '@deepseek-ai/dsh-session'
describe('Session', () => {
it('finds the latest closed turn that entered a model step', () => {
const session = new Session(SessionId('last-message-turn'))
session.append('turn/start', { turn: 1 })
session.append('turn/end', { turn: 1, reason: { kind: 'blocked' } })
expect(findLastMessageTurnEnd(session.events)).toBeUndefined()
session.append('turn/start', { turn: 2 })
session.append('step/start', { turn: 2, step: 1 })
session.append('step/end', { turn: 2, step: 1 })
session.append('turn/end', { turn: 2, reason: { kind: 'max-tokens' } })
expect(findLastMessageTurnEnd(session.events)?.data)
.toEqual({ turn: 2, reason: { kind: 'max-tokens' } })
})
it('exposes one stable readonly surface view', () => {
const session = new Session(SessionId('surface-view'))
const surface = session.surface