datarobot-genai 0.2.26__tar.gz → 0.2.27__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/PKG-INFO +1 -1
  2. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/pyproject.toml +1 -1
  3. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/clients/gdrive.py +127 -0
  4. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/gdrive/tools.py +95 -2
  5. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/.gitignore +0 -0
  6. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/AUTHORS +0 -0
  7. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/LICENSE +0 -0
  8. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/README.md +0 -0
  9. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/__init__.py +0 -0
  10. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/__init__.py +0 -0
  11. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/agents/__init__.py +0 -0
  12. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/agents/base.py +0 -0
  13. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/chat/__init__.py +0 -0
  14. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/chat/auth.py +0 -0
  15. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/chat/client.py +0 -0
  16. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/chat/responses.py +0 -0
  17. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/cli/__init__.py +0 -0
  18. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/cli/agent_environment.py +0 -0
  19. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/cli/agent_kernel.py +0 -0
  20. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/custom_model.py +0 -0
  21. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/mcp/__init__.py +0 -0
  22. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/mcp/common.py +0 -0
  23. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/telemetry_agent.py +0 -0
  24. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/utils/__init__.py +0 -0
  25. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/utils/auth.py +0 -0
  26. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/core/utils/urls.py +0 -0
  27. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/crewai/__init__.py +0 -0
  28. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/crewai/agent.py +0 -0
  29. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/crewai/base.py +0 -0
  30. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/crewai/events.py +0 -0
  31. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/crewai/mcp.py +0 -0
  32. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/__init__.py +0 -0
  33. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/__init__.py +0 -0
  34. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/auth.py +0 -0
  35. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/clients.py +0 -0
  36. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/config.py +0 -0
  37. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/config_utils.py +0 -0
  38. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/constants.py +0 -0
  39. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/credentials.py +0 -0
  40. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dr_mcp_server.py +0 -0
  41. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dr_mcp_server_logo.py +0 -0
  42. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_prompts/__init__.py +0 -0
  43. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_prompts/controllers.py +0 -0
  44. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_prompts/dr_lib.py +0 -0
  45. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_prompts/register.py +0 -0
  46. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_prompts/utils.py +0 -0
  47. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/__init__.py +0 -0
  48. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/__init__.py +0 -0
  49. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/__init__.py +0 -0
  50. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/base.py +0 -0
  51. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/default.py +0 -0
  52. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/drum.py +0 -0
  53. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/config.py +0 -0
  54. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/controllers.py +0 -0
  55. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/metadata.py +0 -0
  56. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/register.py +0 -0
  57. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_agentic_fallback_schema.json +0 -0
  58. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_prediction_fallback_schema.json +0 -0
  59. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/register.py +0 -0
  60. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/dynamic_tools/schema.py +0 -0
  61. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/exceptions.py +0 -0
  62. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/logging.py +0 -0
  63. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/mcp_instance.py +0 -0
  64. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/memory_management/__init__.py +0 -0
  65. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/memory_management/manager.py +0 -0
  66. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/memory_management/memory_tools.py +0 -0
  67. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/routes.py +0 -0
  68. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/routes_utils.py +0 -0
  69. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/server_life_cycle.py +0 -0
  70. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/telemetry.py +0 -0
  71. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/tool_config.py +0 -0
  72. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/tool_filter.py +0 -0
  73. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/core/utils.py +0 -0
  74. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/server.py +0 -0
  75. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/__init__.py +0 -0
  76. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/elicitation_test_tool.py +0 -0
  77. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/integration_mcp_server.py +0 -0
  78. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/mcp_utils_ete.py +0 -0
  79. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/mcp_utils_integration.py +0 -0
  80. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/openai_llm_mcp_client.py +0 -0
  81. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/test_interactive.py +0 -0
  82. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/tool_base_ete.py +0 -0
  83. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/test_utils/utils.py +0 -0
  84. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/__init__.py +0 -0
  85. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/clients/__init__.py +0 -0
  86. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/clients/atlassian.py +0 -0
  87. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/clients/confluence.py +0 -0
  88. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/clients/jira.py +0 -0
  89. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/clients/s3.py +0 -0
  90. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/confluence/__init__.py +0 -0
  91. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/confluence/tools.py +0 -0
  92. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/gdrive/__init__.py +0 -0
  93. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/jira/__init__.py +0 -0
  94. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/jira/tools.py +0 -0
  95. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/__init__.py +0 -0
  96. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/data.py +0 -0
  97. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/deployment.py +0 -0
  98. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/deployment_info.py +0 -0
  99. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/model.py +0 -0
  100. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/predict.py +0 -0
  101. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/predict_realtime.py +0 -0
  102. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/project.py +0 -0
  103. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/drmcp/tools/predictive/training.py +0 -0
  104. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/langgraph/__init__.py +0 -0
  105. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/langgraph/agent.py +0 -0
  106. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/langgraph/mcp.py +0 -0
  107. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/llama_index/__init__.py +0 -0
  108. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/llama_index/agent.py +0 -0
  109. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/llama_index/base.py +0 -0
  110. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/llama_index/mcp.py +0 -0
  111. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/nat/__init__.py +0 -0
  112. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/nat/agent.py +0 -0
  113. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/nat/datarobot_auth_provider.py +0 -0
  114. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/nat/datarobot_llm_clients.py +0 -0
  115. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/nat/datarobot_llm_providers.py +0 -0
  116. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/nat/datarobot_mcp_client.py +0 -0
  117. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/nat/helpers.py +0 -0
  118. {datarobot_genai-0.2.26 → datarobot_genai-0.2.27}/src/datarobot_genai/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datarobot-genai
