mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
feat: add structured tool result types for better rendering
Server-side: - Add StructuredToolResult discriminated union type with command, file_write, file_edit, file_read, and generic variants - Implement buildStructuredToolResult in claude-agent.ts to detect tool types and emit properly structured results - Update codex-agent.ts to emit structured command results Client-side: - Add type guard and extraction functions for structured results - Render tool calls based on result.type when available: - command: show command, output, exit code - file_write/file_edit: show diff viewer with proper +/- format - file_read: show file content - generic: show raw JSON - Fall back to heuristic parsing for backwards compatibility This fixes file writes showing as "Command: success message" - they now properly show as diffs with +line additions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,7 @@ import { baseColors, theme } from "@/styles/theme";
|
||||
import { Colors } from "@/constants/theme";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
import type { TodoEntry, ThoughtStatus } from "@/types/stream";
|
||||
import type { CommandDetails, EditEntry, ReadEntry } from "@/utils/tool-call-parsers";
|
||||
import type { CommandDetails, EditEntry, ReadEntry, DiffLine } from "@/utils/tool-call-parsers";
|
||||
import { DiffViewer } from "./diff-viewer";
|
||||
import { resolveToolCallPreview } from "./tool-call-preview";
|
||||
|
||||
@@ -1205,6 +1205,80 @@ function formatFullValue(value: unknown): string {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to build diff lines (mirrors logic from tool-call-parsers)
|
||||
function buildLineDiffFromStrings(originalText: string, updatedText: string): DiffLine[] {
|
||||
const splitIntoLines = (text: string): string[] => {
|
||||
if (!text) return [];
|
||||
return text.replace(/\r\n/g, "\n").split("\n");
|
||||
};
|
||||
|
||||
const originalLines = splitIntoLines(originalText);
|
||||
const updatedLines = splitIntoLines(updatedText);
|
||||
|
||||
const hasAnyContent = originalLines.length > 0 || updatedLines.length > 0;
|
||||
if (!hasAnyContent) return [];
|
||||
|
||||
const m = originalLines.length;
|
||||
const n = updatedLines.length;
|
||||
const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
||||
|
||||
for (let i = m - 1; i >= 0; i -= 1) {
|
||||
for (let j = n - 1; j >= 0; j -= 1) {
|
||||
if (originalLines[i] === updatedLines[j]) {
|
||||
dp[i][j] = dp[i + 1][j + 1] + 1;
|
||||
} else {
|
||||
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const diff: DiffLine[] = [];
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
|
||||
while (i < m && j < n) {
|
||||
if (originalLines[i] === updatedLines[j]) {
|
||||
diff.push({ type: "context", content: ` ${originalLines[i]}` });
|
||||
i += 1;
|
||||
j += 1;
|
||||
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
||||
diff.push({ type: "remove", content: `-${originalLines[i]}` });
|
||||
i += 1;
|
||||
} else {
|
||||
diff.push({ type: "add", content: `+${updatedLines[j]}` });
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
|
||||
while (i < m) {
|
||||
diff.push({ type: "remove", content: `-${originalLines[i]}` });
|
||||
i += 1;
|
||||
}
|
||||
|
||||
while (j < n) {
|
||||
diff.push({ type: "add", content: `+${updatedLines[j]}` });
|
||||
j += 1;
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
// Type guard for structured tool results
|
||||
type StructuredToolResult = {
|
||||
type: "command" | "file_write" | "file_edit" | "file_read" | "generic";
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
function isStructuredToolResult(result: unknown): result is StructuredToolResult {
|
||||
return (
|
||||
typeof result === "object" &&
|
||||
result !== null &&
|
||||
"type" in result &&
|
||||
typeof result.type === "string" &&
|
||||
["command", "file_write", "file_edit", "file_read", "generic"].includes(result.type)
|
||||
);
|
||||
}
|
||||
|
||||
export const ToolCall = memo(function ToolCall({
|
||||
toolName,
|
||||
kind,
|
||||
@@ -1230,6 +1304,12 @@ export const ToolCall = memo(function ToolCall({
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
// Check if result has a type field for structured rendering
|
||||
const structuredResult = useMemo(
|
||||
() => (isStructuredToolResult(result) ? result : null),
|
||||
[result]
|
||||
);
|
||||
|
||||
const IconComponent = kind
|
||||
? toolKindIcons[kind.toLowerCase()] || Wrench
|
||||
: Wrench;
|
||||
@@ -1251,6 +1331,79 @@ export const ToolCall = memo(function ToolCall({
|
||||
setIsExpanded((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
// Helper functions to extract data from structured results
|
||||
const extractCommandFromStructured = useCallback(
|
||||
(structured: StructuredToolResult): CommandDetails | null => {
|
||||
if (structured.type !== "command") return null;
|
||||
|
||||
const cmd: CommandDetails = {};
|
||||
if (typeof structured.command === "string") cmd.command = structured.command;
|
||||
if (typeof structured.cwd === "string") cmd.cwd = structured.cwd;
|
||||
if (typeof structured.output === "string") cmd.output = structured.output;
|
||||
if (typeof structured.exitCode === "number") cmd.exitCode = structured.exitCode;
|
||||
|
||||
return cmd.command || cmd.output ? cmd : null;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const extractDiffFromStructured = useCallback(
|
||||
(structured: StructuredToolResult): EditEntry[] => {
|
||||
if (structured.type !== "file_write" && structured.type !== "file_edit") {
|
||||
return [];
|
||||
}
|
||||
|
||||
const filePath = typeof structured.filePath === "string" ? structured.filePath : undefined;
|
||||
|
||||
// For file_write, create a diff from oldContent -> newContent
|
||||
if (structured.type === "file_write") {
|
||||
const oldContent = typeof structured.oldContent === "string" ? structured.oldContent : "";
|
||||
const newContent = typeof structured.newContent === "string" ? structured.newContent : "";
|
||||
|
||||
// Use the same diff building logic from tool-call-parsers
|
||||
const diffLines = buildLineDiffFromStrings(oldContent, newContent);
|
||||
if (diffLines.length > 0) {
|
||||
return [{ filePath, diffLines }];
|
||||
}
|
||||
}
|
||||
|
||||
// For file_edit, it might already have diffLines or we need to construct them
|
||||
if (structured.type === "file_edit") {
|
||||
// Check if diffLines are provided directly
|
||||
if (Array.isArray(structured.diffLines)) {
|
||||
return [{ filePath, diffLines: structured.diffLines as DiffLine[] }];
|
||||
}
|
||||
|
||||
// Otherwise try to build from old/new content
|
||||
const oldContent = typeof structured.oldContent === "string" ? structured.oldContent : "";
|
||||
const newContent = typeof structured.newContent === "string" ? structured.newContent : "";
|
||||
const diffLines = buildLineDiffFromStrings(oldContent, newContent);
|
||||
if (diffLines.length > 0) {
|
||||
return [{ filePath, diffLines }];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const extractReadFromStructured = useCallback(
|
||||
(structured: StructuredToolResult): ReadEntry[] => {
|
||||
if (structured.type !== "file_read") return [];
|
||||
|
||||
const filePath = typeof structured.filePath === "string" ? structured.filePath : undefined;
|
||||
const content = typeof structured.content === "string" ? structured.content : "";
|
||||
|
||||
if (content) {
|
||||
return [{ filePath, content }];
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const hasCommandDetails = Boolean(
|
||||
commandDetails &&
|
||||
(commandDetails.command ||
|
||||
@@ -1412,7 +1565,185 @@ export const ToolCall = memo(function ToolCall({
|
||||
}, [args, serializedArgs, result, serializedResult, error, serializedError, hasStructuredContent]);
|
||||
|
||||
const renderDetails = useCallback(() => {
|
||||
// Don't show readSections if commandDetails has output (they show the same data)
|
||||
// If we have a structured result, use type-based rendering
|
||||
if (structuredResult) {
|
||||
const sections: ReactNode[] = [];
|
||||
|
||||
// Render based on type
|
||||
switch (structuredResult.type) {
|
||||
case "command": {
|
||||
const cmd = extractCommandFromStructured(structuredResult);
|
||||
if (cmd) {
|
||||
// Reuse the command section rendering logic
|
||||
sections.push(
|
||||
<View key="command" style={toolCallStylesheet.section}>
|
||||
<Text style={toolCallStylesheet.sectionTitle}>Command</Text>
|
||||
{cmd.command ? (
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={toolCallStylesheet.jsonScroll}
|
||||
contentContainerStyle={toolCallStylesheet.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={toolCallStylesheet.scrollText}>{cmd.command}</Text>
|
||||
</ScrollView>
|
||||
) : null}
|
||||
{cmd.cwd ? (
|
||||
<View style={toolCallStylesheet.metaRow}>
|
||||
<Text style={toolCallStylesheet.metaLabel}>Directory</Text>
|
||||
<Text style={toolCallStylesheet.metaValue}>{cmd.cwd}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
{cmd.exitCode !== undefined ? (
|
||||
<View style={toolCallStylesheet.metaRow}>
|
||||
<Text style={toolCallStylesheet.metaLabel}>Exit Code</Text>
|
||||
<Text style={toolCallStylesheet.metaValue}>
|
||||
{cmd.exitCode === null ? "Unknown" : cmd.exitCode}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
{cmd.output ? (
|
||||
<ScrollView
|
||||
style={toolCallStylesheet.scrollArea}
|
||||
contentContainerStyle={toolCallStylesheet.scrollContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator={true}
|
||||
>
|
||||
<Text style={toolCallStylesheet.scrollText}>{cmd.output}</Text>
|
||||
</ScrollView>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "file_write":
|
||||
case "file_edit": {
|
||||
const diffs = extractDiffFromStructured(structuredResult);
|
||||
diffs.forEach((entry, index) => {
|
||||
sections.push(
|
||||
<View
|
||||
key={`diff-${index}`}
|
||||
style={toolCallStylesheet.section}
|
||||
>
|
||||
<Text style={toolCallStylesheet.sectionTitle}>Diff</Text>
|
||||
{entry.filePath ? (
|
||||
<View style={toolCallStylesheet.fileBadge}>
|
||||
<Text style={toolCallStylesheet.fileBadgeText}>{entry.filePath}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
<View style={toolCallStylesheet.diffContainer}>
|
||||
<DiffViewer diffLines={entry.diffLines} maxHeight={240} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "file_read": {
|
||||
const reads = extractReadFromStructured(structuredResult);
|
||||
reads.forEach((entry, index) => {
|
||||
sections.push(
|
||||
<View
|
||||
key={`read-${index}`}
|
||||
style={toolCallStylesheet.section}
|
||||
>
|
||||
<Text style={toolCallStylesheet.sectionTitle}>Read Result</Text>
|
||||
{entry.filePath ? (
|
||||
<View style={toolCallStylesheet.fileBadge}>
|
||||
<Text style={toolCallStylesheet.fileBadgeText}>{entry.filePath}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
<ScrollView
|
||||
style={toolCallStylesheet.scrollArea}
|
||||
contentContainerStyle={toolCallStylesheet.scrollContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator={true}
|
||||
>
|
||||
<Text style={toolCallStylesheet.scrollText}>{entry.content}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "generic":
|
||||
default: {
|
||||
// Show raw JSON for generic type
|
||||
if (result !== undefined) {
|
||||
sections.push(
|
||||
<View key="result" style={toolCallStylesheet.section}>
|
||||
<Text style={toolCallStylesheet.sectionTitle}>Result</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={toolCallStylesheet.jsonScroll}
|
||||
contentContainerStyle={toolCallStylesheet.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={toolCallStylesheet.scrollText}>{serializedResult}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Always show args if available
|
||||
if (args !== undefined) {
|
||||
sections.unshift(
|
||||
<View key="args" style={toolCallStylesheet.section}>
|
||||
<Text style={toolCallStylesheet.sectionTitle}>Arguments</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={toolCallStylesheet.jsonScroll}
|
||||
contentContainerStyle={toolCallStylesheet.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={toolCallStylesheet.scrollText}>{serializedArgs}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Always show errors if available
|
||||
if (error !== undefined) {
|
||||
sections.push(
|
||||
<View key="error" style={toolCallStylesheet.section}>
|
||||
<Text style={toolCallStylesheet.sectionTitle}>Error</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={[toolCallStylesheet.jsonScroll, toolCallStylesheet.jsonScrollError]}
|
||||
contentContainerStyle={toolCallStylesheet.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={[toolCallStylesheet.scrollText, toolCallStylesheet.errorText]}>
|
||||
{serializedError}
|
||||
</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (sections.length === 0) {
|
||||
return (
|
||||
<Text style={toolCallStylesheet.emptyStateText}>
|
||||
No additional details available
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return <View style={toolCallStylesheet.detailContent}>{sections}</View>;
|
||||
}
|
||||
|
||||
// Fall back to heuristic parsing for backwards compatibility
|
||||
const showReadSections = !commandDetails?.output && readSections.length > 0;
|
||||
|
||||
if (
|
||||
@@ -1436,7 +1767,23 @@ export const ToolCall = memo(function ToolCall({
|
||||
{jsonSections}
|
||||
</View>
|
||||
);
|
||||
}, [commandSection, commandDetails?.output, editSections, readSections, jsonSections]);
|
||||
}, [
|
||||
structuredResult,
|
||||
extractCommandFromStructured,
|
||||
extractDiffFromStructured,
|
||||
extractReadFromStructured,
|
||||
serializedArgs,
|
||||
serializedResult,
|
||||
serializedError,
|
||||
args,
|
||||
result,
|
||||
error,
|
||||
commandSection,
|
||||
commandDetails?.output,
|
||||
editSections,
|
||||
readSections,
|
||||
jsonSections,
|
||||
]);
|
||||
|
||||
return (
|
||||
<ExpandableBadge
|
||||
|
||||
@@ -1043,11 +1043,23 @@ class ClaudeAgentSession implements AgentSession {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const server = entry?.server ?? block.server ?? "tool";
|
||||
const tool = entry?.name ?? block.tool_name ?? "tool";
|
||||
const content = typeof block.content === "string" ? block.content : "";
|
||||
const input = entry?.input;
|
||||
|
||||
// Build structured result based on tool type
|
||||
const structured = this.buildStructuredToolResult(server, tool, content, input);
|
||||
|
||||
if (structured) {
|
||||
return structured;
|
||||
}
|
||||
|
||||
// Fallback to legacy format
|
||||
const result: Record<string, unknown> = {};
|
||||
|
||||
// SDK tool_result blocks have content as a string
|
||||
if (typeof block.content === "string" && block.content.length > 0) {
|
||||
result.result = { output: block.content };
|
||||
if (content.length > 0) {
|
||||
result.result = { output: content };
|
||||
}
|
||||
|
||||
// Preserve file changes tracked during tool execution
|
||||
@@ -1058,6 +1070,86 @@ class ClaudeAgentSession implements AgentSession {
|
||||
return Object.keys(result).length > 0 ? result : undefined;
|
||||
}
|
||||
|
||||
private buildStructuredToolResult(
|
||||
server: string,
|
||||
tool: string,
|
||||
output: string,
|
||||
input?: Record<string, unknown> | null
|
||||
): Record<string, unknown> | undefined {
|
||||
const normalizedServer = server.toLowerCase();
|
||||
const normalizedTool = tool.toLowerCase();
|
||||
|
||||
// Command execution tools
|
||||
if (
|
||||
normalizedServer.includes("bash") ||
|
||||
normalizedServer.includes("shell") ||
|
||||
normalizedServer.includes("command") ||
|
||||
normalizedTool.includes("bash") ||
|
||||
normalizedTool.includes("shell") ||
|
||||
normalizedTool.includes("command") ||
|
||||
(input && (typeof input.command === "string" || Array.isArray(input.command)))
|
||||
) {
|
||||
const command = this.extractCommandText(input ?? {}) ?? "command";
|
||||
return {
|
||||
type: "command",
|
||||
command,
|
||||
output,
|
||||
cwd: typeof input?.cwd === "string" ? input.cwd : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
// File write tools (new files or complete replacements)
|
||||
if (
|
||||
normalizedTool.includes("write") ||
|
||||
normalizedTool === "write_file" ||
|
||||
normalizedTool === "create_file"
|
||||
) {
|
||||
if (input && typeof input.file_path === "string") {
|
||||
return {
|
||||
type: "file_write",
|
||||
filePath: input.file_path,
|
||||
oldContent: "",
|
||||
newContent: typeof input.content === "string" ? input.content : output,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// File edit/patch tools
|
||||
if (
|
||||
normalizedTool.includes("edit") ||
|
||||
normalizedTool.includes("patch") ||
|
||||
normalizedTool === "apply_patch" ||
|
||||
normalizedTool === "apply_diff"
|
||||
) {
|
||||
if (input && typeof input.file_path === "string") {
|
||||
return {
|
||||
type: "file_edit",
|
||||
filePath: input.file_path,
|
||||
diff: typeof input.patch === "string" ? input.patch : typeof input.diff === "string" ? input.diff : undefined,
|
||||
oldContent: typeof input.old_str === "string" ? input.old_str : undefined,
|
||||
newContent: typeof input.new_str === "string" ? input.new_str : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// File read tools
|
||||
if (
|
||||
normalizedTool.includes("read") ||
|
||||
normalizedTool === "read_file" ||
|
||||
normalizedTool === "view_file"
|
||||
) {
|
||||
if (input && typeof input.file_path === "string") {
|
||||
return {
|
||||
type: "file_read",
|
||||
filePath: input.file_path,
|
||||
content: output,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private mapPartialEvent(event: SDKPartialAssistantMessage["event"]): AgentTimelineItem[] {
|
||||
if (event.type === "content_block_start" && (event.content_block as ClaudeContentChunk | undefined)?.type === "tool_use") {
|
||||
const block = event.content_block as ClaudeContentChunk;
|
||||
|
||||
@@ -817,7 +817,12 @@ class CodexAgentSession implements AgentSession {
|
||||
return { type: "assistant_message", text: item.text };
|
||||
case "reasoning":
|
||||
return { type: "reasoning", text: item.text };
|
||||
case "command_execution":
|
||||
case "command_execution": {
|
||||
const output = (item as any)?.output;
|
||||
const cwd = (item as any)?.cwd;
|
||||
const commandValue = item.command;
|
||||
const command = typeof commandValue === "string" ? commandValue :
|
||||
Array.isArray(commandValue) ? (commandValue as string[]).join(" ") : "command";
|
||||
return createToolCallTimelineItem({
|
||||
server: "command",
|
||||
tool: "shell",
|
||||
@@ -825,10 +830,16 @@ class CodexAgentSession implements AgentSession {
|
||||
callId: (item as any)?.call_id,
|
||||
displayName: buildCommandDisplayName(item.command),
|
||||
kind: "execute",
|
||||
input: { command: item.command, cwd: (item as any)?.cwd },
|
||||
output: (item as any)?.output,
|
||||
input: { command: item.command, cwd },
|
||||
output: typeof output === "string" ? {
|
||||
type: "command",
|
||||
command,
|
||||
output,
|
||||
cwd,
|
||||
} : undefined,
|
||||
error: (item as any)?.error,
|
||||
});
|
||||
}
|
||||
case "file_change": {
|
||||
const files = item.changes.map((change) => ({ path: change.path, kind: change.kind }));
|
||||
return createToolCallTimelineItem({
|
||||
@@ -1100,6 +1111,16 @@ function finalizeRolloutFunctionCall(
|
||||
const result = safeJsonParse<CommandExecutionResult>(payload.output);
|
||||
const exitCode = result?.metadata?.exit_code;
|
||||
const status = exitCode === undefined || exitCode === 0 ? "completed" : "failed";
|
||||
|
||||
// Build structured command output
|
||||
const output = result?.stdout ? {
|
||||
type: "command" as const,
|
||||
command: command.command,
|
||||
output: result.stdout,
|
||||
exitCode,
|
||||
cwd: command.cwd,
|
||||
} : result;
|
||||
|
||||
events.push({
|
||||
type: "timeline",
|
||||
provider: "codex",
|
||||
@@ -1111,7 +1132,7 @@ function finalizeRolloutFunctionCall(
|
||||
displayName: buildCommandDisplayName(command.command),
|
||||
kind: "execute",
|
||||
input: { command: command.command, cwd: command.cwd },
|
||||
output: result,
|
||||
output,
|
||||
}),
|
||||
});
|
||||
commandCalls.delete(payload.call_id);
|
||||
|
||||
@@ -115,6 +115,15 @@ export const AgentPermissionRequestPayloadSchema: z.ZodType<AgentPermissionReque
|
||||
metadata: z.record(z.unknown()).optional(),
|
||||
});
|
||||
|
||||
// Structured tool result types for better client rendering
|
||||
// These types define the structure of the `output` field in tool_call timeline items
|
||||
export type StructuredToolResult =
|
||||
| { type: "command"; command: string; output: string; exitCode?: number; cwd?: string }
|
||||
| { type: "file_write"; filePath: string; oldContent: string; newContent: string }
|
||||
| { type: "file_edit"; filePath: string; diff?: string; oldContent?: string; newContent?: string }
|
||||
| { type: "file_read"; filePath: string; content: string }
|
||||
| { type: "generic"; data: unknown };
|
||||
|
||||
export const AgentTimelineItemPayloadSchema: z.ZodType<AgentTimelineItem> =
|
||||
z.discriminatedUnion("type", [
|
||||
z.object({
|
||||
|
||||
Reference in New Issue
Block a user