@@ -893,6 +893,101 @@ public async Task GrokPreservesEndUserIdFromClientOptions()
893893 Assert . Equal ( "kzu" , capturedRequest . User ) ;
894894 }
895895
896+ [ Fact ]
897+ public async Task GrokPassesToolsFromChatOptionsToGrpcRequest ( )
898+ {
899+ GetCompletionsRequest ? capturedRequest = null ;
900+ var client = new Mock < xAI . Protocol . Chat . ChatClient > ( MockBehavior . Strict ) ;
901+ client . Setup ( x => x . GetCompletionAsync ( It . IsAny < GetCompletionsRequest > ( ) , null , null , CancellationToken . None ) )
902+ . Callback < GetCompletionsRequest , Metadata ? , DateTime ? , CancellationToken > ( ( req , _ , _ , _ ) => capturedRequest = req )
903+ . Returns ( CallHelpers . CreateAsyncUnaryCall ( new GetChatCompletionResponse
904+ {
905+ Outputs =
906+ {
907+ new CompletionOutput
908+ {
909+ Message = new CompletionMessage { Content = "Done" }
910+ }
911+ }
912+ } ) ) ;
913+
914+ var grok = new GrokChatClient ( client . Object , "grok-4-1-fast-non-reasoning" ) ;
915+ var options = new ChatOptions
916+ {
917+ Tools =
918+ [
919+ AIFunctionFactory . Create (
920+ ( string city , string unit ) => $ "72°{ unit } in { city } ",
921+ "get_weather" ,
922+ "Gets the current weather for a city" ) ,
923+ ]
924+ } ;
925+
926+ await grok . GetResponseAsync ( [ new ChatMessage ( ChatRole . User , "What's the weather in Seattle?" ) ] , options ) ;
927+
928+ Assert . NotNull ( capturedRequest ) ;
929+ var tool = Assert . Single ( capturedRequest . Tools ) ;
930+ Assert . NotNull ( tool . Function ) ;
931+
932+ Assert . Equal ( "get_weather" , tool . Function . Name ) ;
933+ Assert . Equal ( "Gets the current weather for a city" , tool . Function . Description ) ;
934+
935+ // Parameters must be a non-empty JSON schema reflecting the function signature
936+ Assert . NotEmpty ( tool . Function . Parameters ) ;
937+ var schema = JsonDocument . Parse ( tool . Function . Parameters ) ;
938+ var properties = schema . RootElement . GetProperty ( "properties" ) ;
939+ Assert . True ( properties . TryGetProperty ( "city" , out _ ) , "Schema should contain 'city' parameter" ) ;
940+ Assert . True ( properties . TryGetProperty ( "unit" , out _ ) , "Schema should contain 'unit' parameter" ) ;
941+ }
942+
943+ [ Fact ]
944+ public async Task GrokPassesMultipleToolsFromChatOptionsToGrpcRequest ( )
945+ {
946+ GetCompletionsRequest ? capturedRequest = null ;
947+ var client = new Mock < xAI . Protocol . Chat . ChatClient > ( MockBehavior . Strict ) ;
948+ client . Setup ( x => x . GetCompletionAsync ( It . IsAny < GetCompletionsRequest > ( ) , null , null , CancellationToken . None ) )
949+ . Callback < GetCompletionsRequest , Metadata ? , DateTime ? , CancellationToken > ( ( req , _ , _ , _ ) => capturedRequest = req )
950+ . Returns ( CallHelpers . CreateAsyncUnaryCall ( new GetChatCompletionResponse
951+ {
952+ Outputs =
953+ {
954+ new CompletionOutput
955+ {
956+ Message = new CompletionMessage { Content = "Done" }
957+ }
958+ }
959+ } ) ) ;
960+
961+ var grok = new GrokChatClient ( client . Object , "grok-4-1-fast-non-reasoning" ) ;
962+ var options = new ChatOptions
963+ {
964+ Tools =
965+ [
966+ AIFunctionFactory . Create ( ( ) => DateTimeOffset . UtcNow . ToString ( "O" ) , "get_date" , "Gets today's date" ) ,
967+ AIFunctionFactory . Create ( ( string city ) => $ "72°F in { city } ", "get_weather" , "Gets the weather" ) ,
968+ new HostedWebSearchTool ( ) ,
969+ ]
970+ } ;
971+
972+ await grok . GetResponseAsync ( [ new ChatMessage ( ChatRole . User , "Hello" ) ] , options ) ;
973+
974+ Assert . NotNull ( capturedRequest ) ;
975+ Assert . Equal ( 3 , capturedRequest . Tools . Count ) ;
976+
977+ var getDate = capturedRequest . Tools . FirstOrDefault ( t => t . Function ? . Name == "get_date" ) ;
978+ Assert . NotNull ( getDate ? . Function ) ;
979+ Assert . Equal ( "Gets today's date" , getDate . Function . Description ) ;
980+
981+ var getWeather = capturedRequest . Tools . FirstOrDefault ( t => t . Function ? . Name == "get_weather" ) ;
982+ Assert . NotNull ( getWeather ? . Function ) ;
983+ Assert . Equal ( "Gets the weather" , getWeather . Function . Description ) ;
984+ var schema = JsonDocument . Parse ( getWeather . Function . Parameters ) ;
985+ Assert . True ( schema . RootElement . GetProperty ( "properties" ) . TryGetProperty ( "city" , out _ ) ) ;
986+
987+ var webSearch = capturedRequest . Tools . FirstOrDefault ( t => t . WebSearch != null ) ;
988+ Assert . NotNull ( webSearch ? . WebSearch ) ;
989+ }
990+
896991 class TestGrpcChannel ( CallInvoker invoker ) : ChannelBase ( "test" )
897992 {
898993 public override CallInvoker CreateCallInvoker ( ) => invoker ;
0 commit comments