fast-agent-mcp 0.4.7__py3-none-any.whl

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 (261) hide show
  1. fast_agent/__init__.py +183 -0
  2. fast_agent/acp/__init__.py +19 -0
  3. fast_agent/acp/acp_aware_mixin.py +304 -0
  4. fast_agent/acp/acp_context.py +437 -0
  5. fast_agent/acp/content_conversion.py +136 -0
  6. fast_agent/acp/filesystem_runtime.py +427 -0
  7. fast_agent/acp/permission_store.py +269 -0
  8. fast_agent/acp/server/__init__.py +5 -0
  9. fast_agent/acp/server/agent_acp_server.py +1472 -0
  10. fast_agent/acp/slash_commands.py +1050 -0
  11. fast_agent/acp/terminal_runtime.py +408 -0
  12. fast_agent/acp/tool_permission_adapter.py +125 -0
  13. fast_agent/acp/tool_permissions.py +474 -0
  14. fast_agent/acp/tool_progress.py +814 -0
  15. fast_agent/agents/__init__.py +85 -0
  16. fast_agent/agents/agent_types.py +64 -0
  17. fast_agent/agents/llm_agent.py +350 -0
  18. fast_agent/agents/llm_decorator.py +1139 -0
  19. fast_agent/agents/mcp_agent.py +1337 -0
  20. fast_agent/agents/tool_agent.py +271 -0
  21. fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
  22. fast_agent/agents/workflow/chain_agent.py +212 -0
  23. fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
  24. fast_agent/agents/workflow/iterative_planner.py +652 -0
  25. fast_agent/agents/workflow/maker_agent.py +379 -0
  26. fast_agent/agents/workflow/orchestrator_models.py +218 -0
  27. fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
  28. fast_agent/agents/workflow/parallel_agent.py +250 -0
  29. fast_agent/agents/workflow/router_agent.py +353 -0
  30. fast_agent/cli/__init__.py +0 -0
  31. fast_agent/cli/__main__.py +73 -0
  32. fast_agent/cli/commands/acp.py +159 -0
  33. fast_agent/cli/commands/auth.py +404 -0
  34. fast_agent/cli/commands/check_config.py +783 -0
  35. fast_agent/cli/commands/go.py +514 -0
  36. fast_agent/cli/commands/quickstart.py +557 -0
  37. fast_agent/cli/commands/serve.py +143 -0
  38. fast_agent/cli/commands/server_helpers.py +114 -0
  39. fast_agent/cli/commands/setup.py +174 -0
  40. fast_agent/cli/commands/url_parser.py +190 -0
  41. fast_agent/cli/constants.py +40 -0
  42. fast_agent/cli/main.py +115 -0
  43. fast_agent/cli/terminal.py +24 -0
  44. fast_agent/config.py +798 -0
  45. fast_agent/constants.py +41 -0
  46. fast_agent/context.py +279 -0
  47. fast_agent/context_dependent.py +50 -0
  48. fast_agent/core/__init__.py +92 -0
  49. fast_agent/core/agent_app.py +448 -0
  50. fast_agent/core/core_app.py +137 -0
  51. fast_agent/core/direct_decorators.py +784 -0
  52. fast_agent/core/direct_factory.py +620 -0
  53. fast_agent/core/error_handling.py +27 -0
  54. fast_agent/core/exceptions.py +90 -0
  55. fast_agent/core/executor/__init__.py +0 -0
  56. fast_agent/core/executor/executor.py +280 -0
  57. fast_agent/core/executor/task_registry.py +32 -0
  58. fast_agent/core/executor/workflow_signal.py +324 -0
  59. fast_agent/core/fastagent.py +1186 -0
  60. fast_agent/core/logging/__init__.py +5 -0
  61. fast_agent/core/logging/events.py +138 -0
  62. fast_agent/core/logging/json_serializer.py +164 -0
  63. fast_agent/core/logging/listeners.py +309 -0
  64. fast_agent/core/logging/logger.py +278 -0
  65. fast_agent/core/logging/transport.py +481 -0
  66. fast_agent/core/prompt.py +9 -0
  67. fast_agent/core/prompt_templates.py +183 -0
  68. fast_agent/core/validation.py +326 -0
  69. fast_agent/event_progress.py +62 -0
  70. fast_agent/history/history_exporter.py +49 -0
  71. fast_agent/human_input/__init__.py +47 -0
  72. fast_agent/human_input/elicitation_handler.py +123 -0
  73. fast_agent/human_input/elicitation_state.py +33 -0
  74. fast_agent/human_input/form_elements.py +59 -0
  75. fast_agent/human_input/form_fields.py +256 -0
  76. fast_agent/human_input/simple_form.py +113 -0
  77. fast_agent/human_input/types.py +40 -0
  78. fast_agent/interfaces.py +310 -0
  79. fast_agent/llm/__init__.py +9 -0
  80. fast_agent/llm/cancellation.py +22 -0
  81. fast_agent/llm/fastagent_llm.py +931 -0
  82. fast_agent/llm/internal/passthrough.py +161 -0
  83. fast_agent/llm/internal/playback.py +129 -0
  84. fast_agent/llm/internal/silent.py +41 -0
  85. fast_agent/llm/internal/slow.py +38 -0
  86. fast_agent/llm/memory.py +275 -0
  87. fast_agent/llm/model_database.py +490 -0
  88. fast_agent/llm/model_factory.py +388 -0
  89. fast_agent/llm/model_info.py +102 -0
  90. fast_agent/llm/prompt_utils.py +155 -0
  91. fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
  92. fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
  93. fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
  94. fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
  95. fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
  96. fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
  97. fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
  98. fast_agent/llm/provider/google/google_converter.py +466 -0
  99. fast_agent/llm/provider/google/llm_google_native.py +681 -0
  100. fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
  101. fast_agent/llm/provider/openai/llm_azure.py +143 -0
  102. fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
  103. fast_agent/llm/provider/openai/llm_generic.py +35 -0
  104. fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
  105. fast_agent/llm/provider/openai/llm_groq.py +42 -0
  106. fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
  107. fast_agent/llm/provider/openai/llm_openai.py +1195 -0
  108. fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
  109. fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
  110. fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
  111. fast_agent/llm/provider/openai/llm_xai.py +38 -0
  112. fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
  113. fast_agent/llm/provider/openai/openai_multipart.py +169 -0
  114. fast_agent/llm/provider/openai/openai_utils.py +67 -0
  115. fast_agent/llm/provider/openai/responses.py +133 -0
  116. fast_agent/llm/provider_key_manager.py +139 -0
  117. fast_agent/llm/provider_types.py +34 -0
  118. fast_agent/llm/request_params.py +61 -0
  119. fast_agent/llm/sampling_converter.py +98 -0
  120. fast_agent/llm/stream_types.py +9 -0
  121. fast_agent/llm/usage_tracking.py +445 -0
  122. fast_agent/mcp/__init__.py +56 -0
  123. fast_agent/mcp/common.py +26 -0
  124. fast_agent/mcp/elicitation_factory.py +84 -0
  125. fast_agent/mcp/elicitation_handlers.py +164 -0
  126. fast_agent/mcp/gen_client.py +83 -0
  127. fast_agent/mcp/helpers/__init__.py +36 -0
  128. fast_agent/mcp/helpers/content_helpers.py +352 -0
  129. fast_agent/mcp/helpers/server_config_helpers.py +25 -0
  130. fast_agent/mcp/hf_auth.py +147 -0
  131. fast_agent/mcp/interfaces.py +92 -0
  132. fast_agent/mcp/logger_textio.py +108 -0
  133. fast_agent/mcp/mcp_agent_client_session.py +411 -0
  134. fast_agent/mcp/mcp_aggregator.py +2175 -0
  135. fast_agent/mcp/mcp_connection_manager.py +723 -0
  136. fast_agent/mcp/mcp_content.py +262 -0
  137. fast_agent/mcp/mime_utils.py +108 -0
  138. fast_agent/mcp/oauth_client.py +509 -0
  139. fast_agent/mcp/prompt.py +159 -0
  140. fast_agent/mcp/prompt_message_extended.py +155 -0
  141. fast_agent/mcp/prompt_render.py +84 -0
  142. fast_agent/mcp/prompt_serialization.py +580 -0
  143. fast_agent/mcp/prompts/__init__.py +0 -0
  144. fast_agent/mcp/prompts/__main__.py +7 -0
  145. fast_agent/mcp/prompts/prompt_constants.py +18 -0
  146. fast_agent/mcp/prompts/prompt_helpers.py +238 -0
  147. fast_agent/mcp/prompts/prompt_load.py +186 -0
  148. fast_agent/mcp/prompts/prompt_server.py +552 -0
  149. fast_agent/mcp/prompts/prompt_template.py +438 -0
  150. fast_agent/mcp/resource_utils.py +215 -0
  151. fast_agent/mcp/sampling.py +200 -0
  152. fast_agent/mcp/server/__init__.py +4 -0
  153. fast_agent/mcp/server/agent_server.py +613 -0
  154. fast_agent/mcp/skybridge.py +44 -0
  155. fast_agent/mcp/sse_tracking.py +287 -0
  156. fast_agent/mcp/stdio_tracking_simple.py +59 -0
  157. fast_agent/mcp/streamable_http_tracking.py +309 -0
  158. fast_agent/mcp/tool_execution_handler.py +137 -0
  159. fast_agent/mcp/tool_permission_handler.py +88 -0
  160. fast_agent/mcp/transport_tracking.py +634 -0
  161. fast_agent/mcp/types.py +24 -0
  162. fast_agent/mcp/ui_agent.py +48 -0
  163. fast_agent/mcp/ui_mixin.py +209 -0
  164. fast_agent/mcp_server_registry.py +89 -0
  165. fast_agent/py.typed +0 -0
  166. fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
  167. fast_agent/resources/examples/data-analysis/analysis.py +68 -0
  168. fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
  169. fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
  170. fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
  171. fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
  172. fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
  173. fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
  174. fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
  175. fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
  176. fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
  177. fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
  178. fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
  179. fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
  180. fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
  181. fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
  182. fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
  183. fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
  184. fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
  185. fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
  186. fast_agent/resources/examples/researcher/researcher.py +36 -0
  187. fast_agent/resources/examples/tensorzero/.env.sample +2 -0
  188. fast_agent/resources/examples/tensorzero/Makefile +31 -0
  189. fast_agent/resources/examples/tensorzero/README.md +56 -0
  190. fast_agent/resources/examples/tensorzero/agent.py +35 -0
  191. fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
  192. fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
  193. fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
  194. fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
  195. fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
  196. fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
  197. fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
  198. fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
  199. fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
  200. fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
  201. fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
  202. fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
  203. fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
  204. fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
  205. fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
  206. fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
  207. fast_agent/resources/examples/workflows/chaining.py +37 -0
  208. fast_agent/resources/examples/workflows/evaluator.py +77 -0
  209. fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
  210. fast_agent/resources/examples/workflows/graded_report.md +89 -0
  211. fast_agent/resources/examples/workflows/human_input.py +28 -0
  212. fast_agent/resources/examples/workflows/maker.py +156 -0
  213. fast_agent/resources/examples/workflows/orchestrator.py +70 -0
  214. fast_agent/resources/examples/workflows/parallel.py +56 -0
  215. fast_agent/resources/examples/workflows/router.py +69 -0
  216. fast_agent/resources/examples/workflows/short_story.md +13 -0
  217. fast_agent/resources/examples/workflows/short_story.txt +19 -0
  218. fast_agent/resources/setup/.gitignore +30 -0
  219. fast_agent/resources/setup/agent.py +28 -0
  220. fast_agent/resources/setup/fastagent.config.yaml +65 -0
  221. fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
  222. fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
  223. fast_agent/skills/__init__.py +9 -0
  224. fast_agent/skills/registry.py +235 -0
  225. fast_agent/tools/elicitation.py +369 -0
  226. fast_agent/tools/shell_runtime.py +402 -0
  227. fast_agent/types/__init__.py +59 -0
  228. fast_agent/types/conversation_summary.py +294 -0
  229. fast_agent/types/llm_stop_reason.py +78 -0
  230. fast_agent/types/message_search.py +249 -0
  231. fast_agent/ui/__init__.py +38 -0
  232. fast_agent/ui/console.py +59 -0
  233. fast_agent/ui/console_display.py +1080 -0
  234. fast_agent/ui/elicitation_form.py +946 -0
  235. fast_agent/ui/elicitation_style.py +59 -0
  236. fast_agent/ui/enhanced_prompt.py +1400 -0
  237. fast_agent/ui/history_display.py +734 -0
  238. fast_agent/ui/interactive_prompt.py +1199 -0
  239. fast_agent/ui/markdown_helpers.py +104 -0
  240. fast_agent/ui/markdown_truncator.py +1004 -0
  241. fast_agent/ui/mcp_display.py +857 -0
  242. fast_agent/ui/mcp_ui_utils.py +235 -0
  243. fast_agent/ui/mermaid_utils.py +169 -0
  244. fast_agent/ui/message_primitives.py +50 -0
  245. fast_agent/ui/notification_tracker.py +205 -0
  246. fast_agent/ui/plain_text_truncator.py +68 -0
  247. fast_agent/ui/progress_display.py +10 -0
  248. fast_agent/ui/rich_progress.py +195 -0
  249. fast_agent/ui/streaming.py +774 -0
  250. fast_agent/ui/streaming_buffer.py +449 -0
  251. fast_agent/ui/tool_display.py +422 -0
  252. fast_agent/ui/usage_display.py +204 -0
  253. fast_agent/utils/__init__.py +5 -0
  254. fast_agent/utils/reasoning_stream_parser.py +77 -0
  255. fast_agent/utils/time.py +22 -0
  256. fast_agent/workflow_telemetry.py +261 -0
  257. fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
  258. fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
  259. fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
  260. fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
  261. fast_agent_mcp-0.4.7.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,84 @@
