refactor(agent): exhaust recovery and compaction decisions

Dispatch RequestErrorDecision and CompactionTrigger through explicit discriminant switches. End each closed union with assertNever so new variants fail compilation instead of silently inheriting fail or pressure behavior.

This preserves the current retry, fail, pressure, and overflow semantics while aligning the new recovery seams with the repository closed-union contract.
This commit is contained in:
Tianyi Cui
2026-07-19 15:02:11 +08:00
parent 1d43472b35
commit 98e224e45e
2 changed files with 24 additions and 11 deletions

View File

@@ -9,7 +9,7 @@ import z from 'schemastery'
import { CompactService } from '@deepseek-ai/dsh-compact' import { CompactService } from '@deepseek-ai/dsh-compact'
import type { CompactionResult, CompactionTrigger } from '@deepseek-ai/dsh-compact' import type { CompactionResult, CompactionTrigger } from '@deepseek-ai/dsh-compact'
import type { Session } from '@deepseek-ai/dsh-session' import type { Session } from '@deepseek-ai/dsh-session'
import { CONTEXT_WINDOW_EXCEEDED_CODE } from '@deepseek-ai/dsh-llm' import { CONTEXT_WINDOW_EXCEEDED_CODE, assertNever } from '@deepseek-ai/dsh-llm'
import type { ContentBlock } from '@deepseek-ai/dsh-llm' import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import type { Agent } from '@deepseek-ai/dsh-agent' import type { Agent } from '@deepseek-ai/dsh-agent'
import { resolveConfig } from './config.ts' import { resolveConfig } from './config.ts'
@@ -152,11 +152,18 @@ export class BasicCompactService extends CompactService {
const model = routedModel(agent.session) const model = routedModel(agent.session)
if (model === undefined) return null if (model === undefined) return null
const meter = this.ctx.tokenMeter const meter = this.ctx.tokenMeter
if (trigger === 'context-overflow') { switch (trigger) {
const measurement = meter.measure(agent.session) case 'context-overflow': {
const range = selectCompactableRange(agent.session, measurement, 0) const measurement = meter.measure(agent.session)
if (range === null) return null const range = selectCompactableRange(agent.session, measurement, 0)
return this.compactRegion(range.start, range.end, agent, signal) if (range === null) return null
return this.compactRegion(range.start, range.end, agent, signal)
}
case 'pressure':
break
/* v8 ignore next -- closed-union exhaustiveness guard */
default:
assertNever(trigger, 'compaction trigger')
} }
const threshold = Math.floor(meter.contextWindow * this.config.thresholdRatio) const threshold = Math.floor(meter.contextWindow * this.config.thresholdRatio)

View File

@@ -8,7 +8,7 @@
import type { Context } from 'cordis' import type { Context } from 'cordis'
import type { ContentBlock, FinishReason, GenerateOptions, LlmCallConfig, Message } from '@deepseek-ai/dsh-llm' import type { ContentBlock, FinishReason, GenerateOptions, LlmCallConfig, Message } from '@deepseek-ai/dsh-llm'
import { isDeepStrictEqual } from 'node:util' import { isDeepStrictEqual } from 'node:util'
import { BlockAssembler, HarnessError, deepFreeze, isLlmAdapterFailure } from '@deepseek-ai/dsh-llm' import { BlockAssembler, HarnessError, assertNever, deepFreeze, isLlmAdapterFailure } from '@deepseek-ai/dsh-llm'
import { agentEvents, assembleContextFor } from '@deepseek-ai/dsh-agent' import { agentEvents, assembleContextFor } from '@deepseek-ai/dsh-agent'
import type { AgentEventDispatch, ContinuationDecision, HookContext, PromptDecision, RequestError, RequestErrorDecision } from '@deepseek-ai/dsh-agent' import type { AgentEventDispatch, ContinuationDecision, HookContext, PromptDecision, RequestError, RequestErrorDecision } from '@deepseek-ai/dsh-agent'
import { canonicalHeader } from '@deepseek-ai/dsh-session' import { canonicalHeader } from '@deepseek-ai/dsh-session'
@@ -392,11 +392,17 @@ async function runTurn(
: { kind: 'aborted', reason: String(abort.signal.reason) } : { kind: 'aborted', reason: String(abort.signal.reason) }
break break
} }
if (recoveryDecision.action === 'retry') { switch (recoveryDecision.action) {
requestRetryAttempt += 1 case 'retry':
continue requestRetryAttempt += 1
continue
case 'fail':
failTurn(stepOutcome.requestError)
break
/* v8 ignore next -- closed-union exhaustiveness guard */
default:
assertNever(recoveryDecision, 'agent request-error decision')
} }
failTurn(stepOutcome.requestError)
break break
} }