


























@@ -2,6 +2,50 @@ import type { AgentMessage } from "@mariozechner/pi-agent-core";
22import { describe, expect, it } from "vitest";
33import { sanitizeReplayToolCallIdsForStream } from "./attempt.tool-call-normalization.js";
445+type AssistantMessage = Extract<AgentMessage, { role: "assistant" }>;
6+type ToolResultMessage = Extract<AgentMessage, { role: "toolResult" }>;
7+8+function requireAssistantMessage(message: AgentMessage | undefined): AssistantMessage {
9+if (!message || message.role !== "assistant") {
10+throw new Error(`expected assistant message, got ${message?.role ?? "missing"}`);
11+}
12+return message;
13+}
14+15+function requireToolResultMessage(message: AgentMessage | undefined): ToolResultMessage {
16+if (!message || message.role !== "toolResult") {
17+throw new Error(`expected toolResult message, got ${message?.role ?? "missing"}`);
18+}
19+return message;
20+}
21+22+function assistantToolUseSummaries(message: AgentMessage | undefined) {
23+const assistant = requireAssistantMessage(message);
24+return assistant.content.map((content) => {
25+const record = content as unknown as Record<string, unknown>;
26+if (record.type !== "toolUse") {
27+throw new Error(`expected toolUse content, got ${String(record.type)}`);
28+}
29+return {
30+type: record.type,
31+id: record.id,
32+name: record.name,
33+};
34+});
35+}
36+37+function toolResultSummary(message: AgentMessage | undefined) {
38+const toolResult = requireToolResultMessage(message);
39+const record = toolResult as unknown as Record<string, unknown>;
40+return {
41+role: toolResult.role,
42+toolCallId: toolResult.toolCallId,
43+toolUseId: record.toolUseId,
44+toolName: toolResult.toolName,
45+isError: toolResult.isError,
46+};
47+}
48+549describe("sanitizeReplayToolCallIdsForStream", () => {
650it("drops orphaned tool results after strict id sanitization", () => {
751const messages: AgentMessage[] = [
@@ -47,18 +91,17 @@ describe("sanitizeReplayToolCallIdsForStream", () => {
4791repairToolUseResultPairing: true,
4892});
499350-expect(out).toMatchObject([
51-{
52-role: "assistant",
53-content: [{ type: "toolUse", id: "callfunctionav7cbkigmk7x1", name: "read" }],
54-},
55-{
56-role: "toolResult",
57-toolCallId: "callfunctionav7cbkigmk7x1",
58-toolUseId: "callfunctionav7cbkigmk7x1",
59-toolName: "read",
60-},
94+expect(out.map((message) => message.role)).toEqual(["assistant", "toolResult"]);
95+expect(assistantToolUseSummaries(out[0])).toEqual([
96+{ type: "toolUse", id: "callfunctionav7cbkigmk7x1", name: "read" },
6197]);
98+expect(toolResultSummary(out[1])).toEqual({
99+role: "toolResult",
100+toolCallId: "callfunctionav7cbkigmk7x1",
101+toolUseId: "callfunctionav7cbkigmk7x1",
102+toolName: "read",
103+isError: false,
104+});
62105});
6310664107it("synthesizes missing tool results after strict id sanitization", () => {
@@ -86,18 +129,22 @@ describe("sanitizeReplayToolCallIdsForStream", () => {
86129});
8713088131expect(out.map((message) => message.role)).toEqual(["assistant", "toolResult", "toolResult"]);
89-expect((out[0] as Extract<AgentMessage, { role: "assistant" }>).content).toMatchObject([
132+expect(assistantToolUseSummaries(out[0])).toEqual([
90133{ type: "toolUse", id: "callfunctionav7cbkigmk7x1", name: "read" },
91134{ type: "toolUse", id: "callmissing", name: "exec" },
92135]);
93-expect(out[1]).toMatchObject({
136+expect(toolResultSummary(out[1])).toEqual({
94137role: "toolResult",
95138toolCallId: "callfunctionav7cbkigmk7x1",
96139toolUseId: "callfunctionav7cbkigmk7x1",
140+toolName: "read",
141+isError: false,
97142});
98-expect(out[2]).toMatchObject({
143+expect(toolResultSummary(out[2])).toEqual({
99144role: "toolResult",
100145toolCallId: "callmissing",
146+toolUseId: undefined,
147+toolName: "exec",
101148isError: true,
102149});
103150});
@@ -114,10 +161,14 @@ describe("sanitizeReplayToolCallIdsForStream", () => {
114161repairToolUseResultPairing: true,
115162});
116163117-expect(out).toMatchObject([
118-{ role: "assistant" },
119-{ role: "toolResult", toolCallId: "callmissing", isError: true },
120-]);
164+expect(out.map((message) => message.role)).toEqual(["assistant", "toolResult"]);
165+expect(toolResultSummary(out[1])).toEqual({
166+role: "toolResult",
167+toolCallId: "callmissing",
168+toolUseId: undefined,
169+toolName: "exec",
170+isError: true,
171+});
121172});
122173123174it("keeps real tool results for aborted assistant spans", () => {
@@ -146,21 +197,17 @@ describe("sanitizeReplayToolCallIdsForStream", () => {
146197repairToolUseResultPairing: true,
147198});
148199149-expect(out).toMatchObject([
150-{
151-role: "assistant",
152-stopReason: "aborted",
153-content: [{ type: "toolUse", id: "callfunctionav7cbkigmk7x1", name: "read" }],
154-},
155-{
156-role: "toolResult",
157-toolCallId: "callfunctionav7cbkigmk7x1",
158-toolUseId: "callfunctionav7cbkigmk7x1",
159-toolName: "read",
160-},
161-{
162-role: "user",
163-},
200+expect(out.map((message) => message.role)).toEqual(["assistant", "toolResult", "user"]);
201+expect(requireAssistantMessage(out[0]).stopReason).toBe("aborted");
202+expect(assistantToolUseSummaries(out[0])).toEqual([
203+{ type: "toolUse", id: "callfunctionav7cbkigmk7x1", name: "read" },
164204]);
205+expect(toolResultSummary(out[1])).toEqual({
206+role: "toolResult",
207+toolCallId: "callfunctionav7cbkigmk7x1",
208+toolUseId: "callfunctionav7cbkigmk7x1",
209+toolName: "read",
210+isError: false,
211+});
165212});
166213});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。