Skip to content

Conversation

@HenryHengZJ
Copy link
Contributor

@HenryHengZJ HenryHengZJ commented Oct 31, 2025

implement parseWithTypeConversion - parse a value against a Zod schema with automatic type conversion for common type mismatches

Summary by CodeRabbit

  • New Features

    • Added a utility that automatically converts common data types during input validation.
  • Improvements

    • Improved input validation across tools to perform automatic type conversion for common mismatches, reducing validation errors and increasing compatibility with varied input formats.
    • Tools now more robustly handle type mismatches by attempting conversion before reporting parsing failures.

…a with automatic type conversion for common type mismatches
@coderabbitai
Copy link

coderabbitai bot commented Oct 31, 2025

Walkthrough

A new utility function parseWithTypeConversion was added and six tool modules were updated to call it instead of invoking Zod's schema.parseAsync directly; the utility attempts type conversions for common mismatches and retries parsing on Zod validation failures.

Changes

Cohort / File(s) Change Summary
Tool Parsing Updates
packages/components/nodes/tools/AgentAsTool/AgentAsTool.ts, packages/components/nodes/tools/ChatflowTool/ChatflowTool.ts, packages/components/nodes/tools/CodeInterpreterE2B/CodeInterpreterE2B.ts, packages/components/nodes/tools/CustomTool/core.ts, packages/components/nodes/tools/OpenAPIToolkit/core.ts, packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts
Replaced direct Zod parsing (this.schema.parseAsync(arg)) with parseWithTypeConversion(this.schema, arg) and added the utility import; parsing call sites now use the new conversion-and-retry parsing helper while preserving existing error handling sites.
New Utility Function
packages/components/src/utils.ts
Added export async function parseWithTypeConversion<T extends z.ZodTypeAny>(schema: T, arg: unknown, maxDepth = 10): Promise<z.infer<T>> which wraps Zod parsing, detects invalid_type ZodErrors, attempts nested value conversions (string/number/boolean/object/array) via path helpers and cloneDeep, and retries parsing; rethrows original error if conversion fails or max depth reached.

Sequence Diagram

sequenceDiagram
    participant Tool as Tool (caller)
    participant Parser as parseWithTypeConversion
    participant Schema as Zod Schema
    participant Converter as Converter Helpers

    Tool->>Parser: parseWithTypeConversion(schema, arg)
    Parser->>Schema: schema.parseAsync(arg)
    alt parse succeeds
        Schema-->>Parser: parsed value
        Parser-->>Tool: return parsed value
    else parse fails (ZodError)
        Schema-->>Parser: ZodError (invalid_type)
        Parser->>Converter: extract error paths & values
        Converter->>Converter: attempt conversions (string/number/boolean/object/array)
        Converter-->>Parser: converted arg
        Parser->>Schema: schema.parseAsync(converted arg)
        alt retry succeeds
            Schema-->>Parser: parsed value
            Parser-->>Tool: return parsed value
        else retry fails
            Schema-->>Parser: ZodError
            Parser-->>Tool: rethrow original ZodError
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus areas for review:
    • packages/components/src/utils.ts: correctness of nested-path extraction, conversion heuristics, recursion and maxDepth logic, and safe mutation with cloneDeep
    • Consistency across the six tool files that now call parseWithTypeConversion and preservation of original error semantics
    • Potential edge cases where conversion might change intended types (arrays vs objects) and any test coverage for those paths
    • Imports (lodash utilities) and ensuring no accidental circular references

Poem

🐰
I hopped through schemas, nose to code,
Converting bits that once erred mode,
Strings to numbers, false to true,
I nudged each value, then retried too,
A happy parse — a rabbit's ode 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title Check ❓ Inconclusive The PR title "Bugfix/Zod Parsing" uses a generic pattern that lacks specificity about what the changeset actually accomplishes. While the title does reference Zod parsing, which is involved in the changes, it fails to convey the primary innovation of this PR: the introduction of a new parseWithTypeConversion utility function that adds automatic type conversion capabilities to Zod schema parsing. The title could apply to many different Zod-related changes and doesn't clearly communicate to a teammate scanning the history what this specific changeset does or what problem it solves. Consider revising the title to be more descriptive and specific about the actual change. A clearer title might be something like "Add automatic type conversion for Zod schema parsing" or "Implement parseWithTypeConversion utility for schema validation," which would better convey the main purpose of the changeset and help teammates understand the change at a glance.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bugfix/Zod-Parsing

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3d5b77 and 02bab45.

