Compare commits

...
Author SHA1 Message Date
paseo-bot[bot]andYanlin 3d9dcffc6d fix(plugin): keep ACP reasoning separate from replies
Scope explicit timeline identities by chunk kind so a provider sharing a native message ID cannot merge reasoning into the assistant reply. Preserve the native assistant messageId for consumers.

Co-authored-by: Yanlin <127009+cncolder@users.noreply.github.com>
2026-10-18 05:38:18 +02:00
2 changed files with 32 additions and 3 deletions
@@ -951,7 +951,9 @@ class AcpRuntime {
): string {
if (messageId) {
this.fallbackChunkIds.delete(kind);
return messageId;
// Native IDs identify a message within its chunk kind. Timeline IDs must
// also distinguish reasoning from replies and from generated fallback IDs.
return JSON.stringify([kind, messageId]);
}
let id = this.fallbackChunkIds.get(kind);
if (!id) {
+29 -2
View File
@@ -1307,7 +1307,15 @@ describe("runAcpProvider streamed chunks without messageId (#4699)", () => {
const items = events.flatMap((event) =>
event.type === "timeline.item" &&
(event.item.type === "assistant_message" || event.item.type === "reasoning")
? [{ id: event.item.id, text: event.item.text }]
? [
{
id: event.item.id,
text: event.item.text,
...(event.item.type === "assistant_message" && event.item.messageId
? { messageId: event.item.messageId }
: {}),
},
]
: [],
);
expect(items).toEqual([
@@ -1315,7 +1323,11 @@ describe("runAcpProvider streamed chunks without messageId (#4699)", () => {
{ id: "agent_message_chunk:1", text: "- **Current temperature**: 25°C" },
{ id: "agent_message_chunk:2", text: "next turn" },
{ id: "agent_thought_chunk:3", text: "think A" },
{ id: "m-explicit", text: "say" },
{
id: JSON.stringify(["agent_message_chunk", "m-explicit"]),
messageId: "m-explicit",
text: "say",
},
{ id: "agent_thought_chunk:4", text: "think B" },
]);
await connection.close();
@@ -1402,6 +1414,21 @@ async function streamChunkTurns(
}
describe("ACP streamed message boundaries", () => {
it("keeps reasoning separate from a reply sharing its native message ID", async () => {
expect(
await streamChunkTurns([
textChunk("private ", "agent_thought_chunk", "shared-id"),
textChunk("reasoning", "agent_thought_chunk", "shared-id"),
textChunk("public ", "agent_message_chunk", "shared-id"),
textChunk("reply", "agent_message_chunk", "shared-id"),
]),
).toEqual([
{ type: "reasoning", text: "private reasoning" },
{ type: "assistant_message", text: "public reply" },
{ type: "assistant_message", text: "Next turn" },
]);
});
it.each([
{
name: "tool call",