From 98e224e45e04308b99751eae4bc227df1c2ba763 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 19 Jul 2026 15:02:11 +0800 Subject: [PATCH] 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. --- packages/compact/compact-basic/src/index.ts | 19 +++++++++++++------ packages/core/agent-loop/src/loop.ts | 16 +++++++++++----- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/packages/compact/compact-basic/src/index.ts b/packages/compact/compact-basic/src/index.ts index b77ebebc76..5d325d57ba 100644 --- a/packages/compact/compact-basic/src/index.ts +++ b/packages/compact/compact-basic/src/index.ts @@ -9,7 +9,7 @@ import z from 'schemastery' import { CompactService } from '@deepseek-ai/dsh-compact' import type { CompactionResult, CompactionTrigger } from '@deepseek-ai/dsh-compact' 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 { Agent } from '@deepseek-ai/dsh-agent' import { resolveConfig } from './config.ts' @@ -152,11 +152,18 @@ export class BasicCompactService extends CompactService { const model = routedModel(agent.session) if (model === undefined) return null const meter = this.ctx.tokenMeter - if (trigger === 'context-overflow') { - const measurement = meter.measure(agent.session) - const range = selectCompactableRange(agent.session, measurement, 0) - if (range === null) return null - return this.compactRegion(range.start, range.end, agent, signal) + switch (trigger) { + case 'context-overflow': { + const measurement = meter.measure(agent.session) + const range = selectCompactableRange(agent.session, measurement, 0) + 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) diff --git a/packages/core/agent-loop/src/loop.ts b/packages/core/agent-loop/src/loop.ts index 54cbbc6bdd..08c1ab9412 100644 --- a/packages/core/agent-loop/src/loop.ts +++ b/packages/core/agent-loop/src/loop.ts @@ -8,7 +8,7 @@ import type { Context } from 'cordis' import type { ContentBlock, FinishReason, GenerateOptions, LlmCallConfig, Message } from '@deepseek-ai/dsh-llm' 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 type { AgentEventDispatch, ContinuationDecision, HookContext, PromptDecision, RequestError, RequestErrorDecision } from '@deepseek-ai/dsh-agent' import { canonicalHeader } from '@deepseek-ai/dsh-session' @@ -392,11 +392,17 @@ async function runTurn( : { kind: 'aborted', reason: String(abort.signal.reason) } break } - if (recoveryDecision.action === 'retry') { - requestRetryAttempt += 1 - continue + switch (recoveryDecision.action) { + case 'retry': + 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 }