diff --git a/packages/builtin-tools/src/tools/SnipTool/SnipTool.ts b/packages/builtin-tools/src/tools/SnipTool/SnipTool.ts index 39629f2b6..e81462bdb 100644 --- a/packages/builtin-tools/src/tools/SnipTool/SnipTool.ts +++ b/packages/builtin-tools/src/tools/SnipTool/SnipTool.ts @@ -79,13 +79,11 @@ Guidelines: }, async call(input: SnipInput) { - // Snip implementation is handled by the query engine's projection system. - // The tool call itself records the intent; the query engine intercepts - // snip tool results and adjusts its message projection accordingly. + const ids = input?.message_ids ?? [] return { data: { - snipped_count: input.message_ids.length, - summary: input.reason ?? `Snipped ${input.message_ids.length} messages`, + snipped_count: ids.length, + summary: input?.reason ?? `Snipped ${ids.length} messages`, }, } }, diff --git a/src/commands/force-snip.ts b/src/commands/force-snip.ts index e20febd76..c874ac17d 100644 --- a/src/commands/force-snip.ts +++ b/src/commands/force-snip.ts @@ -1,3 +1,4 @@ +import type { UUID } from 'crypto' import { randomUUID } from 'crypto' import type { Command, LocalCommandCall } from '../types/command.js' import type { Message } from '../types/message.js' @@ -25,7 +26,7 @@ const call: LocalCommandCall = async (_args, context) => { // Collect UUIDs of every message that will be snipped (everything currently // in the conversation). The next call to `snipCompactIfNeeded` will honour // the boundary and strip these from the model-facing view. - const removedUuids = messages.map(m => m.uuid) + const removedUuids = messages.map(m => m.uuid).filter((u): u is UUID => !!u) const boundaryMessage: Message = { type: 'system', diff --git a/src/services/compact/snipCompact.ts b/src/services/compact/snipCompact.ts index e95d6bfe0..5aa6adcda 100644 --- a/src/services/compact/snipCompact.ts +++ b/src/services/compact/snipCompact.ts @@ -126,12 +126,15 @@ export function snipCompactIfNeeded( } // Filter out messages whose UUIDs are listed in removedUuids - const removedSet = new Set(removedUuids) + // Guard: filter out any falsy entries (e.g. messages that lacked a uuid) + const removedSet = new Set( + removedUuids.filter((u): u is string => typeof u === 'string'), + ) const kept: Message[] = [] let tokensFreed = 0 for (const msg of messages) { - if (removedSet.has(msg.uuid)) { + if (msg.uuid && removedSet.has(msg.uuid)) { tokensFreed += estimateMessageTokens(msg) continue } diff --git a/src/services/compact/snipProjection.ts b/src/services/compact/snipProjection.ts index b5d942068..92a65a704 100644 --- a/src/services/compact/snipProjection.ts +++ b/src/services/compact/snipProjection.ts @@ -46,7 +46,7 @@ export function projectSnippedView(messages: Message[]): Message[] { | undefined if (meta?.removedUuids) { for (const uuid of meta.removedUuids) { - removedSet.add(uuid) + if (typeof uuid === 'string') removedSet.add(uuid) } } } diff --git a/src/utils/sessionStorage.ts b/src/utils/sessionStorage.ts index e38dd96b0..bfa892662 100644 --- a/src/utils/sessionStorage.ts +++ b/src/utils/sessionStorage.ts @@ -1996,7 +1996,9 @@ function applySnipRemovals(messages: Map): void { for (const entry of messages.values()) { const removedUuids = (entry as WithSnipMeta).snipMetadata?.removedUuids if (!removedUuids) continue - for (const uuid of removedUuids) toDelete.add(uuid) + for (const uuid of removedUuids) { + if (uuid) toDelete.add(uuid) + } } if (toDelete.size === 0) return