fast-agent-mcp 0.2.58__py3-none-any.whl → 0.3.1__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.

Potentially problematic release.


This version of fast-agent-mcp might be problematic. Click here for more details.

Files changed (234) hide show
  1. fast_agent/__init__.py +127 -0
  2. fast_agent/agents/__init__.py +36 -0
  3. {mcp_agent/core → fast_agent/agents}/agent_types.py +2 -1
  4. fast_agent/agents/llm_agent.py +217 -0
  5. fast_agent/agents/llm_decorator.py +486 -0
  6. mcp_agent/agents/base_agent.py → fast_agent/agents/mcp_agent.py +377 -385
  7. fast_agent/agents/tool_agent.py +168 -0
  8. {mcp_agent → fast_agent}/agents/workflow/chain_agent.py +43 -33
  9. {mcp_agent → fast_agent}/agents/workflow/evaluator_optimizer.py +31 -35
  10. {mcp_agent → fast_agent}/agents/workflow/iterative_planner.py +56 -47
  11. {mcp_agent → fast_agent}/agents/workflow/orchestrator_models.py +4 -4
  12. {mcp_agent → fast_agent}/agents/workflow/parallel_agent.py +34 -41
  13. {mcp_agent → fast_agent}/agents/workflow/router_agent.py +54 -39
  14. {mcp_agent → fast_agent}/cli/__main__.py +5 -3
  15. {mcp_agent → fast_agent}/cli/commands/check_config.py +95 -66
  16. {mcp_agent → fast_agent}/cli/commands/go.py +20 -11
  17. {mcp_agent → fast_agent}/cli/commands/quickstart.py +4 -4
  18. {mcp_agent → fast_agent}/cli/commands/server_helpers.py +1 -1
  19. {mcp_agent → fast_agent}/cli/commands/setup.py +75 -134
  20. {mcp_agent → fast_agent}/cli/commands/url_parser.py +9 -8
  21. {mcp_agent → fast_agent}/cli/main.py +36 -16
  22. {mcp_agent → fast_agent}/cli/terminal.py +2 -2
  23. {mcp_agent → fast_agent}/config.py +10 -2
  24. fast_agent/constants.py +8 -0
  25. {mcp_agent → fast_agent}/context.py +24 -19
  26. {mcp_agent → fast_agent}/context_dependent.py +9 -5
  27. fast_agent/core/__init__.py +52 -0
  28. {mcp_agent → fast_agent}/core/agent_app.py +39 -36
  29. fast_agent/core/core_app.py +135 -0
  30. {mcp_agent → fast_agent}/core/direct_decorators.py +12 -26
  31. {mcp_agent → fast_agent}/core/direct_factory.py +95 -73
  32. {mcp_agent → fast_agent/core}/executor/executor.py +4 -5
  33. {mcp_agent → fast_agent}/core/fastagent.py +32 -32
  34. fast_agent/core/logging/__init__.py +5 -0
  35. {mcp_agent → fast_agent/core}/logging/events.py +3 -3
  36. {mcp_agent → fast_agent/core}/logging/json_serializer.py +1 -1
  37. {mcp_agent → fast_agent/core}/logging/listeners.py +85 -7
  38. {mcp_agent → fast_agent/core}/logging/logger.py +7 -7
  39. {mcp_agent → fast_agent/core}/logging/transport.py +10 -11
  40. fast_agent/core/prompt.py +9 -0
  41. {mcp_agent → fast_agent}/core/validation.py +4 -4
  42. fast_agent/event_progress.py +61 -0
  43. fast_agent/history/history_exporter.py +44 -0
  44. {mcp_agent → fast_agent}/human_input/__init__.py +9 -12
  45. {mcp_agent → fast_agent}/human_input/elicitation_handler.py +26 -8
  46. {mcp_agent → fast_agent}/human_input/elicitation_state.py +7 -7
  47. {mcp_agent → fast_agent}/human_input/simple_form.py +6 -4
  48. {mcp_agent → fast_agent}/human_input/types.py +1 -18
  49. fast_agent/interfaces.py +228 -0
  50. fast_agent/llm/__init__.py +9 -0
  51. mcp_agent/llm/augmented_llm.py → fast_agent/llm/fastagent_llm.py +127 -218
  52. fast_agent/llm/internal/passthrough.py +137 -0
  53. mcp_agent/llm/augmented_llm_playback.py → fast_agent/llm/internal/playback.py +29 -25
  54. mcp_agent/llm/augmented_llm_silent.py → fast_agent/llm/internal/silent.py +10 -17
  55. fast_agent/llm/internal/slow.py +38 -0
  56. {mcp_agent → fast_agent}/llm/memory.py +40 -30
  57. {mcp_agent → fast_agent}/llm/model_database.py +35 -2
  58. {mcp_agent → fast_agent}/llm/model_factory.py +103 -77
  59. fast_agent/llm/model_info.py +126 -0
  60. {mcp_agent/llm/providers → fast_agent/llm/provider/anthropic}/anthropic_utils.py +7 -7
  61. fast_agent/llm/provider/anthropic/llm_anthropic.py +603 -0
  62. {mcp_agent/llm/providers → fast_agent/llm/provider/anthropic}/multipart_converter_anthropic.py +79 -86
  63. {mcp_agent/llm/providers → fast_agent/llm/provider/bedrock}/bedrock_utils.py +3 -1
  64. mcp_agent/llm/providers/augmented_llm_bedrock.py → fast_agent/llm/provider/bedrock/llm_bedrock.py +833 -717
  65. {mcp_agent/llm/providers → fast_agent/llm/provider/google}/google_converter.py +66 -14
  66. fast_agent/llm/provider/google/llm_google_native.py +431 -0
  67. mcp_agent/llm/providers/augmented_llm_aliyun.py → fast_agent/llm/provider/openai/llm_aliyun.py +6 -7
  68. mcp_agent/llm/providers/augmented_llm_azure.py → fast_agent/llm/provider/openai/llm_azure.py +4 -4
  69. mcp_agent/llm/providers/augmented_llm_deepseek.py → fast_agent/llm/provider/openai/llm_deepseek.py +10 -11
  70. mcp_agent/llm/providers/augmented_llm_generic.py → fast_agent/llm/provider/openai/llm_generic.py +4 -4
  71. mcp_agent/llm/providers/augmented_llm_google_oai.py → fast_agent/llm/provider/openai/llm_google_oai.py +4 -4
  72. mcp_agent/llm/providers/augmented_llm_groq.py → fast_agent/llm/provider/openai/llm_groq.py +14 -16
  73. mcp_agent/llm/providers/augmented_llm_openai.py → fast_agent/llm/provider/openai/llm_openai.py +133 -207
  74. mcp_agent/llm/providers/augmented_llm_openrouter.py → fast_agent/llm/provider/openai/llm_openrouter.py +6 -6
  75. mcp_agent/llm/providers/augmented_llm_tensorzero_openai.py → fast_agent/llm/provider/openai/llm_tensorzero_openai.py +17 -16
  76. mcp_agent/llm/providers/augmented_llm_xai.py → fast_agent/llm/provider/openai/llm_xai.py +6 -6
  77. {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/multipart_converter_openai.py +125 -63
  78. {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/openai_multipart.py +12 -12
  79. {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/openai_utils.py +18 -16
  80. {mcp_agent → fast_agent}/llm/provider_key_manager.py +2 -2
  81. {mcp_agent → fast_agent}/llm/provider_types.py +2 -0
  82. {mcp_agent → fast_agent}/llm/sampling_converter.py +15 -12
  83. {mcp_agent → fast_agent}/llm/usage_tracking.py +23 -5
  84. fast_agent/mcp/__init__.py +54 -0
  85. {mcp_agent → fast_agent}/mcp/elicitation_factory.py +3 -3
  86. {mcp_agent → fast_agent}/mcp/elicitation_handlers.py +19 -10
  87. {mcp_agent → fast_agent}/mcp/gen_client.py +3 -3
  88. fast_agent/mcp/helpers/__init__.py +36 -0
  89. fast_agent/mcp/helpers/content_helpers.py +183 -0
  90. {mcp_agent → fast_agent}/mcp/helpers/server_config_helpers.py +8 -8
  91. {mcp_agent → fast_agent}/mcp/hf_auth.py +25 -23
  92. fast_agent/mcp/interfaces.py +93 -0
  93. {mcp_agent → fast_agent}/mcp/logger_textio.py +4 -4
  94. {mcp_agent → fast_agent}/mcp/mcp_agent_client_session.py +49 -44
  95. {mcp_agent → fast_agent}/mcp/mcp_aggregator.py +66 -115
  96. {mcp_agent → fast_agent}/mcp/mcp_connection_manager.py +16 -23
  97. {mcp_agent/core → fast_agent/mcp}/mcp_content.py +23 -15
  98. {mcp_agent → fast_agent}/mcp/mime_utils.py +39 -0
  99. fast_agent/mcp/prompt.py +159 -0
  100. mcp_agent/mcp/prompt_message_multipart.py → fast_agent/mcp/prompt_message_extended.py +27 -20
  101. {mcp_agent → fast_agent}/mcp/prompt_render.py +21 -19
  102. {mcp_agent → fast_agent}/mcp/prompt_serialization.py +46 -46
  103. fast_agent/mcp/prompts/__main__.py +7 -0
  104. {mcp_agent → fast_agent}/mcp/prompts/prompt_helpers.py +31 -30
  105. {mcp_agent → fast_agent}/mcp/prompts/prompt_load.py +8 -8
  106. {mcp_agent → fast_agent}/mcp/prompts/prompt_server.py +11 -19
  107. {mcp_agent → fast_agent}/mcp/prompts/prompt_template.py +18 -18
  108. {mcp_agent → fast_agent}/mcp/resource_utils.py +1 -1
  109. {mcp_agent → fast_agent}/mcp/sampling.py +31 -26
  110. {mcp_agent/mcp_server → fast_agent/mcp/server}/__init__.py +1 -1
  111. {mcp_agent/mcp_server → fast_agent/mcp/server}/agent_server.py +5 -6
  112. fast_agent/mcp/ui_agent.py +48 -0
  113. fast_agent/mcp/ui_mixin.py +209 -0
  114. fast_agent/mcp_server_registry.py +90 -0
  115. {mcp_agent → fast_agent}/resources/examples/data-analysis/analysis-campaign.py +5 -4
  116. {mcp_agent → fast_agent}/resources/examples/data-analysis/analysis.py +1 -1
  117. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/forms_demo.py +3 -3
  118. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character.py +2 -2
  119. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character_handler.py +1 -1
  120. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/tool_call.py +1 -1
  121. {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_one.py +1 -1
  122. {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_two.py +1 -1
  123. {mcp_agent → fast_agent}/resources/examples/researcher/researcher-eval.py +1 -1
  124. {mcp_agent → fast_agent}/resources/examples/researcher/researcher-imp.py +1 -1
  125. {mcp_agent → fast_agent}/resources/examples/researcher/researcher.py +1 -1
  126. {mcp_agent → fast_agent}/resources/examples/tensorzero/agent.py +2 -2
  127. {mcp_agent → fast_agent}/resources/examples/tensorzero/image_demo.py +3 -3
  128. {mcp_agent → fast_agent}/resources/examples/tensorzero/simple_agent.py +1 -1
  129. {mcp_agent → fast_agent}/resources/examples/workflows/chaining.py +1 -1
  130. {mcp_agent → fast_agent}/resources/examples/workflows/evaluator.py +3 -3
  131. {mcp_agent → fast_agent}/resources/examples/workflows/human_input.py +5 -3
  132. {mcp_agent → fast_agent}/resources/examples/workflows/orchestrator.py +1 -1
  133. {mcp_agent → fast_agent}/resources/examples/workflows/parallel.py +2 -2
  134. {mcp_agent → fast_agent}/resources/examples/workflows/router.py +5 -2
  135. fast_agent/resources/setup/.gitignore +24 -0
  136. fast_agent/resources/setup/agent.py +18 -0
  137. fast_agent/resources/setup/fastagent.config.yaml +44 -0
  138. fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
  139. fast_agent/resources/setup/pyproject.toml.tmpl +17 -0
  140. fast_agent/tools/elicitation.py +369 -0
  141. fast_agent/types/__init__.py +32 -0
  142. fast_agent/types/llm_stop_reason.py +77 -0
  143. fast_agent/ui/__init__.py +38 -0
  144. fast_agent/ui/console_display.py +1005 -0
  145. {mcp_agent/human_input → fast_agent/ui}/elicitation_form.py +17 -12
  146. mcp_agent/human_input/elicitation_forms.py → fast_agent/ui/elicitation_style.py +1 -1
  147. {mcp_agent/core → fast_agent/ui}/enhanced_prompt.py +96 -25
  148. {mcp_agent/core → fast_agent/ui}/interactive_prompt.py +330 -125
  149. fast_agent/ui/mcp_ui_utils.py +224 -0
  150. {mcp_agent → fast_agent/ui}/progress_display.py +2 -2
  151. {mcp_agent/logging → fast_agent/ui}/rich_progress.py +4 -4
  152. {mcp_agent/core → fast_agent/ui}/usage_display.py +3 -8
  153. {fast_agent_mcp-0.2.58.dist-info → fast_agent_mcp-0.3.1.dist-info}/METADATA +7 -7
  154. fast_agent_mcp-0.3.1.dist-info/RECORD +203 -0
  155. fast_agent_mcp-0.3.1.dist-info/entry_points.txt +5 -0
  156. fast_agent_mcp-0.2.58.dist-info/RECORD +0 -193
  157. fast_agent_mcp-0.2.58.dist-info/entry_points.txt +0 -6
  158. mcp_agent/__init__.py +0 -114
  159. mcp_agent/agents/agent.py +0 -92
  160. mcp_agent/agents/workflow/__init__.py +0 -1
  161. mcp_agent/agents/workflow/orchestrator_agent.py +0 -597
  162. mcp_agent/app.py +0 -175
  163. mcp_agent/core/__init__.py +0 -26
  164. mcp_agent/core/prompt.py +0 -191
  165. mcp_agent/event_progress.py +0 -134
  166. mcp_agent/human_input/handler.py +0 -81
  167. mcp_agent/llm/__init__.py +0 -2
  168. mcp_agent/llm/augmented_llm_passthrough.py +0 -232
  169. mcp_agent/llm/augmented_llm_slow.py +0 -53
  170. mcp_agent/llm/providers/__init__.py +0 -8
  171. mcp_agent/llm/providers/augmented_llm_anthropic.py +0 -718
  172. mcp_agent/llm/providers/augmented_llm_google_native.py +0 -496
  173. mcp_agent/llm/providers/sampling_converter_anthropic.py +0 -57
  174. mcp_agent/llm/providers/sampling_converter_openai.py +0 -26
  175. mcp_agent/llm/sampling_format_converter.py +0 -37
  176. mcp_agent/logging/__init__.py +0 -0
  177. mcp_agent/mcp/__init__.py +0 -50
  178. mcp_agent/mcp/helpers/__init__.py +0 -25
  179. mcp_agent/mcp/helpers/content_helpers.py +0 -187
  180. mcp_agent/mcp/interfaces.py +0 -266
  181. mcp_agent/mcp/prompts/__init__.py +0 -0
  182. mcp_agent/mcp/prompts/__main__.py +0 -10
  183. mcp_agent/mcp_server_registry.py +0 -343
  184. mcp_agent/tools/tool_definition.py +0 -14
  185. mcp_agent/ui/console_display.py +0 -790
  186. mcp_agent/ui/console_display_legacy.py +0 -401
  187. {mcp_agent → fast_agent}/agents/workflow/orchestrator_prompts.py +0 -0
  188. {mcp_agent/agents → fast_agent/cli}/__init__.py +0 -0
  189. {mcp_agent → fast_agent}/cli/constants.py +0 -0
  190. {mcp_agent → fast_agent}/core/error_handling.py +0 -0
  191. {mcp_agent → fast_agent}/core/exceptions.py +0 -0
  192. {mcp_agent/cli → fast_agent/core/executor}/__init__.py +0 -0
  193. {mcp_agent → fast_agent/core}/executor/task_registry.py +0 -0
  194. {mcp_agent → fast_agent/core}/executor/workflow_signal.py +0 -0
  195. {mcp_agent → fast_agent}/human_input/form_fields.py +0 -0
  196. {mcp_agent → fast_agent}/llm/prompt_utils.py +0 -0
  197. {mcp_agent/core → fast_agent/llm}/request_params.py +0 -0
  198. {mcp_agent → fast_agent}/mcp/common.py +0 -0
  199. {mcp_agent/executor → fast_agent/mcp/prompts}/__init__.py +0 -0
  200. {mcp_agent → fast_agent}/mcp/prompts/prompt_constants.py +0 -0
  201. {mcp_agent → fast_agent}/py.typed +0 -0
  202. {mcp_agent → fast_agent}/resources/examples/data-analysis/fastagent.config.yaml +0 -0
  203. {mcp_agent → fast_agent}/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -0
  204. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_account_server.py +0 -0
  205. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_forms_server.py +0 -0
  206. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_game_server.py +0 -0
  207. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/fastagent.config.yaml +0 -0
  208. {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +0 -0
  209. {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/fastagent.config.yaml +0 -0
  210. {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +0 -0
  211. {mcp_agent → fast_agent}/resources/examples/researcher/fastagent.config.yaml +0 -0
  212. {mcp_agent → fast_agent}/resources/examples/tensorzero/.env.sample +0 -0
  213. {mcp_agent → fast_agent}/resources/examples/tensorzero/Makefile +0 -0
  214. {mcp_agent → fast_agent}/resources/examples/tensorzero/README.md +0 -0
  215. {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
  216. {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/crab.png +0 -0
  217. {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
  218. {mcp_agent → fast_agent}/resources/examples/tensorzero/docker-compose.yml +0 -0
  219. {mcp_agent → fast_agent}/resources/examples/tensorzero/fastagent.config.yaml +0 -0
  220. {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/Dockerfile +0 -0
  221. {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/entrypoint.sh +0 -0
  222. {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/mcp_server.py +0 -0
  223. {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/pyproject.toml +0 -0
  224. {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/system_schema.json +0 -0
  225. {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +0 -0
  226. {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +0 -0
  227. {mcp_agent → fast_agent}/resources/examples/workflows/fastagent.config.yaml +0 -0
  228. {mcp_agent → fast_agent}/resources/examples/workflows/graded_report.md +0 -0
  229. {mcp_agent → fast_agent}/resources/examples/workflows/short_story.md +0 -0
  230. {mcp_agent → fast_agent}/resources/examples/workflows/short_story.txt +0 -0
  231. {mcp_agent → fast_agent/ui}/console.py +0 -0
  232. {mcp_agent/core → fast_agent/ui}/mermaid_utils.py +0 -0
  233. {fast_agent_mcp-0.2.58.dist-info → fast_agent_mcp-0.3.1.dist-info}/WHEEL +0 -0
  234. {fast_agent_mcp-0.2.58.dist-info → fast_agent_mcp-0.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,369 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import uuid
5
+ from typing import Any, Awaitable, Callable, List, Literal, Optional, Union
6
+
7
+ from mcp.server.fastmcp.tools import Tool as FastMCPTool
8
+ from mcp.types import Tool as McpTool
9
+ from pydantic import BaseModel, Field
10
+
11
+ from fast_agent.constants import HUMAN_INPUT_TOOL_NAME
12
+
13
+ """
14
+ Human-input (elicitation) tool models, schemas, and builders.
15
+
16
+ This module lives in fast_agent to avoid circular imports and provides:
17
+ - A centralized HUMAN_INPUT_TOOL_NAME constant (imported from fast_agent.constants)
18
+ - Pydantic models for a simplified FormSpec (LLM-friendly)
19
+ - MCP Tool schema builder (sanitized, provider-friendly)
20
+ - FastMCP Tool builder sharing the same schema
21
+ - A lightweight async callback registry for UI-specific elicitation handling
22
+ """
23
+
24
+ # -----------------------
25
+ # Pydantic models
26
+ # -----------------------
27
+
28
+
29
+ class OptionItem(BaseModel):
30
+ value: Union[str, int, float, bool]
31
+ label: Optional[str] = None
32
+
33
+
34
+ class FormField(BaseModel):
35
+ name: str
36
+ type: Literal["text", "textarea", "number", "checkbox", "radio"]
37
+ label: Optional[str] = None
38
+ help: Optional[str] = None
39
+ default: Optional[Union[str, int, float, bool]] = None
40
+ required: Optional[bool] = None
41
+ # number constraints
42
+ min: Optional[float] = None
43
+ max: Optional[float] = None
44
+ # select options (for radio)
45
+ options: Optional[List[OptionItem]] = None
46
+
47
+
48
+ class HumanFormArgs(BaseModel):
49
+ """Simplified form spec for human elicitation.
50
+
51
+ Preferred shape for LLMs.
52
+ """
53
+
54
+ title: Optional[str] = None
55
+ description: Optional[str] = None
56
+ message: Optional[str] = None
57
+ fields: List[FormField] = Field(default_factory=list, max_length=7)
58
+
59
+
60
+ # -----------------------
61
+ # MCP tool schema builder
62
+ # -----------------------
63
+
64
+
65
+ def get_elicitation_tool() -> McpTool:
66
+ """Build the MCP Tool schema for the elicitation-backed human input tool.
67
+
68
+ Uses Pydantic models to derive a clean, portable JSON Schema suitable for providers.
69
+ """
70
+
71
+ schema = HumanFormArgs.model_json_schema()
72
+
73
+ def _resolve_refs(fragment: Any, root: dict[str, Any]) -> Any:
74
+ """Inline $ref references within a JSON schema fragment using the given root schema.
75
+
76
+ Supports local references of the form '#/$defs/Name'.
77
+ """
78
+ if not isinstance(fragment, dict):
79
+ return fragment
80
+
81
+ if "$ref" in fragment:
82
+ ref_path: str = fragment["$ref"]
83
+ if ref_path.startswith("#/$defs/") and "$defs" in root:
84
+ key = (
85
+ ref_path.split("/#/$defs/")[-1]
86
+ if "/#/$defs/" in ref_path
87
+ else ref_path[len("#/$defs/") :]
88
+ )
89
+ target = root.get("$defs", {}).get(key)
90
+ if isinstance(target, dict):
91
+ return _resolve_refs(target, root)
92
+ fragment = {k: v for k, v in fragment.items() if k != "$ref"}
93
+ fragment.setdefault("type", "object")
94
+ fragment.setdefault("properties", {})
95
+ return fragment
96
+
97
+ resolved: dict[str, Any] = {}
98
+ for k, v in fragment.items():
99
+ if isinstance(v, dict):
100
+ resolved[k] = _resolve_refs(v, root)
101
+ elif isinstance(v, list):
102
+ resolved[k] = [
103
+ _resolve_refs(item, root) if isinstance(item, (dict, list)) else item
104
+ for item in v
105
+ ]
106
+ else:
107
+ resolved[k] = v
108
+ return resolved
109
+
110
+ sanitized: dict[str, Any] = {"type": "object"}
111
+ if "properties" in schema:
112
+ props = dict(schema["properties"]) # copy
113
+ if "form_schema" in props:
114
+ props["schema"] = props.pop("form_schema")
115
+ props = _resolve_refs(props, schema)
116
+ sanitized["properties"] = props
117
+ else:
118
+ sanitized["properties"] = {}
119
+ if "required" in schema:
120
+ sanitized["required"] = schema["required"]
121
+ sanitized["additionalProperties"] = True
122
+
123
+ return McpTool(
124
+ name=HUMAN_INPUT_TOOL_NAME,
125
+ description=(
126
+ "Collect structured input from a human via a simple form. "
127
+ "Provide up to 7 fields with types: text, textarea, number, checkbox, or radio. "
128
+ "Each field may include label, help, default; numbers may include min/max; radio may include options (value/label). "
129
+ "You may also add an optional message shown above the form."
130
+ ),
131
+ inputSchema=sanitized,
132
+ )
133
+
134
+
135
+ # -----------------------
136
+ # Elicitation input callback registry
137
+ # -----------------------
138
+
139
+ ElicitationCallback = Callable[[dict, Optional[str], Optional[str], Optional[dict]], Awaitable[str]]
140
+
141
+ _elicitation_input_callback: ElicitationCallback | None = None
142
+
143
+
144
+ def set_elicitation_input_callback(callback: ElicitationCallback) -> None:
145
+ """Register the UI/backend-specific elicitation handler.
146
+
147
+ The callback should accept a request dict with fields: prompt, description, request_id, metadata,
148
+ plus optional agent_name, server_name, and server_info, and return an awaited string response.
149
+ """
150
+
151
+ global _elicitation_input_callback
152
+ _elicitation_input_callback = callback
153
+
154
+
155
+ def get_elicitation_input_callback() -> ElicitationCallback | None:
156
+ return _elicitation_input_callback
157
+
158
+
159
+ # -----------------------
160
+ # Runtime: run the elicitation
161
+ # -----------------------
162
+
163
+
164
+ async def run_elicitation_form(arguments: dict | str, agent_name: str | None = None) -> str:
165
+ """Parse arguments into a JSON Schema or simplified fields spec and invoke the registered callback.
166
+
167
+ Returns the response string from the callback. Raises if no callback is registered.
168
+ """
169
+
170
+ def parse_schema_string(val: str) -> dict | None:
171
+ if not isinstance(val, str):
172
+ return None
173
+ s = val.strip()
174
+ if s.startswith("```"):
175
+ lines = s.splitlines()
176
+ if lines:
177
+ lines = lines[1:]
178
+ if lines and lines[-1].strip().startswith("```"):
179
+ lines = lines[:-1]
180
+ s = "\n".join(lines)
181
+ try:
182
+ return json.loads(s)
183
+ except Exception:
184
+ return None
185
+
186
+ if not isinstance(arguments, dict):
187
+ if isinstance(arguments, str):
188
+ parsed = parse_schema_string(arguments)
189
+ if isinstance(parsed, dict):
190
+ arguments = parsed
191
+ else:
192
+ raise ValueError("Invalid arguments. Provide FormSpec or JSON Schema object.")
193
+ else:
194
+ raise ValueError("Invalid arguments. Provide FormSpec or JSON Schema object.")
195
+
196
+ schema: dict | None = None
197
+ message: str | None = None
198
+ title: str | None = None
199
+ description: str | None = None
200
+
201
+ if isinstance(arguments.get("fields"), list):
202
+ fields = arguments.get("fields")
203
+ if len(fields) > 7:
204
+ raise ValueError(
205
+ f"Error: form requests {len(fields)} fields; the maximum allowed is 7."
206
+ )
207
+
208
+ properties: dict[str, Any] = {}
209
+ required_fields: list[str] = []
210
+ for field in fields:
211
+ if not isinstance(field, dict):
212
+ continue
213
+ name = field.get("name")
214
+ ftype = field.get("type")
215
+ if not isinstance(name, str) or not isinstance(ftype, str):
216
+ continue
217
+ prop: dict[str, Any] = {}
218
+ label = field.get("label")
219
+ help_text = field.get("help")
220
+ default = field.get("default")
221
+ required_flag = field.get("required")
222
+
223
+ if ftype in ("text", "textarea"):
224
+ prop["type"] = "string"
225
+ elif ftype == "number":
226
+ prop["type"] = "number"
227
+ if isinstance(field.get("min"), (int, float)):
228
+ prop["minimum"] = field.get("min")
229
+ if isinstance(field.get("max"), (int, float)):
230
+ prop["maximum"] = field.get("max")
231
+ elif ftype == "checkbox":
232
+ prop["type"] = "boolean"
233
+ elif ftype == "radio":
234
+ prop["type"] = "string"
235
+ options = field.get("options") or []
236
+ enum_vals = []
237
+ enum_names = []
238
+ for opt in options:
239
+ if isinstance(opt, dict) and "value" in opt:
240
+ enum_vals.append(opt["value"])
241
+ if isinstance(opt.get("label"), str):
242
+ enum_names.append(opt.get("label"))
243
+ elif opt is not None:
244
+ enum_vals.append(opt)
245
+ if enum_vals:
246
+ prop["enum"] = enum_vals
247
+ if enum_names and len(enum_names) == len(enum_vals):
248
+ prop["enumNames"] = enum_names
249
+ else:
250
+ continue
251
+
252
+ desc_parts = []
253
+ if isinstance(label, str) and label:
254
+ desc_parts.append(label)
255
+ if isinstance(help_text, str) and help_text:
256
+ desc_parts.append(help_text)
257
+ if desc_parts:
258
+ prop["description"] = " - ".join(desc_parts)
259
+ if default is not None:
260
+ prop["default"] = default
261
+ properties[name] = prop
262
+ if isinstance(required_flag, bool) and required_flag:
263
+ required_fields.append(name)
264
+
265
+ if len(properties) == 0:
266
+ raise ValueError("Invalid form specification: no valid fields provided.")
267
+
268
+ schema = {"type": "object", "properties": properties}
269
+ if required_fields:
270
+ schema["required"] = required_fields
271
+
272
+ title = arguments.get("title") if isinstance(arguments.get("title"), str) else None
273
+ description = (
274
+ arguments.get("description") if isinstance(arguments.get("description"), str) else None
275
+ )
276
+ msg = arguments.get("message")
277
+ if isinstance(msg, str):
278
+ message = msg
279
+ if title:
280
+ schema["title"] = title
281
+ if description:
282
+ schema["description"] = description
283
+
284
+ elif isinstance(arguments.get("schema"), (dict, str)):
285
+ schema = arguments.get("schema")
286
+ if isinstance(schema, str):
287
+ parsed = parse_schema_string(schema)
288
+ if isinstance(parsed, dict):
289
+ schema = parsed
290
+ else:
291
+ raise ValueError("Missing or invalid schema. Provide a JSON Schema object.")
292
+ msg = arguments.get("message")
293
+ if isinstance(msg, str):
294
+ message = msg
295
+ if isinstance(arguments.get("title"), str) and "title" not in schema:
296
+ schema["title"] = arguments.get("title")
297
+ if isinstance(arguments.get("description"), str) and "description" not in schema:
298
+ schema["description"] = arguments.get("description")
299
+ if isinstance(arguments.get("required"), list) and "required" not in schema:
300
+ schema["required"] = arguments.get("required")
301
+ if isinstance(arguments.get("properties"), dict) and "properties" not in schema:
302
+ schema["properties"] = arguments.get("properties")
303
+
304
+ elif ("type" in arguments and "properties" in arguments) or (
305
+ "$schema" in arguments and "properties" in arguments
306
+ ):
307
+ schema = arguments
308
+ message = None
309
+ else:
310
+ raise ValueError("Missing or invalid schema or fields in arguments.")
311
+
312
+ props = schema.get("properties", {}) if isinstance(schema.get("properties"), dict) else {}
313
+ if len(props) > 7:
314
+ raise ValueError(f"Error: schema requests {len(props)} fields; the maximum allowed is 7.")
315
+
316
+ request_payload: dict[str, Any] = {
317
+ "prompt": message or schema.get("title") or "Please complete this form:",
318
+ "description": schema.get("description"),
319
+ "request_id": f"__human_input__{uuid.uuid4()}",
320
+ "metadata": {
321
+ "agent_name": agent_name or "Unknown Agent",
322
+ "requested_schema": schema,
323
+ },
324
+ }
325
+
326
+ cb = get_elicitation_input_callback()
327
+ if not cb:
328
+ raise RuntimeError("No elicitation input callback registered")
329
+
330
+ response_text: str = await cb(
331
+ request_payload,
332
+ agent_name or "Unknown Agent",
333
+ "__human_input__",
334
+ None,
335
+ )
336
+
337
+ return response_text
338
+
339
+
340
+ # -----------------------
341
+ # FastMCP tool builder
342
+ # -----------------------
343
+
344
+
345
+ def get_elicitation_fastmcp_tool() -> FastMCPTool:
346
+ async def elicit(
347
+ title: Optional[str] = None,
348
+ description: Optional[str] = None,
349
+ message: Optional[str] = None,
350
+ fields: List[FormField] = Field(default_factory=list, max_length=7),
351
+ ) -> str:
352
+ args = {
353
+ "title": title,
354
+ "description": description,
355
+ "message": message,
356
+ "fields": [f.model_dump() if isinstance(f, BaseModel) else f for f in fields],
357
+ }
358
+ return await run_elicitation_form(args)
359
+
360
+ tool = FastMCPTool.from_function(elicit)
361
+ tool.name = HUMAN_INPUT_TOOL_NAME
362
+ tool.description = (
363
+ "Collect structured input from a human via a simple form. Provide up to 7 fields "
364
+ "(text, textarea, number, checkbox, radio). Fields can include label, help, default; "
365
+ "numbers support min/max; radio supports options (value/label); optional message is shown above the form."
366
+ )
367
+ # Harmonize input schema with the sanitized MCP schema for provider compatibility
368
+ tool.parameters = get_elicitation_tool().inputSchema
369
+ return tool
@@ -0,0 +1,32 @@
1
+ """Shared type definitions and helpers for fast-agent.
2
+
3
+ Goals:
4
+ - Provide a stable import path for commonly used public types and helpers
5
+ - Keep dependencies minimal to reduce import-time cycles
6
+ """
7
+
8
+ # Re-export common enums/types
9
+ # Public request parameters used to configure LLM calls
10
+ from fast_agent.llm.request_params import RequestParams
11
+
12
+ # Content helpers commonly used by users to build messages
13
+ from fast_agent.mcp.helpers.content_helpers import (
14
+ ensure_multipart_messages,
15
+ normalize_to_extended_list,
16
+ text_content,
17
+ )
18
+
19
+ # Public message model used across providers and MCP integration
20
+ from fast_agent.mcp.prompt_message_extended import PromptMessageExtended
21
+ from fast_agent.types.llm_stop_reason import LlmStopReason
22
+
23
+ __all__ = [
24
+ # Enums / types
25
+ "LlmStopReason",
26
+ "PromptMessageExtended",
27
+ "RequestParams",
28
+ # Content helpers
29
+ "text_content",
30
+ "ensure_multipart_messages",
31
+ "normalize_to_extended_list",
32
+ ]
@@ -0,0 +1,77 @@
1
+ """LLM-related type definitions for fast-agent."""
2
+
3
+ from enum import Enum
4
+ from typing import Union
5
+
6
+
7
+ class LlmStopReason(str, Enum):
8
+ """
9
+ Enumeration of stop reasons for LLM message generation.
10
+
11
+ Extends the MCP SDK's standard stop reasons with additional custom values.
12
+ Inherits from str to ensure compatibility with string-based APIs.
13
+ Used primarily in PromptMessageExtended and LLM response handling.
14
+ """
15
+
16
+ # MCP SDK standard values (from mcp.types.StopReason)
17
+ END_TURN = "endTurn"
18
+ STOP_SEQUENCE = "stopSequence"
19
+ MAX_TOKENS = "maxTokens"
20
+ TOOL_USE = "toolUse" # Used when LLM stops to call tools
21
+ PAUSE = "pause"
22
+
23
+ # Custom extensions for fast-agent
24
+ ERROR = "error" # Used when there's an error in generation
25
+
26
+ TIMEOUT = "timeout" # Used when generation times out
27
+ SAFETY = "safety" # a safety or content warning was triggered
28
+
29
+ def __eq__(self, other: object) -> bool:
30
+ """
31
+ Allow comparison with both enum members and raw strings.
32
+
33
+ This enables code like:
34
+ - result.stopReason == LlmStopReason.END_TURN
35
+ - result.stopReason == "endTurn"
36
+ """
37
+ if isinstance(other, str):
38
+ return self.value == other
39
+ return super().__eq__(other)
40
+
41
+ @classmethod
42
+ def from_string(cls, value: Union[str, "LlmStopReason"]) -> "LlmStopReason":
43
+ """
44
+ Convert a string to a LlmStopReason enum member.
45
+
46
+ Args:
47
+ value: A string or LlmStopReason enum member
48
+
49
+ Returns:
50
+ The corresponding LlmStopReason enum member
51
+
52
+ Raises:
53
+ ValueError: If the string doesn't match any enum value
54
+ """
55
+ if isinstance(value, cls):
56
+ return value
57
+
58
+ for member in cls:
59
+ if member.value == value:
60
+ return member
61
+
62
+ raise ValueError(
63
+ f"Invalid stop reason: {value}. Valid values are: {[m.value for m in cls]}"
64
+ )
65
+
66
+ @classmethod
67
+ def is_valid(cls, value: str) -> bool:
68
+ """
69
+ Check if a string is a valid stop reason.
70
+
71
+ Args:
72
+ value: A string to check
73
+
74
+ Returns:
75
+ True if the string matches a valid stop reason, False otherwise
76
+ """
77
+ return value in [member.value for member in cls]
@@ -0,0 +1,38 @@
1
+ """UI utilities and primitives for interactive console features.
2
+
3
+ Design goals:
4
+ - Keep import side-effects minimal to avoid circular imports.
5
+ - Make primitives easy to access with lazy attribute loading.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ __all__ = [
11
+ "ElicitationForm",
12
+ "show_simple_elicitation_form",
13
+ "form_dialog",
14
+ "ELICITATION_STYLE",
15
+ ]
16
+
17
+
18
+ def __getattr__(name: str) -> Any:
19
+ """Lazy attribute loader to avoid importing heavy modules at package import time."""
20
+ if name == "ELICITATION_STYLE":
21
+ from .elicitation_style import ELICITATION_STYLE as _STYLE
22
+
23
+ return _STYLE
24
+ if name in ("ElicitationForm", "show_simple_elicitation_form", "form_dialog"):
25
+ from .elicitation_form import (
26
+ ElicitationForm as _Form,
27
+ )
28
+ from .elicitation_form import (
29
+ show_simple_elicitation_form as _show,
30
+ )
31
+
32
+ if name == "ElicitationForm":
33
+ return _Form
34
+ if name == "show_simple_elicitation_form":
35
+ return _show
36
+ if name == "form_dialog":
37
+ return _show
38
+ raise AttributeError(name)