diff --git a/lib/messages/prune.ts b/lib/messages/prune.ts index eb8aae6..8b95e45 100644 --- a/lib/messages/prune.ts +++ b/lib/messages/prune.ts @@ -3,11 +3,10 @@ import type { Logger } from "../logger" import type { PluginConfig } from "../config" import { isMessageCompacted } from "../shared-utils" -const PRUNED_TOOL_INPUT_REPLACEMENT = - "[content removed to save context, this is not what was written to the file, but a placeholder]" const PRUNED_TOOL_OUTPUT_REPLACEMENT = "[Output removed to save context - information superseded or no longer needed]" const PRUNED_TOOL_ERROR_INPUT_REPLACEMENT = "[input removed due to failed tool call]" +const PRUNED_QUESTION_INPUT_REPLACEMENT = "[questions removed - see output for user's answers]" export const prune = ( state: SessionState, @@ -33,7 +32,8 @@ const pruneToolOutputs = (state: SessionState, logger: Logger, messages: WithPar if (!state.prune.toolIds.includes(part.callID)) { continue } - if (part.tool === "write" || part.tool === "edit") { + // Skip write/edit (protected) and question (output contains answers we want to keep) + if (part.tool === "write" || part.tool === "edit" || part.tool === "question") { continue } if (part.state.status === "completed") { @@ -43,10 +43,6 @@ const pruneToolOutputs = (state: SessionState, logger: Logger, messages: WithPar } } -// NOTE: This function is currently unused because "write" and "edit" are protected by default. -// Some models incorrectly use PRUNED_TOOL_INPUT_REPLACEMENT in their output when they see it in context. -// See: https://github.com/Opencode-DCP/opencode-dynamic-context-pruning/issues/215 -// Keeping this function in case the bug is resolved in the future. const pruneToolInputs = (state: SessionState, logger: Logger, messages: WithParts[]): void => { for (const msg of messages) { if (isMessageCompacted(state, msg)) { @@ -60,23 +56,12 @@ const pruneToolInputs = (state: SessionState, logger: Logger, messages: WithPart if (!state.prune.toolIds.includes(part.callID)) { continue } - if (part.tool !== "write" && part.tool !== "edit") { - continue - } if (part.state.status !== "completed") { continue } - if (part.tool === "write" && part.state.input?.content !== undefined) { - part.state.input.content = PRUNED_TOOL_INPUT_REPLACEMENT - } - if (part.tool === "edit") { - if (part.state.input?.oldString !== undefined) { - part.state.input.oldString = PRUNED_TOOL_INPUT_REPLACEMENT - } - if (part.state.input?.newString !== undefined) { - part.state.input.newString = PRUNED_TOOL_INPUT_REPLACEMENT - } + if (part.tool === "question" && part.state.input?.questions !== undefined) { + part.state.input.questions = PRUNED_QUESTION_INPUT_REPLACEMENT } } } diff --git a/lib/messages/utils.ts b/lib/messages/utils.ts index 9a9a2d8..3e8006d 100644 --- a/lib/messages/utils.ts +++ b/lib/messages/utils.ts @@ -177,6 +177,26 @@ export const extractParameterKey = (tool: string, parameters: any): string => { return op } + if (tool === "question") { + const questions = parameters.questions + if (Array.isArray(questions) && questions.length > 0) { + const headers = questions + .map((q: any) => q.header || "") + .filter(Boolean) + .slice(0, 3) + + const count = questions.length + const plural = count > 1 ? "s" : "" + + if (headers.length > 0) { + const suffix = count > 3 ? ` (+${count - 3} more)` : "" + return `${count} question${plural}: ${headers.join(", ")}${suffix}` + } + return `${count} question${plural}` + } + return "question" + } + const paramStr = JSON.stringify(parameters) if (paramStr === "{}" || paramStr === "[]" || paramStr === "null") { return "" diff --git a/lib/strategies/utils.ts b/lib/strategies/utils.ts index c75081f..fa7d5fe 100644 --- a/lib/strategies/utils.ts +++ b/lib/strategies/utils.ts @@ -62,29 +62,15 @@ export const calculateTokensSaved = ( if (part.type !== "tool" || !pruneToolIds.includes(part.callID)) { continue } - // For write and edit tools, count input content as that is all we prune for these tools - // (input is present in both completed and error states) - if (part.tool === "write") { - const inputContent = part.state.input?.content - const content = - typeof inputContent === "string" - ? inputContent - : JSON.stringify(inputContent ?? "") - contents.push(content) - continue - } - if (part.tool === "edit") { - const oldString = part.state.input?.oldString - const newString = part.state.input?.newString - if (typeof oldString === "string") { - contents.push(oldString) - } - if (typeof newString === "string") { - contents.push(newString) + if (part.tool === "question") { + const questions = part.state.input?.questions + if (questions !== undefined) { + const content = + typeof questions === "string" ? questions : JSON.stringify(questions) + contents.push(content) } continue } - // For other tools, count output or error based on status if (part.state.status === "completed") { const content = typeof part.state.output === "string"