Merge remote-tracking branch 'origin/token-meter-service' into compact-post-step-overflow-recovery

# Conflicts:
#	docs/core-data-structures/compaction.md
#	docs/rfc/implemented/architecture/2026-07-15-replay-token-meter-service.i18n.yaml
#	docs/rfc/implemented/architecture/2026-07-15-replay-token-meter-service.md
#	docs/rfc/implemented/architecture/2026-07-15-replay-token-meter-service.zh.md
#	docs/rfc/implemented/feature/2026-06-18-compaction-capability-seam.md
#	examples/coding-agent/cordis.yml
#	packages/compact/compact-basic/README.md
#	packages/compact/compact-basic/src/automatic.ts
#	packages/compact/compact-basic/src/config.ts
#	packages/compact/compact-basic/src/index.ts
#	packages/compact/compact-basic/tests/compact-basic.spec.ts
This commit is contained in:
Hypatia May
2026-07-16 16:18:16 +08:00
36 files changed
+939 -1292

No files matched your search

+17 -61
View File
@@ -11,34 +11,21 @@ import type { CompactionResult, CompactionTrigger } from '@deepseek-ai/dsh-compa
import type { Session } from '@deepseek-ai/dsh-session'
import { CONTEXT_WINDOW_EXCEEDED_CODE } from '@deepseek-ai/dsh-llm'
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import {
TOKEN_METER_MODEL_UNCONFIGURED,
TokenMeterError,
} from '@deepseek-ai/dsh-token-meter'
import type { ModelTokenMeter } from '@deepseek-ai/dsh-token-meter'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { resolveConfig, resolveModelConfig } from './config.ts'
import { resolveConfig } from './config.ts'
import { compactSurfaceRegion, selectCompactableRange } from './region.ts'
import { summarizeWithLlm } from './summarizer.ts'
import type {
BasicCompactConfig,
ResolvedConfig,
ResolvedModelCompactConfig,
} from './types.ts'
export { resolveConfig, resolveModelConfig } from './config.ts'
export { resolveConfig } from './config.ts'
export type {
BasicCompactConfig,
ModelCompactConfig,
ResolvedConfig,
ResolvedModelCompactConfig,
} from './types.ts'
/** Resolve the latest actual routed model, then the agent's configured fallback. */
function effectiveModel(agent: Agent): string | undefined {
return agent.session.requestHeader()?.config.model ?? agent.options.model
}
/** Resolve the exact model durably routed for the latest provider request. */
function routedModel(session: Session): string | undefined {
const model = session.requestHeader()?.config.model
@@ -50,17 +37,15 @@ function routedModel(session: Session): string | undefined {
* retention, provenance, and summary-convergence pricing.
*
* `summarize()` is the sole subclass customization hook; the replay and durable
* mutation strategy stays fixed so every pricing decision uses one effective
* conversation-model meter.
* mutation strategy stays fixed so every pricing decision uses the singleton
* token meter.
*/
export class BasicCompactService extends CompactService {
static inject = ['llm', 'tokenMeter']
static Config: z<BasicCompactConfig> = z.object({
models: z.dict(z.object({
thresholdRatio: z.number(),
retainTokens: z.number().step(1),
})),
thresholdRatio: z.number().default(0.8),
retainTokens: z.number().step(1),
summarizationModel: z.string().default(''),
maxTokens: z.number().step(1).min(1).default(8192),
compactionRetries: z.number().step(1).min(0).default(1),
@@ -68,11 +53,9 @@ export class BasicCompactService extends CompactService {
auto: z.boolean().default(true),
})
/** Resolved and validated common configuration plus named partial overrides. */
/** Resolved and validated compaction configuration. */
readonly config: ResolvedConfig
private readonly modelConfigs = new Map<string, ResolvedModelCompactConfig>()
constructor(ctx: Context, config: BasicCompactConfig = {}) {
super(ctx)
this.config = resolveConfig(config, ctx.tokenMeter)
@@ -105,10 +88,6 @@ export class BasicCompactService extends CompactService {
const result = await this.compactIfNeeded(agent, 'pressure', signal)
if (result !== null) logResult(result, 'post-step pressure')
} catch (error: unknown) {
// A named routed model without a meter profile is configuration failure,
// not an optional operational compaction miss.
if (error instanceof TokenMeterError
&& error.code === TOKEN_METER_MODEL_UNCONFIGURED) throw error
const message = error instanceof Error ? error.message : String(error)
ctx.logger.warn(`post-step compaction failed: ${message}; continuing the turn`)
}
@@ -157,7 +136,7 @@ export class BasicCompactService extends CompactService {
/**
* Compact for replayed post-step pressure or one provider-confirmed context
* overflow. Both triggers price the latest durable routed request model;
* overflow. Both triggers price the latest durable routed request envelope;
* overflow bypasses the normal threshold and retained-tail policy so it can
* force one useful balanced reduction.
* @param agent - agent whose latest durable routed request is measured.
@@ -172,28 +151,21 @@ export class BasicCompactService extends CompactService {
): Promise<CompactionResult | null> {
const model = routedModel(agent.session)
if (model === undefined) return null
const meter = this.ctx.tokenMeter.resolve(model)
const policy = this._modelConfig(meter)
const meter = this.ctx.tokenMeter
if (trigger === 'context-overflow') {
const surface = meter.measureSurface(agent.session)
const range = selectCompactableRange(agent.session, surface, 0)
const measurement = meter.measure(agent.session)
const range = selectCompactableRange(agent.session, measurement, 0)
if (range === null) return null
return this.compactRegion(agent.session, range.start, range.end, agent, signal)
}
const threshold = Math.floor(policy.contextWindow * policy.thresholdRatio)
const threshold = Math.floor(meter.contextWindow * this.config.thresholdRatio)
let measurement = meter.measure(agent.session)
if (measurement.totalTokens < threshold) return null
let result: CompactionResult | null = null
for (let attempt = 0; attempt <= this.config.compactionRetries; attempt += 1) {
const surface = meter.measureSurface(agent.session)
if (surface.logRevision !== measurement.logRevision) {
throw new Error(
`compaction: pressure revision ${measurement.logRevision} does not match surface revision ${surface.logRevision}`,
)
}
const range = selectCompactableRange(agent.session, surface, policy.retainTokens)
const range = selectCompactableRange(agent.session, measurement, this.config.retainTokens)
if (range === null) {
/* v8 ignore else -- concrete replacement preserves a compactable checkpoint; subclass hooks cannot mutate it. */
if (result === null) return null
@@ -213,12 +185,12 @@ export class BasicCompactService extends CompactService {
/**
* Compact one inclusive positional surface range using the effective
* conversation model for all retention and shrink pricing. Reject an agent
* that does not own the exact target before any resolution or mutation.
* token meter for all retention and shrink pricing. Reject an agent that does
* not own the exact target before any mutation.
* @param session - session whose surface is mutated; must equal `agent.session`.
* @param start - inclusive first surface-node seq.
* @param end - inclusive last surface-node seq.
* @param agent - owner of the target session, used by the summarizer and model resolver.
* @param agent - owner of the target session, used by the summarizer.
* @param signal - optional summarization cancellation signal.
* @returns the successful durable compaction result.
*/
@@ -232,27 +204,11 @@ export class BasicCompactService extends CompactService {
if (session !== agent.session) {
throw new Error('compactRegion: agent.session must be the exact target session')
}
const model = effectiveModel(agent)
if (model === undefined || model.length === 0) {
throw new Error('compactRegion: no routed or configured conversation model is available for token pricing')
}
const meter = this.ctx.tokenMeter.resolve(model)
this._modelConfig(meter)
return compactSurfaceRegion({
meter,
meter: this.ctx.tokenMeter,
summarize: (text, owner, abort) => this.summarize(text, owner, abort),
}, session, start, end, agent, signal)
}
/** Resolve and memoize one lazy default/override model policy. */
private _modelConfig(meter: ModelTokenMeter): ResolvedModelCompactConfig {
let modelConfig = this.modelConfigs.get(meter.model)
if (modelConfig === undefined) {
modelConfig = resolveModelConfig(this.config, meter)
this.modelConfigs.set(meter.model, modelConfig)
}
return modelConfig
}
}
export default BasicCompactService