description: "Bounded model-facing output for tools that must cap how much context they return: item and text retainers plus a standardized omission footer."
English | 中文
Use dsh-output-retention to cap the items or text a tool returns to a model while reporting what was omitted. ItemRetainer keeps an ordered head window and can report an exact omitted-item count; TextRetainer keeps head, tail, or head-and-tail byte windows without returning invalid UTF-8 cuts. formatRetentionNotice adds a consistent omission clause while each tool supplies its own recovery guidance. Grouping, line numbering, spill files, and provider errors remain tool responsibilities; consumers import this library directly rather than loading it through cordis.yml.
Use a retainer wherever a tool must cap how much of its result reaches the model, and report honestly what was dropped. Choose ItemRetainer for ordered logical units and TextRetainer for byte-oriented streams.
import { ItemRetainer } from '@deepseek-ai/dsh-output-retention'
declare const globMaxResults: number
declare const candidates: AsyncIterable<{ path: string }>
const retainer = new ItemRetainer<{ path: string }>({ kind: 'head', maxItems: globMaxResults })
for await (const entry of candidates) {
retainer.push(entry) // keep draining past the cap for an exact count
}
const { items, truncated, omitted } = retainer.finish()
push() reports per item whether it was kept, and finish() returns the retained items plus omitted — an exact count when the caller kept feeding every observed unit. A search tool can collect the full result set for a spill file while retaining only the first page for the model.
import { TextRetainer } from '@deepseek-ai/dsh-output-retention'
const out = new TextRetainer({ kind: 'headTail', headBytes: headCap, tailBytes: tailCap })
child.stdout.on('data', (chunk: Buffer) => { out.push(chunk) })
const { text, omittedBytes } = out.finish()
head, tail, and headTail count bytes, not characters or lines: a child's pipe and an HTTP body are byte streams. finish() trims a partial codepoint at each cut, so the returned text never carries a replacement character introduced by the cut, and a codepoint is never reconstructed across the omitted middle.
import { formatRetentionNotice } from '@deepseek-ai/dsh-output-retention'
declare const grepMaxMatches: number
declare const items: { length: number }
import type { Omitted } from '@deepseek-ai/dsh-output-retention'
declare const omitted: Omitted
const footer = formatRetentionNotice(
{ scope: 'grep', strategy: 'head', unit: 'items', limit: grepMaxMatches, kept: items.length, omitted },
({ kept }) => `Results capped at ${kept}. Narrow the pattern, path, or include to see more.`,
)
The library standardizes the omission clause (Omitted 3 items.) and joins it with the tool's own recovery guidance; only the tool knows the recovery action, so the tool supplies those words.
truncated meanstruncated is a budget fact: the retainer omitted otherwise-available content because of a cap. It never means the upstream was incomplete — permission failures, skipped binary files, provider partial failures, and unreadable candidates stay in tool-domain fields, never folded into truncated.
| Tool | Retainer | What the tool still owns |
|---|---|---|
glob |
ItemRetainer, head |
Spill-file collection, path mapping, skipped candidates, incomplete |
grep |
ItemRetainer, head |
Spill-file collection, per-match preview truncation, grouping, sorting |
bash |
TextRetainer, tail or headTail |
Spill files, exit status, signal, timeout, background jobs |
web_fetch |
TextRetainer, head or headTail |
Provider and resource caps, error states |
web_search |
ItemRetainer, head |
The "sources capped" notice wording and provider facts |
read stays outside this library: its line-window pagination (offset/limit, line numbers, totalLines) is a file-specific renderer that a single omission count cannot represent.
Read these pages when you need the consumers or the boundary decision behind the library.
TextRetainer for a bounded preview around a spill-file notice.ItemRetainer consumer collecting full results for spill.Indirectly, through the retention consumers that render retained content and omission metadata.
No direct invalidation; the retention consumers own any request-prefix changes.
These limits define what the retainers deliberately do not cover. They are current package constraints, not a task backlog.
head only — tail, head/tail, pagination, grouping, and provider-completeness semantics remain tool-owned.read pagination require a separate renderer, and a cut may discard partial UTF-8 boundary bytes to keep the returned text valid.