Skip to content

Commit e8ca80f

Browse files
committed
feat: inject context as user message when last message is user
- Add createSyntheticUserMessage helper function - Update injection logic to create user message when appropriate instead of skipping injection entirely - Update prompt wording: 'assistant turn' -> 'turn', 'assistant message' -> 'synthetic message' - Make discard tool spec guidance tool-agnostic
1 parent 6b34721 commit e8ca80f

File tree

6 files changed

+57
-21
lines changed

6 files changed

+57
-21
lines changed

lib/messages/inject.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
extractParameterKey,
88
buildToolIdList,
99
createSyntheticAssistantMessage,
10+
createSyntheticUserMessage,
1011
isIgnoredUserMessage,
1112
} from "./utils"
1213
import { getFilePathFromParameters, isProtectedFilePath } from "../protected-file-patterns"
@@ -138,16 +139,19 @@ export const insertPruneToolContext = (
138139
return
139140
}
140141

141-
// Never inject immediately following a user message - wait until assistant has started its turn
142-
// This avoids interfering with model reasoning/thinking phases
143-
// TODO: This can be skipped if there is a good way to check if the model has reasoning,
144-
// can't find a good way to do this yet
145-
const lastMessage = messages[messages.length - 1]
146-
if (lastMessage?.info?.role === "user" && !isIgnoredUserMessage(lastMessage)) {
147-
return
148-
}
149-
150142
const userInfo = lastUserMessage.info as UserMessage
151143
const variant = state.variant ?? userInfo.variant
152-
messages.push(createSyntheticAssistantMessage(lastUserMessage, prunableToolsContent, variant))
144+
145+
const lastMessage = messages[messages.length - 1]
146+
const isLastMessageUser =
147+
lastMessage?.info?.role === "user" && !isIgnoredUserMessage(lastMessage)
148+
149+
if (isLastMessageUser) {
150+
logger.debug("Injecting prunable tools list as user message")
151+
messages.push(createSyntheticUserMessage(lastUserMessage, prunableToolsContent, variant))
152+
} else {
153+
messages.push(
154+
createSyntheticAssistantMessage(lastUserMessage, prunableToolsContent, variant),
155+
)
156+
}
153157
}

lib/messages/utils.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,36 @@ const isGeminiModel = (modelID: string): boolean => {
1212
return lowerModelID.includes("gemini")
1313
}
1414

