Skip to content

Commit 4fe0a5c

Browse files
authored
Merge pull request #247 from Opencode-DCP/feat/question-tool-support
feat: add question tool support for pruning
2 parents f2866a8 + e8faac5 commit 4fe0a5c

File tree

3 files changed

+31
-40
lines changed

3 files changed

+31
-40
lines changed

lib/messages/prune.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ import type { Logger } from "../logger"
33
import type { PluginConfig } from "../config"
44
import { isMessageCompacted } from "../shared-utils"
55

6-
const PRUNED_TOOL_INPUT_REPLACEMENT =
7-
"[content removed to save context, this is not what was written to the file, but a placeholder]"
86
const PRUNED_TOOL_OUTPUT_REPLACEMENT =
97
"[Output removed to save context - information superseded or no longer needed]"
108
const PRUNED_TOOL_ERROR_INPUT_REPLACEMENT = "[input removed due to failed tool call]"
9+
const PRUNED_QUESTION_INPUT_REPLACEMENT = "[questions removed - see output for user's answers]"
1110

1211
export const prune = (
1312
state: SessionState,
@@ -33,7 +32,8 @@ const pruneToolOutputs = (state: SessionState, logger: Logger, messages: WithPar
3332
if (!state.prune.toolIds.includes(part.callID)) {
3433
continue
3534
}
36-
if (part.tool === "write" || part.tool === "edit") {
35+
// Skip write/edit (protected) and question (output contains answers we want to keep)
36+
if (part.tool === "write" || part.tool === "edit" || part.tool === "question") {
3737
continue
3838
}
3939
if (part.state.status === "completed") {
@@ -43,10 +43,6 @@ const pruneToolOutputs = (state: SessionState, logger: Logger, messages: WithPar
4343
}
4444
}
4545

46-
// NOTE: This function is currently unused because "write" and "edit" are protected by default.
47-
// Some models incorrectly use PRUNED_TOOL_INPUT_REPLACEMENT in their output when they see it in context.
48-
// See: https://github.com/Opencode-DCP/opencode-dynamic-context-pruning/issues/215
49-
// Keeping this function in case the bug is resolved in the future.
5046
const pruneToolInputs = (state: SessionState, logger: Logger, messages: WithParts[]): void => {
5147
for (const msg of messages) {
5248
if (isMessageCompacted(state, msg)) {
@@ -60,23 +56,12 @@ const pruneToolInputs = (state: SessionState, logger: Logger, messages: WithPart
6056
if (!state.prune.toolIds.includes(part.callID)) {
6157
continue
6258
}
63-
if (part.tool !== "write" && part.tool !== "edit") {
64-
continue
65-
}
6659
if (part.state.status !== "completed") {
6760
continue
6861
}
6962

70-
if (part.tool === "write" && part.state.input?.content !== undefined) {
71-
part.state.input.content = PRUNED_TOOL_INPUT_REPLACEMENT
72-
}
73-
if (part.tool === "edit") {
74-
if (part.state.input?.oldString !== undefined) {
75-
part.state.input.oldString = PRUNED_TOOL_INPUT_REPLACEMENT
76-
}
77-
if (part.state.input?.newString !== undefined) {
78-
part.state.input.newString = PRUNED_TOOL_INPUT_REPLACEMENT
79-
}
63+
if (part.tool === "question" && part.state.input?.questions !== undefined) {
64+
part.state.input.questions = PRUNED_QUESTION_INPUT_REPLACEMENT
8065
}
8166
}
8267
}

lib/messages/utils.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,26 @@ export const extractParameterKey = (tool: string, parameters: any): string => {
177177
return op
178178
}
179179

180+
if (tool === "question") {
181+
const questions = parameters.questions
182+
if (Array.isArray(questions) && questions.length > 0) {
183+
const headers = questions
184+
.map((q: any) => q.header || "")
185+
.filter(Boolean)
186+
.slice(0, 3)
187+
188+
const count = questions.length
189+
const plural = count > 1 ? "s" : ""
190+
191+
if (headers.length > 0) {
192+
const suffix = count > 3 ? ` (+${count - 3} more)` : ""
193+
return `${count} question${plural}: ${headers.join(", ")}${suffix}`
194+
}
195+
return `${count} question${plural}`
196+
}
197+
return "question"
198+
}
199+
180200
const paramStr = JSON.stringify(parameters)
181201
if (paramStr === "{}" || paramStr === "[]" || paramStr === "null") {
182202
return ""

lib/strategies/utils.ts

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -62,29 +62,15 @@ export const calculateTokensSaved = (
6262
if (part.type !== "tool" || !pruneToolIds.includes(part.callID)) {
6363
continue
6464
}
65-
// For write and edit tools, count input content as that is all we prune for these tools
66-
// (input is present in both completed and error states)
67-
if (part.tool === "write") {
68-
const inputContent = part.state.input?.content
69-
const content =
70-
typeof inputContent === "string"
71-
? inputContent
72-
: JSON.stringify(inputContent ?? "")
73-
contents.push(content)
74-
continue
75-
}
76-
if (part.tool === "edit") {
77-
const oldString = part.state.input?.oldString
78-
const newString = part.state.input?.newString
79-
if (typeof oldString === "string") {
80-
contents.push(oldString)
81-
}
82-
if (typeof newString === "string") {
83-
contents.push(newString)
65+
if (part.tool === "question") {
66+
const questions = part.state.input?.questions
67+
if (questions !== undefined) {
68+
const content =
69+
typeof questions === "string" ? questions : JSON.stringify(questions)
70+
contents.push(content)
8471
}
8572
continue
8673
}
87-
// For other tools, count output or error based on status
8874
if (part.state.status === "completed") {
8975
const content =
9076
typeof part.state.output === "string"

0 commit comments

Comments
 (0)