Skip to content

Commit 0337238

Browse files
committed
fix: skip message ID injection on empty assistant messages (#463)
1 parent bf576d2 commit 0337238

2 files changed

Lines changed: 121 additions & 10 deletions

File tree

lib/messages/inject/inject.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,18 @@ export const injectMessageIds = (
186186
continue
187187
}
188188

189+
const hasContent = message.parts.some(
190+
(p) =>
191+
(p.type === "text" && typeof p.text === "string" && p.text.trim().length > 0) ||
192+
(p.type === "tool" &&
193+
p.state?.status === "completed" &&
194+
typeof p.state.output === "string"),
195+
)
196+
197+
if (!hasContent) {
198+
continue
199+
}
200+
189201
let injected = false
190202
for (const part of message.parts) {
191203
if (part.type === "text") {
@@ -195,16 +207,14 @@ export const injectMessageIds = (
195207
}
196208
}
197209

198-
if (injected) {
199-
continue
200-
}
201-
202-
const syntheticPart = createSyntheticTextPart(message, tag)
203-
const firstToolIndex = message.parts.findIndex((p) => p.type === "tool")
204-
if (firstToolIndex === -1) {
205-
message.parts.push(syntheticPart)
206-
} else {
207-
message.parts.splice(firstToolIndex, 0, syntheticPart)
210+
if (!injected) {
211+
const syntheticPart = createSyntheticTextPart(message, tag)
212+
const firstToolIndex = message.parts.findIndex((p) => p.type === "tool")
213+
if (firstToolIndex === -1) {
214+
message.parts.push(syntheticPart)
215+
} else {
216+
message.parts.splice(firstToolIndex, 0, syntheticPart)
217+
}
208218
}
209219
}
210220
}

tests/message-priority.test.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,3 +670,104 @@ test("hallucination stripping does not affect non-dcp tags", async () => {
670670
"<div>hello</div> <system-reminder>keep</system-reminder>",
671671
)
672672
})
673+
674+
test("injectMessageIds skips empty assistant messages to avoid prefill (issue #463)", () => {
675+
const sessionID = "ses_empty_assistant"
676+
const messages: WithParts[] = [
677+
buildMessage("msg-user-1", "user", sessionID, "Hello", 1),
678+
{
679+
info: {
680+
id: "msg-assistant-empty",
681+
role: "assistant",
682+
sessionID,
683+
agent: "assistant",
684+
time: { created: 2 },
685+
} as WithParts["info"],
686+
parts: [],
687+
},
688+
buildMessage("msg-user-2", "user", sessionID, "continue", 3),
689+
]
690+
const state = createSessionState()
691+
const config = buildConfig("range")
692+
693+
assignMessageRefs(state, messages)
694+
injectMessageIds(state, config, messages)
695+
696+
const emptyAssistant = messages[1]!
697+
assert.equal(emptyAssistant.parts.length, 0, "empty assistant should get no synthetic parts")
698+
})
699+
700+
test("injectMessageIds skips assistant with only pending tool parts (issue #463)", () => {
701+
const sessionID = "ses_pending_tool_assistant"
702+
const messages: WithParts[] = [
703+
buildMessage("msg-user-1", "user", sessionID, "Hello", 1),
704+
{
705+
info: {
706+
id: "msg-assistant-pending",
707+
role: "assistant",
708+
sessionID,
709+
agent: "assistant",
710+
time: { created: 2 },
711+
} as WithParts["info"],
712+
parts: [
713+
{
714+
id: "pending-tool-part",
715+
messageID: "msg-assistant-pending",
716+
sessionID,
717+
type: "tool" as const,
718+
tool: "bash",
719+
callID: "call-pending-1",
720+
state: {
721+
status: "pending" as const,
722+
input: { command: "ls" },
723+
},
724+
} as any,
725+
],
726+
},
727+
buildMessage("msg-user-2", "user", sessionID, "continue", 3),
728+
]
729+
const state = createSessionState()
730+
const config = buildConfig("range")
731+
732+
assignMessageRefs(state, messages)
733+
injectMessageIds(state, config, messages)
734+
735+
const pendingAssistant = messages[1]!
736+
assert.equal(
737+
pendingAssistant.parts.length,
738+
1,
739+
"assistant with only pending tools should not get a synthetic text part",
740+
)
741+
assert.equal(pendingAssistant.parts[0]!.type, "tool")
742+
})
743+
744+
test("injectMessageIds skips assistant with empty text part (issue #463)", () => {
745+
const sessionID = "ses_empty_text_assistant"
746+
const messages: WithParts[] = [
747+
buildMessage("msg-user-1", "user", sessionID, "Hello", 1),
748+
{
749+
info: {
750+
id: "msg-assistant-empty-text",
751+
role: "assistant",
752+
sessionID,
753+
agent: "assistant",
754+
time: { created: 2 },
755+
} as WithParts["info"],
756+
parts: [textPart("msg-assistant-empty-text", sessionID, "empty-text-part", "")],
757+
},
758+
buildMessage("msg-user-2", "user", sessionID, "continue", 3),
759+
]
760+
const state = createSessionState()
761+
const config = buildConfig("range")
762+
763+
assignMessageRefs(state, messages)
764+
injectMessageIds(state, config, messages)
765+
766+
const emptyTextAssistant = messages[1]!
767+
assert.equal(emptyTextAssistant.parts.length, 1, "should not add a synthetic part")
768+
assert.equal(
769+
(emptyTextAssistant.parts[0] as any).text,
770+
"",
771+
"empty text part should remain untouched",
772+
)
773+
})

0 commit comments

Comments
 (0)