3
- Version: 0.2.26
3
+ Version: 0.2.27
4
4
  Summary: Generic helpers for GenAI
5
5
  Project-URL: Homepage, https://github.com/datarobot-oss/datarobot-genai
6
6
  Author: DataRobot, Inc.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datarobot-genai"
7
- version = "0.2.26"
7
+ version = "0.2.27"
8
8
  description = "Generic helpers for GenAI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10, <3.13"
@@ -15,7 +15,9 @@
15
15
  """Google Drive API Client and utilities for OAuth."""
16
16
 
17
17
  import io
18
+ import json
18
19
  import logging
20
+ import uuid
19
21
  from typing import Annotated
20
22
  from typing import Any
21
23
 
@@ -45,6 +47,12 @@ GOOGLE_WORKSPACE_EXPORT_MIMES: dict[str, str] = {
45
47
  "application/vnd.google-apps.presentation": "text/plain",
46
48
  }
47
49
 
50
+ # MIME type mappings for content conversion during upload to Google Workspace formats
51
+ UPLOAD_CONTENT_TYPES: dict[str, str] = {
52
+ "application/vnd.google-apps.document": "text/plain",
53
+ "application/vnd.google-apps.spreadsheet": "text/csv",
54
+ }
55
+
48
56
  BINARY_MIME_PREFIXES = (
49
57
  "image/",
50
58
  "audio/",
@@ -599,6 +607,125 @@ class GoogleDriveClient:
599
607
  web_view_link=file_metadata.web_view_link,
600
608
  )
601
609
 
