mirror of
https://github.com/block/buzz.git
synced 2026-11-03 16:01:35 +00:00
Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co>
157 lines
3.6 KiB
TypeScript
157 lines
3.6 KiB
TypeScript
import type {
|
|
AgentActivityDescriptor,
|
|
TranscriptItem,
|
|
} from "./agentSessionTypes";
|
|
import {
|
|
asRecord,
|
|
getToolString,
|
|
parseToolResultValue,
|
|
} from "./agentSessionUtils";
|
|
|
|
type ToolItem = Extract<TranscriptItem, { type: "tool" }>;
|
|
|
|
export type FileReadContentLine = {
|
|
kind: "context" | "meta";
|
|
text: string;
|
|
};
|
|
|
|
export type FileReadContent = {
|
|
footerText: string;
|
|
footerTitle: string;
|
|
lines: FileReadContentLine[];
|
|
path: string;
|
|
};
|
|
|
|
export function buildSkillReadContent(
|
|
item: ToolItem,
|
|
descriptor: AgentActivityDescriptor,
|
|
): FileReadContent | null {
|
|
if (descriptor.renderClass !== "skill-read") {
|
|
return null;
|
|
}
|
|
|
|
const skillRef =
|
|
getToolString(item.args, ["name"]) ??
|
|
descriptor.preview ??
|
|
descriptor.object;
|
|
if (!skillRef) {
|
|
return null;
|
|
}
|
|
|
|
const resultText = getResultText(item.result);
|
|
if (!resultText.trim()) {
|
|
return null;
|
|
}
|
|
|
|
const rawLines = trimTrailingEmptyLines(resultText.split(/\r?\n/));
|
|
const lines =
|
|
rawLines.length > 0
|
|
? rawLines.map((line) => ({
|
|
kind: "context" as const,
|
|
text: line,
|
|
}))
|
|
: [{ kind: "meta" as const, text: "No skill content returned." }];
|
|
|
|
const footerText = skillRef.includes("/") ? skillRef : `${skillRef}/SKILL.md`;
|
|
|
|
return {
|
|
footerText,
|
|
footerTitle: skillRef,
|
|
lines,
|
|
path: skillRef,
|
|
};
|
|
}
|
|
|
|
export function buildFileReadContent(
|
|
item: ToolItem,
|
|
descriptor: AgentActivityDescriptor,
|
|
): FileReadContent | null {
|
|
if (descriptor.renderClass !== "file-read") {
|
|
return null;
|
|
}
|
|
|
|
const path =
|
|
getToolString(item.args, ["path", "file", "file_path", "target_file"]) ??
|
|
descriptor.object ??
|
|
descriptor.preview;
|
|
if (!path) {
|
|
return null;
|
|
}
|
|
|
|
const resultText = getResultText(item.result);
|
|
if (!resultText.trim()) {
|
|
return null;
|
|
}
|
|
|
|
const parsed = parseReadFileOutput(resultText, path);
|
|
return {
|
|
footerText: parsed.footerText,
|
|
footerTitle: parsed.footerTitle,
|
|
lines: parsed.lines,
|
|
path,
|
|
};
|
|
}
|
|
|
|
function parseReadFileOutput(resultText: string, path: string) {
|
|
const rawLines = trimTrailingEmptyLines(resultText.split(/\r?\n/));
|
|
const firstLine = rawLines[0] ?? "";
|
|
const remainingLines = rawLines.slice(1);
|
|
const hasRangeHeader =
|
|
firstLine.startsWith(path) && /\s\(lines \d+-\d+ of \d+\)$/.test(firstLine);
|
|
const contentLines = hasRangeHeader ? remainingLines : rawLines;
|
|
const lines =
|
|
contentLines.length > 0
|
|
? contentLines.map((line) => ({
|
|
kind: isReadFileMetaLine(line)
|
|
? ("meta" as const)
|
|
: ("context" as const),
|
|
text: line,
|
|
}))
|
|
: [{ kind: "meta" as const, text: "No file content returned." }];
|
|
|
|
return {
|
|
footerText: hasRangeHeader ? firstLine : path,
|
|
footerTitle: hasRangeHeader ? `${path}\n${firstLine}` : path,
|
|
lines,
|
|
};
|
|
}
|
|
|
|
function getResultText(result: string): string {
|
|
const parsed = parseToolResultValue(result);
|
|
if (typeof parsed === "string") {
|
|
return parsed;
|
|
}
|
|
|
|
const record = asRecord(parsed);
|
|
return (
|
|
getRecordString(record, ["content", "text", "output", "stdout"]) ?? result
|
|
);
|
|
}
|
|
|
|
function getRecordString(
|
|
record: Record<string, unknown>,
|
|
keys: string[],
|
|
): string | null {
|
|
for (const key of keys) {
|
|
const value = record[key];
|
|
if (typeof value === "string") {
|
|
return value;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function isReadFileMetaLine(line: string) {
|
|
return /^\[showing lines \d+-\d+ of \d+; use offset=\d+ to continue\]$/.test(
|
|
line,
|
|
);
|
|
}
|
|
|
|
function trimTrailingEmptyLines(lines: string[]) {
|
|
let end = lines.length;
|
|
while (end > 0 && lines[end - 1] === "") {
|
|
end -= 1;
|
|
}
|
|
return end === lines.length ? lines : lines.slice(0, end);
|
|
}
|