feat(agent): omit resume documents from the copied conversation json

This commit is contained in:
Amruth Pillai
2026-08-20 09:40:57 +02:00
parent 39f36b4ac5
commit 8ce899a04b
2 changed files with 70 additions and 2 deletions
@@ -5,7 +5,7 @@ import { fireEvent, render, screen, within } from "@testing-library/react";
import { beforeAll, describe, expect, it, vi } from "vitest";
import { i18n } from "@lingui/core";
import { I18nProvider } from "@lingui/react";
import { AskUserQuestion, AssistantMarkdown } from "./agent-chat";
import { AskUserQuestion, AssistantMarkdown, withoutResumeDataForExport } from "./agent-chat";
describe("AssistantMarkdown", () => {
it("renders GitHub-style pipe tables as tables", () => {
@@ -27,6 +27,47 @@ describe("AssistantMarkdown", () => {
});
});
describe("withoutResumeDataForExport", () => {
it("strips the resume document from tool outputs but keeps everything else", () => {
const messages = [
{
id: "m1",
role: "assistant",
parts: [
{
type: "tool-read_resume",
toolCallId: "c1",
state: "output-available",
input: {},
output: { id: "r1", updatedAt: "2026-08-20T00:00:00.000Z", data: { basics: { name: "John" } } },
},
{
type: "tool-apply_resume_patch",
toolCallId: "c2",
state: "output-available",
input: { title: "Edit" },
output: { actionId: "a1", changedPaths: ["/basics/name"], resume: { basics: { name: "John" } } },
},
{ type: "text", text: "Done." },
],
},
] as unknown as UIMessage[];
const exported = withoutResumeDataForExport(messages);
const exportedParts = (exported[0]?.parts ?? []) as Array<{ output?: Record<string, unknown> }>;
expect(exportedParts[0]?.output?.data).toBe("[resume data omitted]");
expect(exportedParts[0]?.output?.updatedAt).toBe("2026-08-20T00:00:00.000Z");
expect(exportedParts[1]?.output?.resume).toBe("[resume data omitted]");
expect(exportedParts[1]?.output?.changedPaths).toEqual(["/basics/name"]);
expect(exported[0]?.parts[2]).toEqual({ type: "text", text: "Done." });
// The live chat state is never mutated — only the copied structure is redacted.
const originalParts = (messages[0]?.parts ?? []) as Array<{ output?: Record<string, unknown> }>;
expect(originalParts[0]?.output?.data).toEqual({ basics: { name: "John" } });
});
});
const questionPart = {
type: "tool-ask_user_question",
toolCallId: "call-1",
@@ -200,6 +200,33 @@ type AgentChatComposerProps = {
const ANSWER_FIELD = "answer";
const OMITTED_RESUME_DATA = "[resume data omitted]";
// "Copy JSON" is for sharing/debugging a conversation; the full resume document embedded in every
// read_resume result (`output.data`) and fresh-document patch result (`output.resume`) would make
// the export enormous and repetitive. Strip those two payloads; everything else copies verbatim.
export function withoutResumeDataForExport(messages: UIMessage[]): UIMessage[] {
return messages.map((message) => ({
...message,
parts: message.parts.map((part) => {
if (part.type !== "tool-read_resume" && part.type !== "tool-apply_resume_patch") return part;
const output = "output" in part && typeof part.output === "object" && part.output ? part.output : null;
if (!output) return part;
const outputRecord = output as Record<string, unknown>;
if (part.type === "tool-read_resume" && "data" in outputRecord) {
return { ...part, output: { ...outputRecord, data: OMITTED_RESUME_DATA } } as UIMessage["parts"][number];
}
if (part.type === "tool-apply_resume_patch" && "resume" in outputRecord) {
return { ...part, output: { ...outputRecord, resume: OMITTED_RESUME_DATA } } as UIMessage["parts"][number];
}
return part;
}),
}));
}
function toRecord(value: unknown) {
return typeof value === "object" && value !== null ? (value as Record<string, unknown>) : null;
}
@@ -994,7 +1021,7 @@ export function AgentChat({
chatStatus: status,
isReadOnly,
readOnlyReason,
messages,
messages: withoutResumeDataForExport(messages),
actions,
},
null,