610
+ async def create_file(
611
+ self,
612
+ name: str,
613
+ mime_type: str,
614
+ parent_id: str | None = None,
615
+ initial_content: str | None = None,
616
+ ) -> GoogleDriveFile:
617
+ """Create a new file or folder in Google Drive.
618
+
619
+ Creates a new file with the specified name and MIME type. Optionally places
620
+ it in a specific folder and populates it with initial content.
621
+
622
+ For Google Workspace files (Docs, Sheets), the Drive API automatically
623
+ converts plain text content to the appropriate format.
624
+
625
+ Args:
626
+ name: The name for the new file or folder.
627
+ mime_type: The MIME type of the file (e.g., 'text/plain',
628
+ 'application/vnd.google-apps.document',
629
+ 'application/vnd.google-apps.folder').
630
+ parent_id: Optional ID of the parent folder. If not specified,
631
+ the file is created in the root of the user's Drive.
632
+ initial_content: Optional text content to populate the file.
633
+ Ignored for folders.
634
+
635
+ Returns
636
+ -------
637
+ GoogleDriveFile with the created file's metadata.
638
+
639
+ Raises
640
+ ------
641
+ GoogleDriveError: If file creation fails (permission denied,
642
+ parent not found, rate limited, etc.).
643
+ """
644
+ metadata: dict[str, Any] = {
645
+ "name": name,
646
+ "mimeType": mime_type,
647
+ }
648
+ if parent_id:
649
+ metadata["parents"] = [parent_id]
650
+
651
+ if mime_type == GOOGLE_DRIVE_FOLDER_MIME or not initial_content:
652
+ response = await self._client.post(
653
+ "/",
654
+ json=metadata,
655
+ params={"fields": SUPPORTED_FIELDS_STR, "supportsAllDrives": "true"},
656
+ )
657
+ else:
658
+ response = await self._create_file_with_content(
659
+ metadata=metadata,
660
+ content=initial_content,
661
+ target_mime_type=mime_type,
662
+ )
663
+
664
+ if response.status_code == 404:
665
+ raise GoogleDriveError(
666
+ f"Parent folder with ID '{parent_id}' not found."
667
+ if parent_id
668
+ else "Resource not found."
669
+ )
670
+ if response.status_code == 403:
671
+ raise GoogleDriveError(
672
+ "Permission denied: you don't have permission to create files in this location."
673
+ )
674
+ if response.status_code == 400:
675
+ raise GoogleDriveError(
676
+ f"Bad request: invalid parameters for file creation. "
677
+ f"Check that the MIME type '{mime_type}' is valid."
678
+ )
679
+ if response.status_code == 429:
680
+ raise GoogleDriveError("Rate limit exceeded. Please try again later.")
681
+
682
+ response.raise_for_status()
683
+ return GoogleDriveFile.from_api_response(response.json())
684
+
685
+ async def _create_file_with_content(
686
+ self,
687
+ metadata: dict[str, Any],
688
+ content: str,
689
+ target_mime_type: str,
690
+ ) -> httpx.Response:
691
+ """Create a file with content using multipart upload.
692
+
693
+ Args:
694
+ metadata: File metadata dictionary.
695
+ content: Text content for the file.
696
+ target_mime_type: The target MIME type for the file.
697
+
698
+ Returns
699
+ -------
700
+ The HTTP response from the upload.
701
+ """
702
+ content_type = UPLOAD_CONTENT_TYPES.get(target_mime_type, "text/plain")
703
+ boundary = f"===gdrive_boundary_{uuid.uuid4().hex}==="
704
+ body_parts = [
705
+ f"--{boundary}",
706
+ "Content-Type: application/json; charset=UTF-8",
707
+ "",
708
+ json.dumps(metadata),
709
+ f"--{boundary}",
710
+ f"Content-Type: {content_type}",
711
+ "",
712
+ content,
713
+ f"--{boundary}--",
714
+ ]
715
+ body = "\r\n".join(body_parts)
716
+
717
+ upload_url = "https://www.googleapis.com/upload/drive/v3/files"
718
+ return await self._client.post(
719
+ upload_url,
720
+ content=body.encode("utf-8"),
721
+ params={
722
+ "uploadType": "multipart",
723
+ "fields": SUPPORTED_FIELDS_STR,
724
+ "supportsAllDrives": "true",
725
+ },
726
+ headers={"Content-Type": f"multipart/related; boundary={boundary}"},
727
+ )
728
+
602
729
  async def __aenter__(self) -> "GoogleDriveClient":
603
730
  """Async context manager entry."""
604
731
  return self
@@ -21,6 +21,7 @@ from fastmcp.exceptions import ToolError
21
21
  from fastmcp.tools.tool import ToolResult
22
22
 
23
23
  from datarobot_genai.drmcp.core.mcp_instance import dr_mcp_tool
24
+ from datarobot_genai.drmcp.tools.clients.gdrive import GOOGLE_DRIVE_FOLDER_MIME
24
25
  from datarobot_genai.drmcp.tools.clients.gdrive import LIMIT
25
26
  from datarobot_genai.drmcp.tools.clients.gdrive import MAX_PAGE_SIZE
26
27
  from datarobot_genai.drmcp.tools.clients.gdrive import SUPPORTED_FIELDS
@@ -60,7 +61,7 @@ async def gdrive_find_contents(
60
61
  "Optional list of metadata fields to include. Ex. id, name, mimeType. "
61
62
  f"Default = {SUPPORTED_FIELDS_STR}",
62
63
  ] = None,
63
- ) -> ToolResult | ToolError:
64
+ ) -> ToolResult:
64
65
  """
65
66
  Search or list files in the user's Google Drive with pagination and filtering support.
66
67
  Use this tool to discover file names and IDs for use with other tools.
@@ -121,7 +122,7 @@ async def gdrive_read_content(
121
122
  "(e.g., 'text/markdown' for Docs, 'text/csv' for Sheets). "
122
123
  "If not specified, uses sensible defaults. Has no effect on regular files.",
123
124
  ] = None,
124
- ) -> ToolResult | ToolError:
125
+ ) -> ToolResult:
125
126
  """
