From 602f4b31c1521a51ae3d67449fc797e5eb0e057a Mon Sep 17 00:00:00 2001 From: Ammar Date: Fri, 10 Oct 2025 09:55:38 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Use=20Zod=20schemas=20for=20type?= =?UTF-8?q?=20guards=20to=20prevent=20drift?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Type guards in ToolMessage.tsx were manually checking for properties like 'lease' that could get out of sync with the actual type definitions. This replaces all hand-written type guards with schema-based validation using the existing Zod schemas from toolDefinitions.ts. Benefits: - Single source of truth: Zod schemas define both types and validation - Type guards automatically stay in sync with schema changes - Removing 'lease' from schema automatically removes it from guards - More robust: validates full structure, not just presence of keys Changes: - Import TOOL_DEFINITIONS from toolDefinitions.ts - Replace manual property checks with schema.safeParse() - Apply to all tool type guards (bash, file_read, file_edit_*, propose_plan) This architectural improvement prevents the category of bug where type guards drift from type definitions. --- src/components/Messages/ToolMessage.tsx | 46 +++++++------------------ 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/src/components/Messages/ToolMessage.tsx b/src/components/Messages/ToolMessage.tsx index 1a90d4460e..6e119b3d08 100644 --- a/src/components/Messages/ToolMessage.tsx +++ b/src/components/Messages/ToolMessage.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { TOOL_DEFINITIONS } from "@/utils/tools/toolDefinitions"; import type { DisplayedMessage } from "@/types/message"; import { GenericToolCall } from "../tools/GenericToolCall"; import { BashToolCall } from "../tools/BashToolCall"; @@ -24,52 +25,31 @@ interface ToolMessageProps { workspaceId?: string; } -// Type guard for bash tool +// Type guards using Zod schemas for single source of truth +// This ensures type guards stay in sync with tool definitions function isBashTool(toolName: string, args: unknown): args is BashToolArgs { - return ( - toolName === "bash" && - typeof args === "object" && - args !== null && - "script" in args && - "timeout_secs" in args - ); + if (toolName !== "bash") return false; + return TOOL_DEFINITIONS.bash.schema.safeParse(args).success; } -// Type guard for file_read tool function isFileReadTool(toolName: string, args: unknown): args is FileReadToolArgs { - return ( - toolName === "file_read" && typeof args === "object" && args !== null && "filePath" in args - ); + if (toolName !== "file_read") return false; + return TOOL_DEFINITIONS.file_read.schema.safeParse(args).success; } -// Type guard for file_edit_replace tool function isFileEditReplaceTool(toolName: string, args: unknown): args is FileEditReplaceToolArgs { - return ( - toolName === "file_edit_replace" && - typeof args === "object" && - args !== null && - "file_path" in args && - "edits" in args && - "lease" in args - ); + if (toolName !== "file_edit_replace") return false; + return TOOL_DEFINITIONS.file_edit_replace.schema.safeParse(args).success; } -// Type guard for file_edit_insert tool function isFileEditInsertTool(toolName: string, args: unknown): args is FileEditInsertToolArgs { - return ( - toolName === "file_edit_insert" && - typeof args === "object" && - args !== null && - "file_path" in args && - "line_offset" in args && - "content" in args && - "lease" in args - ); + if (toolName !== "file_edit_insert") return false; + return TOOL_DEFINITIONS.file_edit_insert.schema.safeParse(args).success; } -// Type guard for propose_plan tool function isProposePlanTool(toolName: string, args: unknown): args is ProposePlanToolArgs { - return toolName === "propose_plan" && typeof args === "object" && args !== null && "plan" in args; + if (toolName !== "propose_plan") return false; + return TOOL_DEFINITIONS.propose_plan.schema.safeParse(args).success; } export const ToolMessage: React.FC = ({ message, className, workspaceId }) => {