Skip to content

Commit 0bd9771

Browse files
committed
refactor: inject assistant message IDs into last tool output
1 parent 27ed0b1 commit 0bd9771

2 files changed

Lines changed: 41 additions & 16 deletions

File tree

lib/message-ids.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { SessionState, WithParts } from "./state"
22

33
const MESSAGE_REF_REGEX = /^m(\d{4})$/
44
const BLOCK_REF_REGEX = /^b([1-9]\d*)$/
5+
const MESSAGE_ID_TAG_NAME = "dcp-message-id"
56

67
const MESSAGE_REF_WIDTH = 4
78
const MESSAGE_REF_MIN_INDEX = 0
@@ -86,6 +87,10 @@ export function formatMessageIdMarker(ref: string): string {
8687
return `Message ID: ${ref}`
8788
}
8889

90+
export function formatMessageIdTag(ref: string): string {
91+
return `<${MESSAGE_ID_TAG_NAME}>${ref}</${MESSAGE_ID_TAG_NAME}>`
92+
}
93+
8994
export function assignMessageRefs(state: SessionState, messages: WithParts[]): number {
9095
let assigned = 0
9196

lib/messages/inject/inject.ts

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { SessionState, WithParts } from "../../state"
22
import type { Logger } from "../../logger"
33
import type { PluginConfig } from "../../config"
4-
import { formatMessageIdMarker } from "../../message-ids"
4+
import { formatMessageIdMarker, formatMessageIdTag } from "../../message-ids"
55
import { createSyntheticTextPart, createSyntheticToolPart, isIgnoredUserMessage } from "../utils"
66
import {
77
addAnchor,
@@ -16,6 +16,35 @@ import {
1616
import { renderNudge } from "../../prompts"
1717

1818
const CONTEXT_LIMIT_HINT_TEXT = renderNudge("context-limit")
19+
type MessagePart = WithParts["parts"][number]
20+
type ToolPart = Extract<MessagePart, { type: "tool" }>
21+
22+
const appendMessageIdTagToToolOutput = (part: ToolPart, tag: string): boolean => {
23+
if (part.type !== "tool") {
24+
return false
25+
}
26+
if (part.state?.status !== "completed" || typeof part.state.output !== "string") {
27+
return false
28+
}
29+
if (part.state.output.includes(tag)) {
30+
return true
31+
}
32+
33+
const separator = part.state.output.length > 0 && !part.state.output.endsWith("\n") ? "\n" : ""
34+
part.state.output = `${part.state.output}${separator}${tag}`
35+
return true
36+
}
37+
38+
const findLastToolPart = (message: WithParts): ToolPart | null => {
39+
for (let i = message.parts.length - 1; i >= 0; i--) {
40+
const part = message.parts[i]
41+
if (part.type === "tool") {
42+
return part
43+
}
44+
}
45+
46+
return null
47+
}
1948

2049
export const insertCompressToolContext = (
2150
state: SessionState,
@@ -74,6 +103,7 @@ export const insertMessageIdContext = (state: SessionState, messages: WithParts[
74103
}
75104

76105
const marker = formatMessageIdMarker(messageRef)
106+
const tag = formatMessageIdTag(messageRef)
77107

78108
if (message.info.role === "user") {
79109
const hasMarker = message.parts.some(
@@ -89,21 +119,11 @@ export const insertMessageIdContext = (state: SessionState, messages: WithParts[
89119
continue
90120
}
91121

92-
const hasMarker = message.parts.some((part) => {
93-
if (part.type !== "tool") {
94-
return false
95-
}
96-
if (part.tool !== "context_info") {
97-
return false
98-
}
99-
return (
100-
part.state?.status === "completed" &&
101-
typeof part.state.output === "string" &&
102-
part.state.output.trim() === marker
103-
)
104-
})
105-
if (!hasMarker) {
106-
message.parts.push(createSyntheticToolPart(message, marker, toolModelId))
122+
const lastToolPart = findLastToolPart(message)
123+
if (lastToolPart && appendMessageIdTagToToolOutput(lastToolPart, tag)) {
124+
continue
107125
}
126+
127+
message.parts.push(createSyntheticToolPart(message, marker, toolModelId))
108128
}
109129
}

0 commit comments

Comments
 (0)