Clean up read tool activity content
This commit is contained in:
@@ -182,6 +182,36 @@ describe("opencode tool-call mapper", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("unwraps OpenCode XML read output into file content", () => {
|
||||
const item = expectMapped(
|
||||
mapOpencodeToolCall({
|
||||
toolName: "read",
|
||||
callId: "opencode-read-xml",
|
||||
status: "completed",
|
||||
input: { filePath: "/Users/moboudra/dev/paseo/docs/release.md" },
|
||||
output: [
|
||||
"<path>/Users/moboudra/dev/paseo/docs/release.md</path>",
|
||||
"<type>file</type>",
|
||||
"<content>",
|
||||
"1: # Release",
|
||||
"2:",
|
||||
"3: All workspaces share one version and release together.",
|
||||
"</content>",
|
||||
].join("\n"),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(item.detail).toEqual({
|
||||
type: "read",
|
||||
filePath: "/Users/moboudra/dev/paseo/docs/release.md",
|
||||
content: [
|
||||
"1: # Release",
|
||||
"2:",
|
||||
"3: All workspaces share one version and release together.",
|
||||
].join("\n"),
|
||||
});
|
||||
});
|
||||
|
||||
it("maps failed calls with required error", () => {
|
||||
const item = expectMapped(
|
||||
mapOpencodeToolCall({
|
||||
|
||||
@@ -260,8 +260,69 @@ function flattenReadContent(
|
||||
return flattenToolReadContent(value);
|
||||
}
|
||||
|
||||
function readXmlTag(value: string, tagName: "path" | "content"): string | undefined {
|
||||
const match = value.match(new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`, "i"));
|
||||
return match?.[1];
|
||||
}
|
||||
|
||||
function decodeXmlText(value: string): string {
|
||||
return value.replace(
|
||||
/&(?:#(\d+)|#x([0-9a-fA-F]+)|amp|lt|gt|quot|apos);/g,
|
||||
(entity, decimal, hex) => {
|
||||
if (decimal) {
|
||||
return String.fromCodePoint(Number.parseInt(decimal, 10));
|
||||
}
|
||||
if (hex) {
|
||||
return String.fromCodePoint(Number.parseInt(hex, 16));
|
||||
}
|
||||
switch (entity) {
|
||||
case "&":
|
||||
return "&";
|
||||
case "<":
|
||||
return "<";
|
||||
case ">":
|
||||
return ">";
|
||||
case """:
|
||||
return '"';
|
||||
case "'":
|
||||
return "'";
|
||||
default:
|
||||
return entity;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function trimOuterLineBreaks(value: string): string {
|
||||
return value.replace(/^\r?\n/, "").replace(/\r?\n$/, "");
|
||||
}
|
||||
|
||||
function parseXmlReadOutput(value: string): ToolReadOutputValue | undefined {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed.startsWith("<path>") || !/<content>/i.test(trimmed)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const filePath = nonEmptyString(readXmlTag(trimmed, "path")?.trim());
|
||||
const content = readXmlTag(trimmed, "content");
|
||||
if (!filePath && content === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
filePath: filePath ? decodeXmlText(filePath) : undefined,
|
||||
content: content === undefined ? undefined : decodeXmlText(trimOuterLineBreaks(content)),
|
||||
};
|
||||
}
|
||||
|
||||
const ToolReadOutputContentSchema = z.union([
|
||||
z.string().transform((value) => ({ filePath: undefined, content: nonEmptyString(value) })),
|
||||
z.string().transform((value) => {
|
||||
const xmlReadOutput = parseXmlReadOutput(value);
|
||||
if (xmlReadOutput) {
|
||||
return xmlReadOutput;
|
||||
}
|
||||
return { filePath: undefined, content: nonEmptyString(value) };
|
||||
}),
|
||||
ToolReadChunkSchema.transform((value) => ({
|
||||
filePath: undefined,
|
||||
content: flattenReadContent(value),
|
||||
|
||||
Reference in New Issue
Block a user