Represent tool results as tool-role messages and migrate released wrappers without losing outer own JSON fields, call identity, error state, or writer field order. Refuse nested or wrapper-only information that has no lossless current representation before successor publication. Validate current event relationships directly instead of projecting V4 data back into V3. Keep lifecycle, PTC, title/command, compaction, system-field and retired-syntax checks on catalog restoration and ordinary JSONL opens. The Worker JSON equality helper must inspect own keys so it cannot admit PTC arguments that Node rejects. Keep the source role change, required native admission and consumer fixture updates in one complete review layer. Explain shared-type changes in the persistence record and the native reader rationale in its existing note. Use checked branded fixture identities and discriminant narrowing instead of unknown double casts; fixture values and assertions remain intact. This recomposes the original tool-role work by _Kerman with the independently validated migration and admission repairs by Tianyi Cui. Frozen generations and snapshot input version selection are retained. Co-authored-by: Tianyi Cui <[email protected]>
47 lines
2.0 KiB
TypeScript
47 lines
2.0 KiB
TypeScript
/** Select and log permanent image omissions in current model-request order. */
|
|
|
|
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
|
import type { Session, SessionSeq } from '@deepseek-ai/dsh-session'
|
|
import type { ImageOffloadTarget } from './projection.ts'
|
|
|
|
/**
|
|
* Record one decision omitting the oldest retained input-image occurrences.
|
|
* Assistant nodes carry model output and are excluded. Image indexes count
|
|
* every occurrence, including previously offloaded ones, within each message.
|
|
* @param session - session whose next request applies the decision.
|
|
* @param sourceEventSeqs - input message events in the failed request's order.
|
|
* @param count - additional retained occurrences the adapter needs omitted.
|
|
* @returns whether any occurrence remained to offload.
|
|
*/
|
|
export function offloadOldestImages(session: Session, sourceEventSeqs: readonly SessionSeq[], count: number): boolean {
|
|
const targets: ImageOffloadTarget[] = []
|
|
for (const seq of sourceEventSeqs) {
|
|
if (count === 0) break
|
|
// Existing Session history read; migration deferred. Current surface nodes index the durable log.
|
|
// oxlint-disable-next-line typescript/no-non-null-assertion, typescript/no-deprecated
|
|
const event = session.eventAt(seq)!
|
|
if (event.type !== 'user/message' && event.type !== 'tool/result') continue
|
|
// oxlint-disable-next-line typescript/no-non-null-assertion -- both input node types produce a message
|
|
const message = session.deriveEventMessage(event)!
|
|
const imageIndexes: number[] = []
|
|
let imageIndex = 0
|
|
const visit = (blocks: readonly ContentBlock[]): void => {
|
|
for (const block of blocks) {
|
|
if (count === 0) break
|
|
if (block.type === 'image') {
|
|
if (block.offloaded !== true) {
|
|
imageIndexes.push(imageIndex)
|
|
count -= 1
|
|
}
|
|
imageIndex += 1
|
|
}
|
|
}
|
|
}
|
|
visit(message.content)
|
|
if (imageIndexes.length > 0) targets.push({ seq, imageIndexes })
|
|
}
|
|
if (targets.length === 0) return false
|
|
session.append('image/offload', { targets })
|
|
return true
|
|
}
|