126
127
  Retrieve the content of a specific file by its ID. Google Workspace files are
127
128
  automatically exported to LLM-readable formats (Push-Down).
@@ -175,3 +176,95 @@ async def gdrive_read_content(
175
176
  ),
176
177
  structured_content=file_content.as_flat_dict(),
177
178
  )
179
+
180
+
181
+ @dr_mcp_tool(tags={"google", "gdrive", "create", "write", "file", "folder"}, enabled=False)
182
+ async def gdrive_create_file(
183
+ *,
184
+ name: Annotated[str, "The name for the new file or folder."],
185
+ mime_type: Annotated[
186
+ str,
187
+ "The MIME type of the file (e.g., 'text/plain', "
188
+ "'application/vnd.google-apps.document', 'application/vnd.google-apps.folder').",
189
+ ],
190
+ parent_id: Annotated[
191
+ str | None, "The ID of the parent folder where the file should be created."
192
+ ] = None,
193
+ initial_content: Annotated[
194
+ str | None, "Text content to populate the new file, if applicable."
195
+ ] = None,
196
+ ) -> ToolResult:
197
+ """
198
+ Create a new file or folder in Google Drive.
199
+
200
+ This tool is essential for an AI agent to generate new output (like reports or
201
+ documentation) directly into the Drive structure.
202
+
203
+ Usage:
204
+ - Create empty file: gdrive_create_file(name="report.txt", mime_type="text/plain")
205
+ - Create Google Doc: gdrive_create_file(
206
+ name="My Report",
207
+ mime_type="application/vnd.google-apps.document",
208
+ initial_content="# Report Title"
209
+ )
210
+ - Create folder: gdrive_create_file(
211
+ name="Reports",
212
+ mime_type="application/vnd.google-apps.folder"
213
+ )
214
+ - Create in subfolder: gdrive_create_file(
215
+ name="file.txt",
216
+ mime_type="text/plain",
217
+ parent_id="folder_id_here",
218
+ initial_content="File content"
219
+ )
220
+
221
+ Supported MIME types:
222
+ - text/plain: Plain text file
223
+ - application/vnd.google-apps.document: Google Doc (content auto-converted)
224
+ - application/vnd.google-apps.spreadsheet: Google Sheet (CSV content works best)
225
+ - application/vnd.google-apps.folder: Folder (initial_content is ignored)
226
+
227
+ Note: For Google Workspace files, the Drive API automatically converts plain text
228
+ content to the appropriate format.
229
+ """
230
+ if not name or not name.strip():
231
+ raise ToolError("Argument validation error: 'name' cannot be empty.")
232
+
233
+ if not mime_type or not mime_type.strip():
234
+ raise ToolError("Argument validation error: 'mime_type' cannot be empty.")
235
+
236
+ access_token = await get_gdrive_access_token()
237
+ if isinstance(access_token, ToolError):
238
+ raise access_token
239
+
240
+ try:
241
+ async with GoogleDriveClient(access_token) as client:
242
+ created_file = await client.create_file(
243
+ name=name,
244
+ mime_type=mime_type,
245
+ parent_id=parent_id,
246
+ initial_content=initial_content,
247
+ )
248
+ except GoogleDriveError as e:
249
+ logger.error(f"Google Drive error creating file: {e}")
250
+ raise ToolError(str(e))
251
+ except Exception as e:
252
+ logger.error(f"Unexpected error creating Google Drive file: {e}")
253
+ raise ToolError(f"An unexpected error occurred while creating Google Drive file: {str(e)}")
254
+
255
+ # Build response message
256
+ file_type = "folder" if mime_type == GOOGLE_DRIVE_FOLDER_MIME else "file"
257
+ content_info = ""
258
+ if initial_content and mime_type != GOOGLE_DRIVE_FOLDER_MIME:
259
+ content_info = " with initial content"
260
+
261
+ return ToolResult(
262
+ content=f"Successfully created {file_type} '{created_file.name}'{content_info}.",
263
+ structured_content={
264
+ "id": created_file.id,
265
+ "name": created_file.name,
266
+ "mimeType": created_file.mime_type,
267
+ "webViewLink": created_file.web_view_link,
268
+ "createdTime": created_file.created_time,
269
+ },
270
+ )