📒 Files selected for processing (7)
  • packages/components/nodes/tools/AgentAsTool/AgentAsTool.ts (2 hunks)
  • packages/components/nodes/tools/ChatflowTool/ChatflowTool.ts (2 hunks)
  • packages/components/nodes/tools/CodeInterpreterE2B/CodeInterpreterE2B.ts (2 hunks)
  • packages/components/nodes/tools/CustomTool/core.ts (2 hunks)
  • packages/components/nodes/tools/OpenAPIToolkit/core.ts (2 hunks)
  • packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts (2 hunks)
  • packages/components/src/utils.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
packages/components/nodes/tools/OpenAPIToolkit/core.ts (1)
packages/components/src/utils.ts (1)
  • parseWithTypeConversion (1771-1920)
packages/components/nodes/tools/ChatflowTool/ChatflowTool.ts (1)
packages/components/src/utils.ts (1)
  • parseWithTypeConversion (1771-1920)
packages/components/nodes/tools/CustomTool/core.ts (1)
packages/components/src/utils.ts (1)
  • parseWithTypeConversion (1771-1920)
packages/components/nodes/tools/AgentAsTool/AgentAsTool.ts (1)
packages/components/src/utils.ts (1)
  • parseWithTypeConversion (1771-1920)
packages/components/nodes/tools/CodeInterpreterE2B/CodeInterpreterE2B.ts (1)
packages/components/src/utils.ts (1)
  • parseWithTypeConversion (1771-1920)
packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts (1)
packages/components/src/utils.ts (1)
  • parseWithTypeConversion (1771-1920)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (ubuntu-latest, 18.15.0)
  • GitHub Check: build
🔇 Additional comments (4)
packages/components/nodes/tools/CustomTool/core.ts (2)

5-5: LGTM: Import added correctly.

The parseWithTypeConversion import is correctly added alongside existing utilities from the same module.


71-71: Good enhancement: automatic type conversion improves input handling.

The replacement of direct schema parsing with parseWithTypeConversion adds automatic type coercion for common mismatches (e.g., string ↔ number, object ↔ JSON string), making the tool more resilient to input variations. Error handling is preserved, and cloneDeep is properly imported from lodash in utils.ts.

packages/components/nodes/tools/AgentAsTool/AgentAsTool.ts (2)

7-13: LGTM: Import added correctly.

The parseWithTypeConversion utility is correctly imported alongside existing helper functions from the utils module.


282-282: Good enhancement: consistent application of type conversion.

Replacing direct schema parsing with parseWithTypeConversion adds automatic type coercion, improving the tool's resilience to input type mismatches. This change is consistently applied across multiple tool implementations (CustomTool, ChatflowTool, CodeInterpreterE2B, OpenAPIToolkit, RetrieverTool), which enhances maintainability.

…ursion control, preventing infinite loops during parsing.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02bab45 and 5ac1488.

