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", () => {
|
it("maps failed calls with required error", () => {
|
||||||
const item = expectMapped(
|
const item = expectMapped(
|
||||||
mapOpencodeToolCall({
|
mapOpencodeToolCall({
|
||||||
|
|||||||
@@ -260,8 +260,69 @@ function flattenReadContent(
|
|||||||
return flattenToolReadContent(value);
|
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([
|
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) => ({
|
ToolReadChunkSchema.transform((value) => ({
|
||||||
filePath: undefined,
|
filePath: undefined,
|
||||||
content: flattenReadContent(value),
|
content: flattenReadContent(value),
|
||||||
|
|||||||
Reference in New Issue
Block a user