Skip to content

Commit 17bf1f1

Browse files
feat: integrate context annotation into hook and compress tool
1 parent 26dbfc2 commit 17bf1f1

3 files changed

Lines changed: 40 additions & 37 deletions

File tree

lib/hooks.ts

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { PluginConfig } from "./config"
44
import { syncToolCache } from "./state/tool-cache"
55
import { deduplicate, supersedeWrites, purgeErrors } from "./strategies"
66
import { prune, insertCompressToolContext } from "./messages"
7-
import { buildToolIdList, isIgnoredUserMessage } from "./messages/utils"
7+
import { buildToolIdList, isIgnoredUserMessage, annotateContext } from "./messages/utils"
88
import { checkSession } from "./state"
99
import { renderSystemPrompt } from "./prompts"
1010
import { handleStatsCommand } from "./commands/stats"
@@ -13,49 +13,14 @@ import { handleHelpCommand } from "./commands/help"
1313
import { handleSweepCommand } from "./commands/sweep"
1414
import { handleManualToggleCommand, handleManualTriggerCommand } from "./commands/manual"
1515
import { ensureSessionInitialized } from "./state/state"
16-
import { getCurrentParams, countToolTokens } from "./strategies/utils"
16+
import { getCurrentParams } from "./strategies/utils"
1717

1818
const INTERNAL_AGENT_SIGNATURES = [
1919
"You are a title generator",
2020
"You are a helpful AI assistant tasked with summarizing conversations",
2121
"Summarize what was done in this conversation",
2222
]
2323

24-
function formatTokenCount(n: number): string {
25-
if (n >= 1000) return `${(n / 1000).toFixed(1)}k tokens`
26-
return `${n} tokens`
27-
}
28-
29-
function annotateContext(messages: WithParts[]): void {
30-
let uid = 0
31-
let muid = 0
32-
for (const msg of messages) {
33-
const parts = Array.isArray(msg.parts) ? msg.parts : []
34-
35-
if (msg.info.role === "user" && !isIgnoredUserMessage(msg)) {
36-
muid++
37-
const text = parts.find(
38-
(p) => p.type === "text" && !(p as any).ignored && !(p as any).synthetic,
39-
)
40-
if (text && text.type === "text") {
41-
text.text = `[muid_${muid}]\n${text.text}`
42-
}
43-
}
44-
45-
for (const part of parts) {
46-
if (part.type !== "tool") continue
47-
uid++
48-
const tokens = countToolTokens(part)
49-
const tag = tokens > 0 ? `[uid_${uid}, ${formatTokenCount(tokens)}]` : `[uid_${uid}]`
50-
if (part.state?.status === "completed" && typeof part.state.output === "string") {
51-
part.state.output = `${tag}\n${part.state.output}`
52-
} else if (part.state?.status === "error" && typeof part.state.error === "string") {
53-
part.state.error = `${tag}\n${part.state.error}`
54-
}
55-
}
56-
}
57-
}
58-
5924
function applyPendingManualTriggerPrompt(
6025
state: SessionState,
6126
messages: WithParts[],

lib/messages/utils.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { isMessageCompacted } from "../shared-utils"
33
import { Logger } from "../logger"
44
import type { SessionState, WithParts } from "../state"
55
import type { UserMessage } from "@opencode-ai/sdk/v2"
6+
import { countToolTokens } from "../strategies/utils"
67

78
export const COMPRESS_SUMMARY_PREFIX = "[Compressed conversation block]\n\n"
89

@@ -267,3 +268,38 @@ export const isIgnoredUserMessage = (message: WithParts): boolean => {
267268
export const findMessageIndex = (messages: WithParts[], messageId: string): number => {
268269
return messages.findIndex((msg) => msg.info.id === messageId)
269270
}
271+
272+
function formatTokenCount(n: number): string {
273+
if (n >= 1000) return `${(n / 1000).toFixed(1)}k tokens`
274+
return `${n} tokens`
275+
}
276+
277+
export function annotateContext(messages: WithParts[]): void {
278+
let uid = 0
279+
let muid = 0
280+
for (const msg of messages) {
281+
const parts = Array.isArray(msg.parts) ? msg.parts : []
282+
283+
if (msg.info.role === "user" && !isIgnoredUserMessage(msg)) {
284+
muid++
285+
const text = parts.find(
286+
(p) => p.type === "text" && !(p as any).ignored && !(p as any).synthetic,
287+
)
288+
if (text && text.type === "text") {
289+
text.text = `[muid_${muid}]\n${text.text}`
290+
}
291+
}
292+
293+
for (const part of parts) {
294+
if (part.type !== "tool") continue
295+
uid++
296+
const tokens = countToolTokens(part)
297+
const tag = tokens > 0 ? `[uid_${uid}, ${formatTokenCount(tokens)}]` : `[uid_${uid}]`
298+
if (part.state?.status === "completed" && typeof part.state.output === "string") {
299+
part.state.output = `${tag}\n${part.state.output}`
300+
} else if (part.state?.status === "error" && typeof part.state.error === "string") {
301+
part.state.error = `${tag}\n${part.state.error}`
302+
}
303+
}
304+
}
305+
}

lib/tools/compress.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { saveSessionState } from "../state/persistence"
66
import { loadPrompt } from "../prompts"
77
import { getCurrentParams, getTotalToolTokens, countMessageTextTokens } from "../strategies/utils"
88
import { findStringInMessages, collectToolIdsInRange, collectMessageIdsInRange } from "./utils"
9+
import { annotateContext } from "../messages/utils"
910
import { sendCompressNotification } from "../ui/notification"
1011
import { prune as applyPruneTransforms } from "../messages/prune"
1112

@@ -82,6 +83,7 @@ export function createCompressTool(ctx: ToolContext): ReturnType<typeof tool> {
8283

8384
const transformedMessages = structuredClone(messages) as WithParts[]
8485
applyPruneTransforms(state, logger, ctx.config, transformedMessages)
86+
annotateContext(transformedMessages)
8587

8688
const startResult = findStringInMessages(
8789
transformedMessages,

0 commit comments

Comments
 (0)