📒 Files selected for processing (1)
  • packages/components/src/utils.ts (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: build (ubuntu-latest, 18.15.0)

Comment on lines +1784 to +1918
// Deep clone the arg to avoid mutating the original
const modifiedArg = typeof arg === 'object' && arg !== null ? cloneDeep(arg) : arg
let hasModification = false

// Helper function to set a value at a nested path
const setValueAtPath = (obj: any, path: (string | number)[], value: any): void => {
let current = obj
for (let i = 0; i < path.length - 1; i++) {
const key = path[i]
if (current && typeof current === 'object' && key in current) {
current = current[key]
} else {
return // Path doesn't exist
}
}
if (current !== undefined && current !== null) {
const finalKey = path[path.length - 1]
current[finalKey] = value
}
}

// Helper function to get a value at a nested path
const getValueAtPath = (obj: any, path: (string | number)[]): any => {
let current = obj
for (const key of path) {
if (current && typeof current === 'object' && key in current) {
current = current[key]
} else {
return undefined
}
}
return current
}

// Helper function to convert value to expected type
const convertValue = (value: any, expected: string, received: string): any => {
// Expected string
if (expected === 'string') {
if (received === 'object' || received === 'array') {
return JSON.stringify(value)
}
if (received === 'number' || received === 'boolean') {
return String(value)
}
}
// Expected number
else if (expected === 'number') {
if (received === 'string') {
const parsed = parseFloat(value)
if (!isNaN(parsed)) {
return parsed
}
}
if (received === 'boolean') {
return value ? 1 : 0
}
}
// Expected boolean
else if (expected === 'boolean') {
if (received === 'string') {
const lower = String(value).toLowerCase().trim()
if (lower === 'true' || lower === '1' || lower === 'yes') {
return true
}
if (lower === 'false' || lower === '0' || lower === 'no') {
return false
}
}
if (received === 'number') {
return value !== 0
}
}
// Expected object
else if (expected === 'object') {
if (received === 'string') {
try {
const parsed = JSON.parse(value)
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
return parsed
}
} catch {
// Invalid JSON, return undefined to skip conversion
}
}
}
// Expected array
else if (expected === 'array') {
if (received === 'string') {
try {
const parsed = JSON.parse(value)
if (Array.isArray(parsed)) {
return parsed
}
} catch {
// Invalid JSON, return undefined to skip conversion
}
}
if (received === 'object' && value !== null) {
// Convert object to array (e.g., {0: 'a', 1: 'b'} -> ['a', 'b'])
// Only if it looks like an array-like object
const keys = Object.keys(value)
const numericKeys = keys.filter((k) => /^\d+$/.test(k))
if (numericKeys.length === keys.length) {
return numericKeys.map((k) => value[k])
}
}
}
return undefined // No conversion possible
}

// Process each issue in the error
for (const issue of zodError.issues) {
// Handle invalid_type errors (type mismatches)
if (issue.code === 'invalid_type' && issue.path.length > 0) {
try {
const valueAtPath = getValueAtPath(modifiedArg, issue.path)
if (valueAtPath !== undefined) {
const convertedValue = convertValue(valueAtPath, issue.expected, issue.received)
if (convertedValue !== undefined) {
setValueAtPath(modifiedArg, issue.path, convertedValue)
hasModification = true
}
}
} catch (pathError) {
console.error('Error processing path in Zod error', pathError)
}
}
}

// If we modified the arg, recursively call parseWithTypeConversion
// This allows newly surfaced nested errors to also get conversion treatment
// Decrement maxDepth to prevent infinite recursion
if (hasModification) {
return await parseWithTypeConversion(schema, modifiedArg, maxDepth - 1)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Root-level conversions never applied

When Zod reports invalid_type at the root (e.g. schema z.number() fed '42'), issue.path is empty. In that case setValueAtPath becomes a no-op, hasModification never flips, and we bubble the original error even though the conversion logic returns 42. This breaks the primary promise of the helper for any primitive or whole-object mismatch.

Make modifiedArg mutable and treat the empty-path branch specially so the converted value actually replaces the argument before recursing.

-            const modifiedArg = typeof arg === 'object' && arg !== null ? cloneDeep(arg) : arg
+            let modifiedArg = typeof arg === 'object' && arg !== null ? cloneDeep(arg) : arg
...
-                            if (convertedValue !== undefined) {
-                                setValueAtPath(modifiedArg, issue.path, convertedValue)
-                                hasModification = true
-                            }
+                            if (convertedValue !== undefined) {
+                                if (issue.path.length === 0) {
+                                    modifiedArg = convertedValue
+                                } else {
+                                    setValueAtPath(modifiedArg, issue.path, convertedValue)
+                                }
+                                hasModification = true
+                            }
🤖 Prompt for AI Agents
In packages/components/src/utils.ts around lines 1784 to 1918, root-level Zod
invalid_type issues (empty issue.path) are never applied because setValueAtPath
expects a non-empty path; detect the empty-path case and assign the converted
value to modifiedArg directly (using the original value when needed), mark
hasModification = true, and continue so the function recurses with the replaced
root value; also ensure you only replace when convertValue returns a defined
value and handle null/undefined safely.

@HenryHengZJ HenryHengZJ merged commit 4417102 into main Oct 31, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants