-
-
Notifications
You must be signed in to change notification settings - Fork 23.5k
Bugfix/Zod Parsing #5399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bugfix/Zod Parsing #5399
Conversation
…a with automatic type conversion for common type mismatches
WalkthroughA new utility function Changes
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this 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
📒 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
parseWithTypeConversionimport 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
parseWithTypeConversionadds 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, andcloneDeepis properly imported from lodash in utils.ts.packages/components/nodes/tools/AgentAsTool/AgentAsTool.ts (2)
7-13: LGTM: Import added correctly.The
parseWithTypeConversionutility 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
parseWithTypeConversionadds 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.
There was a problem hiding this 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
📒 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)
| // 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) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
implement parseWithTypeConversion - parse a value against a Zod schema with automatic type conversion for common type mismatches
Summary by CodeRabbit
New Features
Improvements