fix(compact): make config knobs explicit and flag two review smells

Address @tianyicui's minor-revision review on PR #110:

- Make every BasicCompactConfig knob required except `auto` (defaults
  true): there is no data yet to justify default thresholds/budgets, so
  a consumer states each value explicitly. Drop the DEFAULTS export and
  the constructor's `= {}` default; example cordis.yml, the compaction
  e2e, the README, and every test construction site now pass a complete
  config (tests route through a `cfg()` helper).
- Add a TODO on estimateContentTokens: char/4 is coarse; replace with a
  real tokenizer or post-response usage feedback in a follow-up.
- Add a TODO on the agent/pre-step `fullSystemPrompt` param flagging it
  as a smell on a generic per-step seam (compaction is its sole
  consumer); a `//` line comment so it stays out of the generated catalog.
This commit is contained in:
Tianyi Cui
2026-07-01 22:10:49 +08:00
parent 1a5302dbcf
commit 5252477bc9
9 changed files with 139 additions and 97 deletions

View File

@@ -39,7 +39,7 @@ import type { BasicCompactConfig, ResolvedConfig } from './types.ts'
import { resolveConfig } from './types.ts'
export type { BasicCompactConfig, ResolvedConfig } from './types.ts'
export { DEFAULTS, resolveConfig } from './types.ts'
export { resolveConfig } from './types.ts'
/** Per-block structural overhead for JSON framing / type tag. */
const BLOCK_OVERHEAD = 4
@@ -155,10 +155,10 @@ function finishError(finish: FinishReason): Error | undefined {
export class BasicCompactService extends CompactService {
static inject = ['llm']
/** Resolved configuration (defaults applied). */
/** Resolved configuration (`auto` defaulted). */
readonly config: ResolvedConfig
constructor(ctx: Context, config: BasicCompactConfig = {}) {
constructor(ctx: Context, config: BasicCompactConfig) {
super(ctx)
this.config = resolveConfig(config)
@@ -207,6 +207,9 @@ export class BasicCompactService extends CompactService {
// ---- Token estimation (overridable hooks) ----
// TODO: char/4 is a coarse heuristic. Replace with an exact count — a real
// tokenizer, or the provider's post-response `usage` (input tokens) fed back
// as a correction — so threshold decisions match the model's actual budget.
/**
* Estimate the token count of content blocks — char/4 with per-block
* overhead. Override in a subclass to plug in a real tokenizer.

View File

@@ -9,40 +9,34 @@
* @module @deepseek-ai/dsh-compact-basic/types
*/
/** Backend configuration — all optional with sensible defaults. */
/**
* Backend configuration. Every knob is REQUIRED except `auto`: there is no
* concrete data yet to justify default thresholds/budgets, so a consumer must
* state each value explicitly rather than inherit a guessed default. `auto`
* alone defaults to `true` (auto-compaction is the intended posture).
*/
export interface BasicCompactConfig {
/** Context window size in tokens (default 128000). */
contextWindow?: number
/** Compact when estimated token usage exceeds this fraction of context window (default 0.8). */
thresholdRatio?: number
/** Number of tokens of recent context to retain during compaction (default 20480). */
retainTokens?: number
/** Model to use for summarization (default '' — uses the agent's model). */
summarizationModel?: string
/** Provider generation cap for the summarization call (default 8192). */
maxTokens?: number
/** Extra compaction attempts when the first compacted surface is still over threshold (default 1). */
compactionRetries?: number
/** Context window size in tokens. */
contextWindow: number
/** Compact when estimated token usage exceeds this fraction of context window. */
thresholdRatio: number
/** Number of tokens of recent context to retain during compaction. */
retainTokens: number
/** Model to use for summarization (`''` — uses the agent's model). */
summarizationModel: string
/** Provider generation cap for the summarization call. */
maxTokens: number
/** Extra compaction attempts when the first compacted surface is still over threshold. */
compactionRetries: number
/** Enable automatic compaction on the `agent/pre-step` seam (default true). */
auto?: boolean
}
/** Resolved config with all defaults applied. */
/** Resolved config with `auto` defaulted. */
export type ResolvedConfig = Required<BasicCompactConfig>
/** Default configuration values. */
export const DEFAULTS: ResolvedConfig = {
contextWindow: 128000,
thresholdRatio: 0.8,
retainTokens: 20480,
summarizationModel: '',
maxTokens: 8192,
compactionRetries: 1,
auto: true,
}
/**
* Apply defaults to a partial config and reject nonsensical numeric knobs.
* Default `auto` when unset and reject nonsensical numeric knobs.
*
* Convergence is not a static config invariant: provider generation caps can be
* spent on hidden or surfaced reasoning tokens, and the model may emit a summary
@@ -52,7 +46,7 @@ export const DEFAULTS: ResolvedConfig = {
* throwing if the surface still exceeds the threshold.
*/
export function resolveConfig(config: BasicCompactConfig): ResolvedConfig {
const resolved = { ...DEFAULTS, ...config }
const resolved: ResolvedConfig = { auto: true, ...config }
assertPositiveInteger('contextWindow', resolved.contextWindow)
assertRatio('thresholdRatio', resolved.thresholdRatio)