Ver Fonte

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.
Tianyi Cui há 2 meses atrás
pai
commit
98e224e45e

+ 13 - 6
packages/compact/compact-basic/src/index.ts

@@ -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') {
-      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)
     const threshold = Math.floor(meter.contextWindow * this.config.thresholdRatio)

+ 11 - 5
packages/core/agent-loop/src/loop.ts

@@ -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') {
-          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
         break
       }
       }