1
+ from typing import Any
2
+
3
+ from fast_agent.types import PromptMessageExtended
4
+
5
+ # Bedrock message format types
6
+ BedrockMessageParam = dict[str, Any]
7
+
8
+
9
+ class BedrockConverter:
10
+ """Converts MCP message types to Bedrock API format."""
11
+
12
+ @staticmethod
13
+ def convert_to_bedrock(multipart_msg: PromptMessageExtended) -> BedrockMessageParam:
14
+ """
15
+ Convert a PromptMessageExtended message to Bedrock API format.
16
+
17
+ This is a wrapper around the instance method _convert_multipart_to_bedrock_message
18
+ to provide a static interface similar to AnthropicConverter.
19
+
20
+ Args:
21
+ multipart_msg: The PromptMessageExtended message to convert
22
+
23
+ Returns:
24
+ A Bedrock API message parameter dictionary
25
+ """
26
+ # Simple conversion without needing BedrockLLM instance
27
+ bedrock_msg = {"role": multipart_msg.role, "content": []}
28
+
29
+ # Handle tool results first (if present)
30
+ if multipart_msg.tool_results:
31
+ import json
32
+
33
+ from mcp.types import TextContent
34
+
35
+ # Check if any tool ID indicates system prompt format
36
+ has_system_prompt_tools = any(
37
+ tool_id.startswith("system_prompt_") for tool_id in multipart_msg.tool_results.keys()
38
+ )
39
+
40
+ if has_system_prompt_tools:
41
+ # For system prompt models: format as human-readable text
42
+ tool_result_parts = []
43
+ for tool_id, tool_result in multipart_msg.tool_results.items():
44
+ result_text = "".join(
45
+ part.text for part in tool_result.content if isinstance(part, TextContent)
46
+ )
47
+ result_payload = {
48
+ "tool_name": tool_id,
49
+ "status": "error" if tool_result.isError else "success",
50
+ "result": result_text,
51
+ }
52
+ tool_result_parts.append(json.dumps(result_payload))
53
+
54
+ if tool_result_parts:
55
+ full_result_text = f"Tool Results:\n{', '.join(tool_result_parts)}"
56
+ bedrock_msg["content"].append({"type": "text", "text": full_result_text})
57
+ else:
58
+ # For Nova/Anthropic models: use structured tool_result format
59
+ for tool_id, tool_result in multipart_msg.tool_results.items():
60
+ result_content_blocks = []
61
+ if tool_result.content:
62
+ for part in tool_result.content:
63
+ if isinstance(part, TextContent):
64
+ result_content_blocks.append({"text": part.text})
65
+
66
+ if not result_content_blocks:
67
+ result_content_blocks.append({"text": "[No content in tool result]"})
68
+
69
+ bedrock_msg["content"].append(
70
+ {
71
+ "type": "tool_result",
72
+ "tool_use_id": tool_id,
73
+ "content": result_content_blocks,
74
+ "status": "error" if tool_result.isError else "success",
75
+ }
76
+ )
77
+
78
+ # Handle regular content
79
+ from mcp.types import TextContent
80
+ for content_item in multipart_msg.content:
81
+ if isinstance(content_item, TextContent):
82
+ bedrock_msg["content"].append({"type": "text", "text": content_item.text})
83
+
84
+ return bedrock_msg
@@ -0,0 +1,466 @@
1
+ import base64
2
+ from typing import Any
3
+
4
+ # Import necessary types from google.genai
5
+ from google.genai import types
6
+ from mcp import Tool
7
+ from mcp.types import (
8
+ BlobResourceContents,
9
+ CallToolRequest,
10
+ CallToolRequestParams,
11
+ CallToolResult,
12
+ ContentBlock,
13
+ EmbeddedResource,
14
+ ImageContent,
15
+ ResourceLink,
16
+ TextContent,
17
+ TextResourceContents,
18
+ )
19
+ from pydantic import AnyUrl
20
+
21
+ from fast_agent.mcp.helpers.content_helpers import (
22
+ get_image_data,
23
+ get_text,
24
+ is_image_content,
25
+ is_resource_content,
26
+ is_resource_link,
27
+ is_text_content,
28
+ )
29
+ from fast_agent.types import PromptMessageExtended, RequestParams
30
+
31
+
32
+ class GoogleConverter:
33
+ """
34
+ Converts between fast-agent and google.genai data structures.
35
+ """
36
+
37
+ def _clean_schema_for_google(self, schema: dict[str, Any]) -> dict[str, Any]:
38
+ """
39
+ Recursively removes unsupported JSON schema keywords for google.genai.types.Schema.
40
+ Specifically removes 'additionalProperties', '$schema', 'exclusiveMaximum', and 'exclusiveMinimum'.
41
+ Also resolves $ref references and inlines $defs.
42
+ """
43
+ # First, resolve any $ref references in the schema
44
+ schema = self._resolve_refs(schema, schema)
45
+
46
+ cleaned_schema = {}
47
+ unsupported_keys = {
48
+ "additionalProperties",
49
+ "$schema",
50
+ "exclusiveMaximum",
51
+ "exclusiveMinimum",
52
+ "$defs", # Remove $defs after resolving references
53
+ }
54
+ supported_string_formats = {"enum", "date-time"}
55
+
56
+ for key, value in schema.items():
57
+ if key in unsupported_keys:
58
+ continue # Skip this key
59
+
60
+ # Rewrite unsupported 'const' to a safe form for Gemini tools
61
+ # - For string const, convert to enum [value]
62
+ # - For non-string const (booleans/numbers), drop the constraint
63
+ if key == "const":
64
+ if isinstance(value, str):
65
+ cleaned_schema["enum"] = [value]
66
+ continue
67
+
68
+ if (
69
+ key == "format"
70
+ and schema.get("type") == "string"
71
+ and value not in supported_string_formats
72
+ ):
73
+ continue # Remove unsupported string formats
74
+
75
+ if isinstance(value, dict):
76
+ cleaned_schema[key] = self._clean_schema_for_google(value)
77
+ elif isinstance(value, list):
78
+ cleaned_schema[key] = [
79
+ self._clean_schema_for_google(item) if isinstance(item, dict) else item
80
+ for item in value
81
+ ]
82
+ else:
83
+ cleaned_schema[key] = value
84
+ return cleaned_schema
85
+
86
+ def _resolve_refs(self, schema: dict[str, Any], root_schema: dict[str, Any]) -> dict[str, Any]:
87
+ """
88
+ Resolve $ref references in a JSON schema by inlining the referenced definitions.
89
+
90
+ Args:
91
+ schema: The current schema fragment being processed
92
+ root_schema: The root schema containing $defs
93
+
94
+ Returns:
95
+ Schema with $ref references resolved
96
+ """
97
+ if not isinstance(schema, dict):
98
+ return schema
99
+
100
+ # If this is a $ref, resolve it
101
+ if "$ref" in schema:
102
+ ref_path = schema["$ref"]
103
+ if ref_path.startswith("#/"):
104
+ # Parse the reference path (e.g., "#/$defs/HumanInputRequest")
105
+ path_parts = ref_path[2:].split("/") # Remove "#/" and split
106
+
107
+ # Navigate to the referenced definition
108
+ ref_target = root_schema
109
+ for part in path_parts:
110
+ if part in ref_target:
111
+ ref_target = ref_target[part]
112
+ else:
113
+ # If reference not found, return the original schema
114
+ return schema
115
+
116
+ # Return the resolved definition (recursively resolve any nested refs)
117
+ return self._resolve_refs(ref_target, root_schema)
118
+
119
+ # Otherwise, recursively process all values in the schema
120
+ resolved = {}
121
+ for key, value in schema.items():
122
+ if isinstance(value, dict):
123
+ resolved[key] = self._resolve_refs(value, root_schema)
124
+ elif isinstance(value, list):
125
+ resolved[key] = [
126
+ self._resolve_refs(item, root_schema) if isinstance(item, dict) else item
127
+ for item in value
128
+ ]
129
+ else:
130
+ resolved[key] = value
131
+
132
+ return resolved
133
+
134
+ def convert_to_google_content(
135
+ self, messages: list[PromptMessageExtended]
136
+ ) -> list[types.Content]:
137
+ """
138
+ Converts a list of fast-agent PromptMessageExtended to google.genai types.Content.
139
+ Handles different roles and content types (text, images, etc.).
140
+ """
141
+ google_contents: list[types.Content] = []
142
+ for message in messages:
143
+ parts: list[types.Part] = []
144
+ for part_content in message.content: # renamed part to part_content to avoid conflict
145
+ if is_text_content(part_content):
146
+ parts.append(types.Part.from_text(text=get_text(part_content) or ""))
147
+ elif is_image_content(part_content):
148
+ assert isinstance(part_content, ImageContent)
149
+ image_bytes = base64.b64decode(get_image_data(part_content) or "")
150
+ parts.append(
151
+ types.Part.from_bytes(mime_type=part_content.mimeType, data=image_bytes)
152
+ )
153
+ elif is_resource_content(part_content):
154
+ assert isinstance(part_content, EmbeddedResource)
155
+ if "application/pdf" == part_content.resource.mimeType and isinstance(
156
+ part_content.resource, BlobResourceContents
157
+ ):
158
+ pdf_bytes = base64.b64decode(part_content.resource.blob)
159
+ parts.append(
160
+ types.Part.from_bytes(
161
+ mime_type=part_content.resource.mimeType or "application/pdf",
162
+ data=pdf_bytes,
163
+ )
164
+ )
165
+ elif part_content.resource.mimeType and part_content.resource.mimeType.startswith(
166
+ "video/"
167
+ ):
168
+ # Handle video content
169
+ if isinstance(part_content.resource, BlobResourceContents):
170
+ video_bytes = base64.b64decode(part_content.resource.blob)
171
+ parts.append(
172
+ types.Part.from_bytes(
173
+ mime_type=part_content.resource.mimeType,
174
+ data=video_bytes,
175
+ )
176
+ )
177
+ else:
178
+ # Handle non-blob video resources (YouTube URLs, File API URIs, etc.)
179
+ # Google supports YouTube URLs and File API URIs directly via file_data
180
+ uri_str = getattr(part_content.resource, "uri", None)
181
+ mime_str = getattr(part_content.resource, "mimeType", "video/mp4")
182
+
183
+ if uri_str:
184
+ # Use file_data for YouTube URLs and File API URIs
185
+ # Google accepts: YouTube URLs, gs:// URIs, and uploaded file URIs
186
+ parts.append(
187
+ types.Part.from_uri(
188
+ file_uri=str(uri_str),
189
+ mime_type=mime_str
190
+ )
191
+ )
192
+ else:
193
+ # Fallback if no URI is available
194
+ parts.append(
195
+ types.Part.from_text(
196
+ text=f"[Video Resource: No URI provided, MIME: {mime_str}]"
197
+ )
198
+ )
199
+ else:
200
+ # Check if the resource itself has text content
201
+ # Use get_text helper to extract text from various content types
202
+ resource_text = get_text(part_content.resource)
203
+
204
+ if resource_text is not None:
205
+ parts.append(types.Part.from_text(text=resource_text))
206
+ else:
207
+ # Fallback for other binary types or types without direct text
208
+ uri_str = getattr(part_content.resource, "uri", "unknown_uri")
209
+ mime_str = getattr(part_content.resource, "mimeType", "unknown_mime")
210
+ parts.append(
211
+ types.Part.from_text(
212
+ text=f"[Resource: {uri_str}, MIME: {mime_str}]"
213
+ )
214
+ )
215
+ elif is_resource_link(part_content):
216
+ # Handle ResourceLink - metadata reference to a resource
217
+ assert isinstance(part_content, ResourceLink)
218
+ mime = part_content.mimeType
219
+ uri_str = str(part_content.uri) if part_content.uri else None
220
+
221
+ # For media types (video/audio/image), use Part.from_uri() to let Google fetch
222
+ if uri_str and mime and (
223
+ mime.startswith("video/")
224
+ or mime.startswith("audio/")
225
+ or mime.startswith("image/")
226
+ ):
227
+ parts.append(types.Part.from_uri(file_uri=uri_str, mime_type=mime))
228
+ else:
229
+ # Fallback to text representation for non-media types
230
+ text = get_text(part_content)
231
+ if text:
232
+ parts.append(types.Part.from_text(text=text))
233
+
234
+ if parts:
235
+ google_role = (
236
+ "user"
237
+ if message.role == "user"
238
+ else ("model" if message.role == "assistant" else "tool")
239
+ )
240
+ google_contents.append(types.Content(role=google_role, parts=parts))
241
+ return google_contents
242
+
243
+ def convert_to_google_tools(self, tools: list[Tool]) -> list[types.Tool]:
244
+ """
245
+ Converts a list of fast-agent ToolDefinition to google.genai types.Tool.
246
+ """
247
+ google_tools: list[types.Tool] = []
248
+ for tool in tools:
249
+ cleaned_input_schema = self._clean_schema_for_google(tool.inputSchema)
250
+ function_declaration = types.FunctionDeclaration(
251
+ name=tool.name,
252
+ description=tool.description if tool.description else "",
253
+ parameters=types.Schema(**cleaned_input_schema),
254
+ )
255
+ google_tools.append(types.Tool(function_declarations=[function_declaration]))
256
+ return google_tools
257
+
258
+ def convert_from_google_content(
259
+ self, content: types.Content
260
+ ) -> list[ContentBlock | CallToolRequestParams]:
261
+ """
262
+ Converts google.genai types.Content from a model response to a list of
263
+ fast-agent content types or tool call requests.
264
+ """
265
+ fast_agent_parts: list[ContentBlock | CallToolRequestParams] = []
266
+
267
+ if content is None or not hasattr(content, "parts") or content.parts is None:
268
+ return [] # Google API response 'content' object is None. Cannot extract parts.
269
+
270
+ for part in content.parts:
271
+ if part.text:
272
+ fast_agent_parts.append(TextContent(type="text", text=part.text))
273
+ elif part.function_call:
274
+ fast_agent_parts.append(
275
+ CallToolRequestParams(
276
+ name=part.function_call.name,
277
+ arguments=part.function_call.args,
278
+ )
279
+ )
280
+ return fast_agent_parts
281
+
282
+ def convert_from_google_function_call(
283
+ self, function_call: types.FunctionCall
284
+ ) -> CallToolRequest:
285
+ """
286
+ Converts a single google.genai types.FunctionCall to a fast-agent CallToolRequest.
287
+ """
288
+ return CallToolRequest(
289
+ method="tools/call",
290
+ params=CallToolRequestParams(
291
+ name=function_call.name,
292
+ arguments=function_call.args,
293
+ ),
294
+ )
295
+
296
+ def convert_function_results_to_google(
297
+ self, tool_results: list[tuple[str, CallToolResult]]
298
+ ) -> list[types.Content]:
299
+ """
300
+ Converts a list of fast-agent tool results to google.genai types.Content
301
+ with role 'tool'. Handles multimodal content in tool results.
302
+ """
303
+ google_tool_response_contents: list[types.Content] = []
304
+ for tool_name, tool_result in tool_results:
305
+ current_content_parts: list[types.Part] = []
306
+ textual_outputs: list[str] = []
307
+ media_parts: list[types.Part] = []
308
+
309
+ for item in tool_result.content:
310
+ if is_text_content(item):
311
+ textual_outputs.append(get_text(item) or "") # Ensure no None is added
312
+ elif is_image_content(item):
313
+ assert isinstance(item, ImageContent)
314
+ try:
315
+ image_bytes = base64.b64decode(get_image_data(item) or "")
316
+ media_parts.append(
317
+ types.Part.from_bytes(data=image_bytes, mime_type=item.mimeType)
318
+ )
319
+ except Exception as e:
320
+ textual_outputs.append(f"[Error processing image from tool result: {e}]")
321
+ elif is_resource_content(item):
322
+ assert isinstance(item, EmbeddedResource)
323
+ if (
324
+ "application/pdf" == item.resource.mimeType
325
+ and hasattr(item.resource, "blob")
326
+ and isinstance(item.resource, BlobResourceContents)
327
+ ):
328
+ try:
329
+ pdf_bytes = base64.b64decode(item.resource.blob)
330
+ media_parts.append(
331
+ types.Part.from_bytes(
332
+ data=pdf_bytes,
333
+ mime_type=item.resource.mimeType or "application/pdf",
334
+ )
335
+ )
336
+ except Exception as e:
337
+ textual_outputs.append(f"[Error processing PDF from tool result: {e}]")
338
+ else:
339
+ # Check if the resource itself has text content
340
+ # Use get_text helper to extract text from various content types
341
+ resource_text = get_text(item.resource)
342
+
343
+ if resource_text is not None:
344
+ textual_outputs.append(resource_text)
345
+ else:
346
+ uri_str = getattr(item.resource, "uri", "unknown_uri")
347
+ mime_str = getattr(item.resource, "mimeType", "unknown_mime")
348
+ textual_outputs.append(
349
+ f"[Unhandled Resource in Tool: {uri_str}, MIME: {mime_str}]"
350
+ )
351
+ elif is_resource_link(item):
352
+ # Handle ResourceLink in tool results
353
+ assert isinstance(item, ResourceLink)
354
+ mime = item.mimeType
355
+ uri_str = str(item.uri) if item.uri else None
356
+
357
+ # For media types, use Part.from_uri() to let Google fetch
358
+ if uri_str and mime and (
359
+ mime.startswith("video/")
360
+ or mime.startswith("audio/")
361
+ or mime.startswith("image/")
362
+ ):
363
+ media_parts.append(types.Part.from_uri(file_uri=uri_str, mime_type=mime))
364
+ else:
365
+ # Fallback to text representation for non-media types
366
+ text = get_text(item)
367
+ if text:
368
+ textual_outputs.append(text)
369
+ # Add handling for other content types if needed, for now they are skipped or become unhandled resource text
370
+
371
+ function_response_payload: dict[str, Any] = {"tool_name": tool_name}
372
+ if textual_outputs:
373
+ function_response_payload["text_content"] = "\n".join(textual_outputs)
374
+
375
+ # Only add media_parts if there are some, otherwise Gemini might error on empty parts for function response
376
+ if media_parts:
377
+ # Create the main FunctionResponse part
378
+ fn_response_part = types.Part.from_function_response(
379
+ name=tool_name, response=function_response_payload
380
+ )
381
+ current_content_parts.append(fn_response_part)
382
+ current_content_parts.extend(
383
+ media_parts
384
+ ) # Add media parts after the main response part
385
+ else: # If no media parts, the textual output (if any) is the sole content of the function response
386
+ fn_response_part = types.Part.from_function_response(
387
+ name=tool_name, response=function_response_payload
388
+ )
389
+ current_content_parts.append(fn_response_part)
390
+
391
+ google_tool_response_contents.append(
392
+ types.Content(role="tool", parts=current_content_parts)
393
+ )
394
+ return google_tool_response_contents
395
+
396
+ def convert_request_params_to_google_config(
397
+ self, request_params: RequestParams
398
+ ) -> types.GenerateContentConfig:
399
+ """
400
+ Converts fast-agent RequestParams to google.genai types.GenerateContentConfig.
401
+ """
402
+ config_args: dict[str, Any] = {}
403
+ if request_params.temperature is not None:
404
+ config_args["temperature"] = request_params.temperature
405
+ if request_params.maxTokens is not None:
406
+ config_args["max_output_tokens"] = request_params.maxTokens
407
+ if hasattr(request_params, "topK") and request_params.topK is not None:
408
+ config_args["top_k"] = request_params.topK
409
+ if hasattr(request_params, "topP") and request_params.topP is not None:
410
+ config_args["top_p"] = request_params.topP
411
+ if hasattr(request_params, "stopSequences") and request_params.stopSequences is not None:
412
+ config_args["stop_sequences"] = request_params.stopSequences
413
+ if (
414
+ hasattr(request_params, "presencePenalty")
415
+ and request_params.presencePenalty is not None
416
+ ):
417
+ config_args["presence_penalty"] = request_params.presencePenalty
418
+ if (
419
+ hasattr(request_params, "frequencyPenalty")
420
+ and request_params.frequencyPenalty is not None
421
+ ):
422
+ config_args["frequency_penalty"] = request_params.frequencyPenalty
423
+ if request_params.systemPrompt is not None:
424
+ config_args["system_instruction"] = request_params.systemPrompt
425
+ return types.GenerateContentConfig(**config_args)
426
+
427
+ def convert_from_google_content_list(
428
+ self, contents: list[types.Content]
429
+ ) -> list[PromptMessageExtended]:
430
+ """
431
+ Converts a list of google.genai types.Content to a list of fast-agent PromptMessageExtended.
432
+ """
433
+ return [self._convert_from_google_content(content) for content in contents]
434
+
435
+ def _convert_from_google_content(self, content: types.Content) -> PromptMessageExtended:
436
+ """
437
+ Converts a single google.genai types.Content to a fast-agent PromptMessageExtended.
438
+ """
439
+ # Official fix for GitHub issue #207: Handle None content or content.parts
440
+ if content is None or not hasattr(content, "parts") or content.parts is None:
441
+ return PromptMessageExtended(role="assistant", content=[])
442
+
443
+ if content.role == "model" and any(part.function_call for part in content.parts):
444
+ return PromptMessageExtended(role="assistant", content=[])
445
+
446
+ fast_agent_parts: list[ContentBlock] = []
447
+ for part in content.parts:
448
+ if part.text:
449
+ fast_agent_parts.append(TextContent(type="text", text=part.text))
450
+ elif part.function_response:
451
+ response_text = str(part.function_response.response)
452
+ fast_agent_parts.append(TextContent(type="text", text=response_text))
453
+ elif part.file_data:
454
+ fast_agent_parts.append(
455
+ EmbeddedResource(
456
+ type="resource",
457
+ resource=TextResourceContents(
458
+ uri=AnyUrl(part.file_data.file_uri or ""),
459
+ mimeType=part.file_data.mime_type,
460
+ text=f"[Resource: {part.file_data.file_uri}, MIME: {part.file_data.mime_type}]",
461
+ ),
462
+ )
463
+ )
464
+
465
+ fast_agent_role = "user" if content.role == "user" else "assistant"
466
+ return PromptMessageExtended(role=fast_agent_role, content=fast_agent_parts)