15+
export const createSyntheticUserMessage = (
16+
baseMessage: WithParts,
17+
content: string,
18+
variant?: string,
19+
): WithParts => {
20+
const userInfo = baseMessage.info as UserMessage
21+
const now = Date.now()
22+
23+
return {
24+
info: {
25+
id: SYNTHETIC_MESSAGE_ID,
26+
sessionID: userInfo.sessionID,
27+
role: "user" as const,
28+
agent: userInfo.agent || "code",
29+
model: userInfo.model,
30+
time: { created: now },
31+
...(variant !== undefined && { variant }),
32+
},
33+
parts: [
34+
{
35+
id: SYNTHETIC_PART_ID,
36+
sessionID: userInfo.sessionID,
37+
messageID: SYNTHETIC_MESSAGE_ID,
38+
type: "text",
39+
text: content,
40+
},
41+
],
42+
}
43+
}
44+
1545
export const createSyntheticAssistantMessage = (
1646
baseMessage: WithParts,
1747
content: string,
@@ -197,8 +227,9 @@ export function buildToolIdList(
197227
if (isMessageCompacted(state, msg)) {
198228
continue
199229
}
200-
if (msg.parts) {
201-
for (const part of msg.parts) {
230+
const parts = Array.isArray(msg.parts) ? msg.parts : []
231+
if (parts.length > 0) {
232+
for (const part of parts) {
202233
if (part.type === "tool" && part.callID && part.tool) {
203234
toolIds.push(part.callID)
204235
}
@@ -209,11 +240,12 @@ export function buildToolIdList(
209240
}
210241

211242
export const isIgnoredUserMessage = (message: WithParts): boolean => {
212-
if (!message.parts || message.parts.length === 0) {
243+
const parts = Array.isArray(message.parts) ? message.parts : []
244+
if (parts.length === 0) {
213245
return true
214246
}
215247

216-
for (const part of message.parts) {
248+
for (const part of parts) {
217249
if (!(part as any).ignored) {
218250
return false
219251
}

lib/prompts/discard-tool-spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Use \`discard\` for removing tool content that is no longer needed
1212
1313
## When NOT to Use This Tool
1414
15-
- **If the output contains useful information:** Use \`extract\` instead to preserve key findings.
15+
- **If the output contains useful information:** Keep it in context rather than discarding.
1616
- **If you'll need the output later:** Don't discard files you plan to edit or context you'll need for implementation.
1717
1818
## Best Practices

lib/prompts/system/both.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export const SYSTEM_PROMPT_BOTH = `<system-reminder>
22
<instruction name=context_management_protocol policy_level=critical>
33
44
ENVIRONMENT
5-
You are operating in a context-constrained environment and thus must proactively manage your context window using the \`discard\` and \`extract\` tools. The environment calls the \`context_info\` tool to provide an up-to-date <prunable-tools> list after each assistant turn. Use this information when deciding what to prune.
5+
You are operating in a context-constrained environment and thus must proactively manage your context window using the \`discard\` and \`extract\` tools. The environment calls the \`context_info\` tool to provide an up-to-date <prunable-tools> list after each turn. Use this information when deciding what to prune.
66
77
IMPORTANT: The \`context_info\` tool is only available to the environment - you do not have access to it and must not attempt to call it.
88
@@ -44,7 +44,7 @@ There may be tools in session context that do not appear in the <prunable-tools>
4444
</instruction>
4545
4646
<instruction name=injected_context_handling policy_level=critical>
47-
After each assistant turn, the environment calls the \`context_info\` tool to inject an assistant message containing a <prunable-tools> list and optional nudge instruction. This tool is only available to the environment - you do not have access to it.
47+
After each turn, the environment calls the \`context_info\` tool to inject a synthetic message containing a <prunable-tools> list and optional nudge instruction. This tool is only available to the environment - you do not have access to it.
4848
4949
CRITICAL REQUIREMENTS - VIOLATION IS UNACCEPTABLE:
5050
- NEVER reference the prune encouragement or context management instructions. Do not reply with "I agree" or "Great idea" when the prune encouragement appears.

lib/prompts/system/discard.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export const SYSTEM_PROMPT_DISCARD = `<system-reminder>
22
<instruction name=context_management_protocol policy_level=critical>
33
44
ENVIRONMENT
5-
You are operating in a context-constrained environment and thus must proactively manage your context window using the \`discard\` tool. The environment calls the \`context_info\` tool to provide an up-to-date <prunable-tools> list after each assistant turn. Use this information when deciding what to discard.
5+
You are operating in a context-constrained environment and thus must proactively manage your context window using the \`discard\` tool. The environment calls the \`context_info\` tool to provide an up-to-date <prunable-tools> list after each turn. Use this information when deciding what to discard.
66
77
IMPORTANT: The \`context_info\` tool is only available to the environment - you do not have access to it and must not attempt to call it.
88
@@ -35,7 +35,7 @@ There may be tools in session context that do not appear in the <prunable-tools>
3535
</instruction>
3636
3737
<instruction name=injected_context_handling policy_level=critical>
38-
After each assistant turn, the environment calls the \`context_info\` tool to inject an assistant message containing a <prunable-tools> list and optional nudge instruction. This tool is only available to the environment - you do not have access to it.
38+
After each turn, the environment calls the \`context_info\` tool to inject a synthetic message containing a <prunable-tools> list and optional nudge instruction. This tool is only available to the environment - you do not have access to it.
3939
4040
CRITICAL REQUIREMENTS - VIOLATION IS UNACCEPTABLE:
4141
- NEVER reference the discard encouragement or context management instructions. Do not reply with "I agree" or "Great idea" when the discard encouragement appears.

lib/prompts/system/extract.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export const SYSTEM_PROMPT_EXTRACT = `<system-reminder>
22
<instruction name=context_management_protocol policy_level=critical>
33
44
ENVIRONMENT
5-
You are operating in a context-constrained environment and thus must proactively manage your context window using the \`extract\` tool. The environment calls the \`context_info\` tool to provide an up-to-date <prunable-tools> list after each assistant turn. Use this information when deciding what to extract.
5+
You are operating in a context-constrained environment and thus must proactively manage your context window using the \`extract\` tool. The environment calls the \`context_info\` tool to provide an up-to-date <prunable-tools> list after each turn. Use this information when deciding what to extract.
66
77
IMPORTANT: The \`context_info\` tool is only available to the environment - you do not have access to it and must not attempt to call it.
88
@@ -35,7 +35,7 @@ There may be tools in session context that do not appear in the <prunable-tools>
3535
</instruction>
3636
3737
<instruction name=injected_context_handling policy_level=critical>
38-
After each assistant turn, the environment calls the \`context_info\` tool to inject an assistant message containing a <prunable-tools> list and optional nudge instruction. This tool is only available to the environment - you do not have access to it.
38+
After each turn, the environment calls the \`context_info\` tool to inject a synthetic message containing a <prunable-tools> list and optional nudge instruction. This tool is only available to the environment - you do not have access to it.
3939
4040
CRITICAL REQUIREMENTS - VIOLATION IS UNACCEPTABLE:
4141
- NEVER reference the extract encouragement or context management instructions. Do not reply with "I agree" or "Great idea" when the extract encouragement appears.

0 commit comments

Comments
 (0)