Download packages/agent-core-v2/src/agent/fullCompaction/strategy.ts from SaylorTwift/kimi-code: direct link, hf CLI and curl.
- Browser
- Download file 8.22 kB
-
https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/agent-core-v2/src/agent/fullCompaction/strategy.ts
- Command line
-
hf download hf://SaylorTwift/kimi-code/packages/agent-core-v2/src/agent/fullCompaction/strategy.ts
-
curl -L -o strategy.ts https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/agent-core-v2/src/agent/fullCompaction/strategy.ts
8.22 kB
| import type { Message } from '#/llm-adapter/contract/message'; | |
| import type { ProfileModelContext } from '#/agent/profile/profile'; | |
| import type { CompactionSource } from './types'; | |
| import { estimateTokensForMessage } from '#/llm-adapter/contract/tokens'; | |
| export interface CompactionConfig { | |
| triggerRatio: number; | |
| blockRatio: number; | |
| reservedContextSize: number; | |
| maxCompactionPerTurn: number; | |
| maxOverflowCompactionAttempts: number; | |
| maxRecentMessages: number; | |
| maxRecentUserMessages: number; | |
| maxRecentSizeRatio: number; | |
| minOverflowReductionRatio: number; | |
| } | |
| export const DEFAULT_COMPACTION_CONFIG: CompactionConfig = { | |
| triggerRatio: 0.85, | |
| blockRatio: 0.85, | |
| reservedContextSize: 50_000, | |
| maxCompactionPerTurn: Infinity, | |
| maxOverflowCompactionAttempts: 3, | |
| maxRecentMessages: 4, | |
| maxRecentUserMessages: Infinity, | |
| maxRecentSizeRatio: 0.2, | |
| minOverflowReductionRatio: 0.05, | |
| }; | |
| export interface CompactionStrategy { | |
| shouldCompact(usedSize: number): boolean; | |
| shouldBlock(usedSize: number): boolean; | |
| computeCompactCount(messages: readonly Message[], source: CompactionSource): number; | |
| reduceCompactOnOverflow(messages: readonly Message[]): number; | |
| readonly checkAfterStep: boolean; | |
| readonly maxCompactionPerTurn: number; | |
| readonly maxOverflowCompactionAttempts: number; | |
| } | |
| export class RuntimeCompactionStrategy implements CompactionStrategy { | |
| constructor( | |
| private readonly context: () => ProfileModelContext, | |
| private readonly estimateMessage: (message: Message) => number = estimateTokensForMessage, | |
| ) { } | |
| shouldCompact(usedSize: number): boolean { | |
| return this.delegate().shouldCompact(usedSize); | |
| } | |
| shouldBlock(usedSize: number): boolean { | |
| return this.delegate().shouldBlock(usedSize); | |
| } | |
| computeCompactCount(messages: readonly Message[], source: CompactionSource): number { | |
| return this.windowDelegate().computeCompactCount(messages, source); | |
| } | |
| reduceCompactOnOverflow(messages: readonly Message[]): number { | |
| return this.windowDelegate().reduceCompactOnOverflow(messages); | |
| } | |
| get checkAfterStep(): boolean { | |
| return this.config().triggerRatio !== this.config().blockRatio; | |
| } | |
| get maxCompactionPerTurn(): number { | |
| return DEFAULT_COMPACTION_CONFIG.maxCompactionPerTurn; | |
| } | |
| get maxOverflowCompactionAttempts(): number { | |
| return DEFAULT_COMPACTION_CONFIG.maxOverflowCompactionAttempts; | |
| } | |
| private delegate(): DefaultCompactionStrategy { | |
| const model = this.context(); | |
| return new DefaultCompactionStrategy( | |
| () => model.modelCapabilities.max_input_tokens ?? model.modelCapabilities.max_context_tokens, | |
| this.config(model), | |
| this.estimateMessage, | |
| ); | |
| } | |
| private windowDelegate(): DefaultCompactionStrategy { | |
| return new DefaultCompactionStrategy( | |
| () => this.context().modelCapabilities.max_input_tokens ?? this.context().modelCapabilities.max_context_tokens, | |
| DEFAULT_COMPACTION_CONFIG, | |
| this.estimateMessage, | |
| ); | |
| } | |
| private config(model: ProfileModelContext = this.context()): CompactionConfig { | |
| const triggerRatio = model.compactionTriggerRatio ?? DEFAULT_COMPACTION_CONFIG.triggerRatio; | |
| const blockRatio = Math.max(triggerRatio, DEFAULT_COMPACTION_CONFIG.blockRatio); | |
| return { | |
| ...DEFAULT_COMPACTION_CONFIG, | |
| triggerRatio, | |
| blockRatio, | |
| reservedContextSize: | |
| model.reservedContextSize ?? DEFAULT_COMPACTION_CONFIG.reservedContextSize, | |
| }; | |
| } | |
| } | |
| export class DefaultCompactionStrategy implements CompactionStrategy { | |
| constructor( | |
| protected readonly maxSizeProvider: () => number, | |
| protected readonly config: CompactionConfig = DEFAULT_COMPACTION_CONFIG, | |
| protected readonly estimateMessage: (message: Message) => number = estimateTokensForMessage, | |
| ) { } | |
| protected get maxSize(): number { | |
| return this.maxSizeProvider(); | |
| } | |
| shouldCompact(usedSize: number): boolean { | |
| if (this.maxSize <= 0) return false; | |
| return ( | |
| usedSize >= this.maxSize * this.config.triggerRatio || | |
| this.shouldUseReservedContext(usedSize) | |
| ); | |
| } | |
| shouldBlock(usedSize: number): boolean { | |
| if (this.maxSize <= 0) return false; | |
| return ( | |
| usedSize >= this.maxSize * this.config.blockRatio || | |
| this.shouldUseReservedContext(usedSize) | |
| ); | |
| } | |
| private shouldUseReservedContext(usedSize: number): boolean { | |
| const reservedSize = this.config.reservedContextSize; | |
| return reservedSize > 0 && reservedSize < this.maxSize && usedSize + reservedSize >= this.maxSize; | |
| } | |
| computeCompactCount(messages: readonly Message[], source: CompactionSource): number { | |
| if (source === 'manual') { | |
| for (let i = messages.length - 1; i > 0; i--) { | |
| if (canSplitAfter(messages, i)) { | |
| return this.fitCompactCountToWindow(messages, i + 1); | |
| } | |
| } | |
| return 0; | |
| } | |
| let recentMessages = 1; | |
| let recentUserMessages = 0; | |
| let recentSize = 0; | |
| let bestN: number | undefined; | |
| for (; recentMessages < messages.length; recentMessages++) { | |
| const splitIndex = messages.length - recentMessages - 1; | |
| const m2 = messages[messages.length - recentMessages]!; | |
| if (m2.role === 'user') { | |
| recentUserMessages++; | |
| } | |
| recentSize += this.estimateMessage(m2); | |
| if (canSplitAfter(messages, splitIndex)) { | |
| bestN = splitIndex + 1; | |
| } | |
| const reachesMax = recentMessages >= this.config.maxRecentMessages | |
| || recentUserMessages >= this.config.maxRecentUserMessages | |
| || recentSize >= this.maxSize * this.config.maxRecentSizeRatio; | |
| if (reachesMax && bestN !== undefined) { | |
| break; | |
| } | |
| } | |
| return this.fitCompactCountToWindow(messages, bestN ?? 0); | |
| } | |
| reduceCompactOnOverflow(messages: readonly Message[]): number { | |
| const minReducedSize = Math.max( | |
| 1, | |
| Math.ceil(this.maxSize * this.config.minOverflowReductionRatio), | |
| ); | |
| let reducedSize = 0; | |
| let bestN: number | undefined; | |
| for (let i = messages.length - 2; i > 0; i--) { | |
| reducedSize += this.estimateMessage(messages[i + 1]!); | |
| if (canSplitAfter(messages, i)) { | |
| bestN = i + 1; | |
| if (reducedSize >= minReducedSize) { | |
| return i + 1; | |
| } | |
| } | |
| } | |
| return bestN ?? messages.length; | |
| } | |
| private fitCompactCountToWindow( | |
| messages: readonly Message[], | |
| compactedCount: number, | |
| ): number { | |
| if (this.maxSize <= 0 || compactedCount <= 0) { | |
| return compactedCount; | |
| } | |
| let compactedSize = 0; | |
| for (let i = 0; i < compactedCount; i++) { | |
| compactedSize += this.estimateMessage(messages[i]!); | |
| } | |
| if (compactedSize <= this.maxSize) { | |
| return compactedCount; | |
| } | |
| let bestN: number | undefined; | |
| for (let n = compactedCount - 1; n > 0; n--) { | |
| compactedSize -= this.estimateMessage(messages[n]!); | |
| if (!canSplitAfter(messages, n - 1)) { | |
| continue; | |
| } | |
| bestN = n; | |
| if (compactedSize <= this.maxSize) { | |
| return n; | |
| } | |
| } | |
| return bestN ?? compactedCount; | |
| } | |
| get checkAfterStep(): boolean { | |
| return this.config.triggerRatio !== this.config.blockRatio; | |
| } | |
| get maxCompactionPerTurn(): number { | |
| return this.config.maxCompactionPerTurn; | |
| } | |
| get maxOverflowCompactionAttempts(): number { | |
| return this.config.maxOverflowCompactionAttempts; | |
| } | |
| } | |
| function canSplitAfter(messages: readonly Message[], index: number): boolean { | |
| const m = messages[index]; | |
| if (m === undefined) return false; | |
| if (m.role === 'user') return false; | |
| if (m.role === 'assistant' && m.toolCalls.length > 0) return false; | |
| if (messages[index + 1]?.role === 'tool') return false; | |
| if (prefixEndsWithOpenToolExchange(messages, index)) return false; | |
| return true; | |
| } | |
| function prefixEndsWithOpenToolExchange(messages: readonly Message[], index: number): boolean { | |
| if (messages[index]?.role !== 'tool') return false; | |
| let toolResultCount = 0; | |
| for (let i = index; i >= 0; i--) { | |
| const message = messages[i]; | |
| if (message === undefined) return false; | |
| if (message.role === 'tool') { | |
| toolResultCount++; | |
| continue; | |
| } | |
| return message.role === 'assistant' && message.toolCalls.length > toolResultCount; | |
| } | |
| return false; | |
| } | |