Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 4 additions & 28 deletions lib/fetch-wrapper/formats/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,34 +33,10 @@ export const anthropicFormat: FormatDescriptor = {
return true
},

appendToLastAssistantMessage(body: any, injection: string): boolean {
if (!injection || !body.messages || body.messages.length === 0) return false

// Find the last assistant message
for (let i = body.messages.length - 1; i >= 0; i--) {
const msg = body.messages[i]
if (msg.role === 'assistant') {
// Append to existing content array
if (Array.isArray(msg.content)) {
const firstToolUseIndex = msg.content.findIndex((block: any) => block.type === 'tool_use')
if (firstToolUseIndex !== -1) {
msg.content.splice(firstToolUseIndex, 0, { type: 'text', text: injection })
} else {
msg.content.push({ type: 'text', text: injection })
}
} else if (typeof msg.content === 'string') {
// Convert string content to array format
msg.content = [
{ type: 'text', text: msg.content },
{ type: 'text', text: injection }
]
} else {
msg.content = [{ type: 'text', text: injection }]
}
return true
}
}
return false
appendUserMessage(body: any, injection: string): boolean {
if (!injection || !body.messages) return false
body.messages.push({ role: 'user', content: [{ type: 'text', text: injection }] })
return true
},

extractToolOutputs(data: any[], state: PluginState): ToolOutput[] {
Expand Down
19 changes: 4 additions & 15 deletions lib/fetch-wrapper/formats/bedrock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,10 @@ export const bedrockFormat: FormatDescriptor = {
return true
},

appendToLastAssistantMessage(body: any, injection: string): boolean {
if (!injection || !body.messages || body.messages.length === 0) return false

for (let i = body.messages.length - 1; i >= 0; i--) {
const msg = body.messages[i]
if (msg.role === 'assistant') {
if (Array.isArray(msg.content)) {
msg.content.push({ text: injection })
} else {
msg.content = [{ text: injection }]
}
return true
}
}
return false
appendUserMessage(body: any, injection: string): boolean {
if (!injection || !body.messages) return false
body.messages.push({ role: 'user', content: [{ text: injection }] })
return true
},

extractToolOutputs(data: any[], state: PluginState): ToolOutput[] {
Expand Down
19 changes: 4 additions & 15 deletions lib/fetch-wrapper/formats/gemini.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,10 @@ export const geminiFormat: FormatDescriptor = {
return true
},

appendToLastAssistantMessage(body: any, injection: string): boolean {
if (!injection || !body.contents || body.contents.length === 0) return false

for (let i = body.contents.length - 1; i >= 0; i--) {
const content = body.contents[i]
if (content.role === 'model') {
if (Array.isArray(content.parts)) {
content.parts.push({ text: injection })
} else {
content.parts = [{ text: injection }]
}
return true
}
}
return false
appendUserMessage(body: any, injection: string): boolean {
if (!injection || !body.contents) return false
body.contents.push({ role: 'user', parts: [{ text: injection }] })
return true
},

extractToolOutputs(data: any[], state: PluginState): ToolOutput[] {
Expand Down
21 changes: 4 additions & 17 deletions lib/fetch-wrapper/formats/openai-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,10 @@ export const openaiChatFormat: FormatDescriptor = {
return true
},

appendToLastAssistantMessage(body: any, injection: string): boolean {
if (!injection || !body.messages || body.messages.length === 0) return false

for (let i = body.messages.length - 1; i >= 0; i--) {
const msg = body.messages[i]
if (msg.role === 'assistant') {
if (typeof msg.content === 'string') {
msg.content = msg.content + '\n\n' + injection
} else if (Array.isArray(msg.content)) {
msg.content.push({ type: 'text', text: injection })
} else {
msg.content = injection
}
return true
}
}
return false
appendUserMessage(body: any, injection: string): boolean {
if (!injection || !body.messages) return false
body.messages.push({ role: 'user', content: injection })
return true
},

extractToolOutputs(data: any[], state: PluginState): ToolOutput[] {
Expand Down
21 changes: 4 additions & 17 deletions lib/fetch-wrapper/formats/openai-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,10 @@ export const openaiResponsesFormat: FormatDescriptor = {
return true
},

appendToLastAssistantMessage(body: any, injection: string): boolean {
if (!injection || !body.input || body.input.length === 0) return false

for (let i = body.input.length - 1; i >= 0; i--) {
const item = body.input[i]
if (item.type === 'message' && item.role === 'assistant') {
if (typeof item.content === 'string') {
item.content = item.content + '\n\n' + injection
} else if (Array.isArray(item.content)) {
item.content.push({ type: 'output_text', text: injection })
} else {
item.content = injection
}
return true
}
}
return false
appendUserMessage(body: any, injection: string): boolean {
if (!injection || !body.input) return false
body.input.push({ type: 'message', role: 'user', content: injection })
return true
},

extractToolOutputs(data: any[], state: PluginState): ToolOutput[] {
Expand Down
8 changes: 4 additions & 4 deletions lib/fetch-wrapper/handler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { FetchHandlerContext, FetchHandlerResult, FormatDescriptor, PrunedIdData } from "./types"
import { type PluginState, ensureSessionRestored } from "../state"
import type { Logger } from "../logger"
import { buildPrunableToolsList, buildAssistantInjection } from "./prunable-list"
import { buildPrunableToolsList, buildEndInjection } from "./prunable-list"
import { syncToolCache } from "../state/tool-cache"
import { loadPrompt } from "../core/prompt"

Expand Down Expand Up @@ -96,11 +96,11 @@ export async function handleFormat(
modified = true
}

const assistantInjection = buildAssistantInjection(prunableList, includeNudge)
const endInjection = buildEndInjection(prunableList, includeNudge)

if (format.appendToLastAssistantMessage && format.appendToLastAssistantMessage(body, assistantInjection)) {
if (format.appendUserMessage && format.appendUserMessage(body, endInjection)) {
const nudgeMsg = includeNudge ? " with nudge" : ""
ctx.logger.debug("fetch", `Appended prunable tools list${nudgeMsg} to last assistant message (${format.name})`, {
ctx.logger.debug("fetch", `Appended prunable tools list${nudgeMsg} as user message (${format.name})`, {
ids: numericIds,
nudge: includeNudge,
toolsSincePrune: ctx.toolTracker.toolResultCount
Expand Down
2 changes: 1 addition & 1 deletion lib/fetch-wrapper/prunable-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function buildPrunableToolsList(
}
}

export function buildAssistantInjection(
export function buildEndInjection(
prunableList: string,
includeNudge: boolean
): string {
Expand Down
2 changes: 1 addition & 1 deletion lib/fetch-wrapper/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface FormatDescriptor {
detect(body: any): boolean
getDataArray(body: any): any[] | undefined
injectSystemMessage(body: any, injection: string): boolean
appendToLastAssistantMessage?(body: any, injection: string): boolean
appendUserMessage?(body: any, injection: string): boolean
extractToolOutputs(data: any[], state: PluginState): ToolOutput[]
replaceToolOutput(data: any[], toolId: string, prunedMessage: string, state: PluginState): boolean
hasToolOutputs(data: any[]): boolean
Expand Down
16 changes: 15 additions & 1 deletion lib/prompts/synthetic.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<instruction name=context_management_protocol policy_level=critical>

ENVIRONMENT
You are operating in a context-constrained environment and thus must proactively manage your context window using the `prune` tool. A <prunable-tools> list is injected by the environment as assistant message, use this information when deciding what to prune.
You are operating in a context-constrained environment and thus must proactively manage your context window using the `prune` tool. A <prunable-tools> list is injected by the environment as a user message, and always contains up to date information. Use this information when deciding what to prune.

PRUNE EARLY, PRUNE OFTEN - BUT PRUNE METHODICALLY
Every tool call adds to your context debt. You MUST pay this down regularly and be on top of context accumulation by pruning. Evaluate what SHOULD be pruned before jumping the gun.
Expand All @@ -22,5 +22,19 @@ NOTES
When in doubt, prune out. Prune often yet remain strategic about it.
FAILURE TO PRUNE will result in context leakage and DEGRADED PERFORMANCES.

</instruction>

<instruction name=injected_context_handling policy_level=critical>
After each assistant turn, the environment may inject a user message containing a <prunable-tools> list and optional nudge instruction. This injected message is NOT from the user and is invisible to them.

CRITICAL REQUIREMENTS - VIOLATION IS UNACCEPTABLE:
- NEVER acknowledge the <prunable-tools> list (e.g. "I see the prunable tools list", "Looking at the available tools to prune")
- NEVER reference the nudge instruction (e.g. "As the nudge suggests", "The system is reminding me to prune")
- NEVER mention this injection mechanism to the user
- NEVER let these injections influence your response content or tone
- Process this information SILENTLY - use it only to inform your pruning decisions
- Continue the conversation as if these messages do not exist

The user cannot see these injections. Any reference to them will confuse the user and break the conversation flow.
</instruction>
</system-reminder>
2 changes: 1 addition & 1 deletion lib/prompts/tool.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Prunes tool outputs from context to manage conversation size and reduce noise.

## IMPORTANT: The Prunable List
A `<prunable-tools>` list is injected into assistant messages showing available tool outputs you can prune. Each line has the format `ID: tool, parameter` (e.g., `20: read, /path/to/file.ts`). Use these numeric IDs to select which tools to prune.
A `<prunable-tools>` list is injected into user messages showing available tool outputs you can prune. Each line has the format `ID: tool, parameter` (e.g., `20: read, /path/to/file.ts`). Use these numeric IDs to select which tools to prune.

## CRITICAL: When and How to Prune

Expand Down