11import { describe , it , expect , beforeEach } from "vitest" ;
22import { z } from "zod/v3" ;
3+ import { z as z4 } from "zod/v4" ;
34
45import {
56 BaseMessage ,
@@ -17,7 +18,12 @@ import {
1718 type BaseCheckpointSaver ,
1819} from "@langchain/langgraph" ;
1920
20- import { providerStrategy , createAgent , createMiddleware } from "../index.js" ;
21+ import {
22+ providerStrategy ,
23+ createAgent ,
24+ createMiddleware ,
25+ toolStrategy ,
26+ } from "../index.js" ;
2127
2228import {
2329 FakeToolCallingChatModel ,
@@ -878,4 +884,128 @@ describe("createAgent", () => {
878884 const taskInput = JSON . parse ( toolMessage . content as string ) ;
879885 expect ( taskInput . customField ) . toBe ( "test-value" ) ;
880886 } ) ;
887+
888+ // https://github.com/langchain-ai/langchainjs/issues/9299
889+ it ( "supports zod 3/4 schemas in createAgent and middleware" , async ( ) => {
890+ // Create middleware with Zod v3 schemas
891+ const middleware1 = createMiddleware ( {
892+ name : "middleware1" ,
893+ stateSchema : z . object ( {
894+ middleware1Value : z . string ( ) . default ( "v3-default" ) ,
895+ } ) ,
896+ contextSchema : z . object ( {
897+ middleware1Context : z . number ( ) ,
898+ } ) ,
899+ beforeModel : ( _state , { context } ) => {
900+ expect ( context . middleware1Context ) . toBe ( 42 ) ;
901+ return {
902+ middleware1Value : "v3-modified" ,
903+ } ;
904+ } ,
905+ } ) ;
906+
907+ // Create middleware with Zod v4 schemas
908+ const middleware2 = createMiddleware ( {
909+ name : "middleware2" ,
910+ stateSchema : z4 . object ( {
911+ middleware2Value : z4 . string ( ) . default ( "v4-default" ) ,
912+ } ) ,
913+ contextSchema : z4 . object ( {
914+ middleware2Context : z4 . boolean ( ) ,
915+ } ) ,
916+ beforeModel : ( _state , { context } ) => {
917+ expect ( context . middleware2Context ) . toBe ( true ) ;
918+ return {
919+ middleware2Value : "v4-modified" ,
920+ } ;
921+ } ,
922+ } ) ;
923+
924+ // Create a tool with Zod v3 schema
925+ const testTool = tool ( async ( input ) => `Result: ${ input . query } ` , {
926+ name : "test_tool" ,
927+ description : "A test tool" ,
928+ schema : z . object ( {
929+ query : z . string ( ) ,
930+ } ) ,
931+ } ) ;
932+
933+ const responseFormat = toolStrategy (
934+ z . object ( {
935+ result : z . string ( ) ,
936+ score : z . number ( ) ,
937+ } )
938+ ) ;
939+
940+ const expectedStructuredResponse = { result : "success" , score : 95 } ;
941+ const model = new FakeToolCallingChatModel ( {
942+ responses : [
943+ new AIMessage ( {
944+ content : "" ,
945+ tool_calls : [
946+ {
947+ name : "test_tool" ,
948+ id : "tool_1" ,
949+ args : { query : "test" } ,
950+ } ,
951+ ] ,
952+ } ) ,
953+ new AIMessage ( {
954+ content : "" ,
955+ tool_calls : [
956+ {
957+ name : responseFormat [ 0 ] . tool . function . name ,
958+ id : "extract" ,
959+ args : expectedStructuredResponse ,
960+ } ,
961+ ] ,
962+ } ) ,
963+ ] ,
964+ structuredResponse : expectedStructuredResponse ,
965+ } ) ;
966+
967+ const agent = createAgent ( {
968+ model,
969+ tools : [ testTool ] ,
970+ // Use Zod v4 for agent stateSchema
971+ stateSchema : z4 . object ( {
972+ agentCounter : z4 . number ( ) . default ( 0 ) ,
973+ agentName : z4 . string ( ) . optional ( ) ,
974+ } ) ,
975+ responseFormat,
976+ middleware : [ middleware1 , middleware2 ] ,
977+ } ) ;
978+
979+ const result = await agent . invoke (
980+ {
981+ messages : [ new HumanMessage ( "Test mixed schemas" ) ] ,
982+ agentCounter : 1 ,
983+ agentName : "test-agent" ,
984+ } ,
985+ {
986+ context : {
987+ middleware1Context : 42 ,
988+ middleware2Context : true ,
989+ } ,
990+ }
991+ ) ;
992+
993+ // Verify agent state schema (Zod v3)
994+ expect ( result . agentCounter ) . toBe ( 1 ) ;
995+ expect ( result . agentName ) . toBe ( "test-agent" ) ;
996+
997+ // Verify middleware1 state (Zod v3)
998+ expect ( result . middleware1Value ) . toBe ( "v3-modified" ) ;
999+
1000+ // Verify middleware2 state (Zod v4)
1001+ expect ( result . middleware2Value ) . toBe ( "v4-modified" ) ;
1002+
1003+ // Verify structured response (Zod v4)
1004+ expect ( result . structuredResponse ) . toEqual ( expectedStructuredResponse ) ;
1005+ expect ( result . structuredResponse . result ) . toBe ( "success" ) ;
1006+ expect ( result . structuredResponse . score ) . toBe ( 95 ) ;
1007+
1008+ // Verify messages were processed correctly
1009+ expect ( result . messages . length ) . toBeGreaterThan ( 0 ) ;
1010+ } ) ;
8811011} ) ;
0 commit comments