Skip to content

Commit a3dcb2a

Browse files
authored
Merge pull request #248 from Opencode-DCP/dev
merge dev into master
2 parents b28f212 + 89e311d commit a3dcb2a

File tree

14 files changed

+86
-83
lines changed

14 files changed

+86
-83
lines changed

index.ts

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import type { Plugin } from "@opencode-ai/plugin"
22
import { getConfig } from "./lib/config"
33
import { Logger } from "./lib/logger"
4-
import { loadPrompt } from "./lib/prompts"
54
import { createSessionState } from "./lib/state"
65
import { createDiscardTool, createExtractTool } from "./lib/strategies"
7-
import { createChatMessageTransformHandler } from "./lib/hooks"
6+
import { createChatMessageTransformHandler, createSystemPromptHandler } from "./lib/hooks"
87

98
const plugin: Plugin = (async (ctx) => {
109
const config = getConfig(ctx)
@@ -13,11 +12,6 @@ const plugin: Plugin = (async (ctx) => {
1312
return {}
1413
}
1514

16-
// Suppress AI SDK warnings
17-
if (typeof globalThis !== "undefined") {
18-
;(globalThis as any).AI_SDK_LOG_WARNINGS = false
19-
}
20-
2115
const logger = new Logger(config.debug)
2216
const state = createSessionState()
2317

@@ -26,38 +20,8 @@ const plugin: Plugin = (async (ctx) => {
2620
})
2721

2822
return {
29-
"experimental.chat.system.transform": async (
30-
_input: unknown,
31-
output: { system: string[] },
32-
) => {
33-
const systemText = output.system.join("\n")
34-
const internalAgentSignatures = [
35-
"You are a title generator",
36-
"You are a helpful AI assistant tasked with summarizing conversations",
37-
"Summarize what was done in this conversation",
38-
]
39-
if (internalAgentSignatures.some((sig) => systemText.includes(sig))) {
40-
logger.info("Skipping DCP system prompt injection for internal agent")
41-
return
42-
}
23+
"experimental.chat.system.transform": createSystemPromptHandler(state, logger, config),
4324

44-
const discardEnabled = config.tools.discard.enabled
45-
const extractEnabled = config.tools.extract.enabled
46-
47-
let promptName: string
48-
if (discardEnabled && extractEnabled) {
49-
promptName = "user/system/system-prompt-both"
50-
} else if (discardEnabled) {
51-
promptName = "user/system/system-prompt-discard"
52-
} else if (extractEnabled) {
53-
promptName = "user/system/system-prompt-extract"
54-
} else {
55-
return
56-
}
57-
58-
const syntheticPrompt = loadPrompt(promptName)
59-
output.system.push(syntheticPrompt)
60-
},
6125
"experimental.chat.messages.transform": createChatMessageTransformHandler(
6226
ctx.client,
6327
state,

lib/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ const DEFAULT_PROTECTED_TOOLS = [
6868
"batch",
6969
"write",
7070
"edit",
71+
"plan_enter",
72+
"plan_exit",
7173
]
7274

7375
// Valid config keys for validation against user config

lib/hooks.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,48 @@ import { syncToolCache } from "./state/tool-cache"
55
import { deduplicate, supersedeWrites, purgeErrors } from "./strategies"
66
import { prune, insertPruneToolContext } from "./messages"
77
import { checkSession } from "./state"
8+
import { loadPrompt } from "./prompts"
9+
10+
const INTERNAL_AGENT_SIGNATURES = [
11+
"You are a title generator",
12+
"You are a helpful AI assistant tasked with summarizing conversations",
13+
"Summarize what was done in this conversation",
14+
]
15+
16+
export function createSystemPromptHandler(
17+
state: SessionState,
18+
logger: Logger,
19+
config: PluginConfig,
20+
) {
21+
return async (_input: unknown, output: { system: string[] }) => {
22+
if (state.isSubAgent) {
23+
return
24+
}
25+
26+
const systemText = output.system.join("\n")
27+
if (INTERNAL_AGENT_SIGNATURES.some((sig) => systemText.includes(sig))) {
28+
logger.info("Skipping DCP system prompt injection for internal agent")
29+
return
30+
}
31+
32+
const discardEnabled = config.tools.discard.enabled
33+
const extractEnabled = config.tools.extract.enabled
34+
35+
let promptName: string
36+
if (discardEnabled && extractEnabled) {
37+
promptName = "system/system-prompt-both"
38+
} else if (discardEnabled) {
39+
promptName = "system/system-prompt-discard"
40+
} else if (extractEnabled) {
41+
promptName = "system/system-prompt-extract"
42+
} else {
43+
return
44+
}
45+
46+
const syntheticPrompt = loadPrompt(promptName)
47+
output.system.push(syntheticPrompt)
48+
}
49+
}
850

951
export function createChatMessageTransformHandler(
1052
client: any,

lib/messages/inject.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ const getNudgeString = (config: PluginConfig): string => {
1616
const extractEnabled = config.tools.extract.enabled
1717

1818
if (discardEnabled && extractEnabled) {
19-
return loadPrompt(`user/nudge/nudge-both`)
19+
return loadPrompt(`nudge/nudge-both`)
2020
} else if (discardEnabled) {
21-
return loadPrompt(`user/nudge/nudge-discard`)
21+
return loadPrompt(`nudge/nudge-discard`)
2222
} else if (extractEnabled) {
23-
return loadPrompt(`user/nudge/nudge-extract`)
23+
return loadPrompt(`nudge/nudge-extract`)
2424
}
2525
return ""
2626
}

lib/messages/prune.ts

Lines changed: 10 additions & 21 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,20 +32,18 @@ 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+
if (part.state.status !== "completed") {
3736
continue
3837
}
39-
if (part.state.status === "completed") {
40-
part.state.output = PRUNED_TOOL_OUTPUT_REPLACEMENT
38+
if (part.tool === "question") {
39+
continue
4140
}
41+
42+
part.state.output = PRUNED_TOOL_OUTPUT_REPLACEMENT
4243
}
4344
}
4445
}
4546

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.
5047
const pruneToolInputs = (state: SessionState, logger: Logger, messages: WithParts[]): void => {
5148
for (const msg of messages) {
5249
if (isMessageCompacted(state, msg)) {
@@ -60,23 +57,15 @@ const pruneToolInputs = (state: SessionState, logger: Logger, messages: WithPart
6057
if (!state.prune.toolIds.includes(part.callID)) {
6158
continue
6259
}
63-
if (part.tool !== "write" && part.tool !== "edit") {
60+
if (part.state.status !== "completed") {
6461
continue
6562
}
66-
if (part.state.status !== "completed") {
63+
if (part.tool !== "question") {
6764
continue
6865
}
6966

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-
}
67+
if (part.state.input?.questions !== undefined) {
68+
part.state.input.questions = PRUNED_QUESTION_INPUT_REPLACEMENT
8069
}
8170
}
8271
}

lib/messages/utils.ts

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

149+
if (tool === "question") {
150+
const questions = parameters.questions
151+
if (Array.isArray(questions) && questions.length > 0) {
152+
const headers = questions
153+
.map((q: any) => q.header || "")
154+
.filter(Boolean)
155+
.slice(0, 3)
156+
157+
const count = questions.length
158+
const plural = count > 1 ? "s" : ""
159+
160+
if (headers.length > 0) {
161+
const suffix = count > 3 ? ` (+${count - 3} more)` : ""
162+
return `${count} question${plural}: ${headers.join(", ")}${suffix}`
163+
}
164+
return `${count} question${plural}`
165+
}
166+
return "question"
167+
}
168+
149169
const paramStr = JSON.stringify(parameters)
150170
if (paramStr === "{}" || paramStr === "[]" || paramStr === "null") {
151171
return ""
File renamed without changes.

0 commit comments

Comments
 (0)