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.
- fast_agent/__init__.py +183 -0
- fast_agent/acp/__init__.py +19 -0
- fast_agent/acp/acp_aware_mixin.py +304 -0
- fast_agent/acp/acp_context.py +437 -0
- fast_agent/acp/content_conversion.py +136 -0
- fast_agent/acp/filesystem_runtime.py +427 -0
- fast_agent/acp/permission_store.py +269 -0
- fast_agent/acp/server/__init__.py +5 -0
- fast_agent/acp/server/agent_acp_server.py +1472 -0
- fast_agent/acp/slash_commands.py +1050 -0
- fast_agent/acp/terminal_runtime.py +408 -0
- fast_agent/acp/tool_permission_adapter.py +125 -0
- fast_agent/acp/tool_permissions.py +474 -0
- fast_agent/acp/tool_progress.py +814 -0
- fast_agent/agents/__init__.py +85 -0
- fast_agent/agents/agent_types.py +64 -0
- fast_agent/agents/llm_agent.py +350 -0
- fast_agent/agents/llm_decorator.py +1139 -0
- fast_agent/agents/mcp_agent.py +1337 -0
- fast_agent/agents/tool_agent.py +271 -0
- fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
- fast_agent/agents/workflow/chain_agent.py +212 -0
- fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
- fast_agent/agents/workflow/iterative_planner.py +652 -0
- fast_agent/agents/workflow/maker_agent.py +379 -0
- fast_agent/agents/workflow/orchestrator_models.py +218 -0
- fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
- fast_agent/agents/workflow/parallel_agent.py +250 -0
- fast_agent/agents/workflow/router_agent.py +353 -0
- fast_agent/cli/__init__.py +0 -0
- fast_agent/cli/__main__.py +73 -0
- fast_agent/cli/commands/acp.py +159 -0
- fast_agent/cli/commands/auth.py +404 -0
- fast_agent/cli/commands/check_config.py +783 -0
- fast_agent/cli/commands/go.py +514 -0
- fast_agent/cli/commands/quickstart.py +557 -0
- fast_agent/cli/commands/serve.py +143 -0
- fast_agent/cli/commands/server_helpers.py +114 -0
- fast_agent/cli/commands/setup.py +174 -0
- fast_agent/cli/commands/url_parser.py +190 -0
- fast_agent/cli/constants.py +40 -0
- fast_agent/cli/main.py +115 -0
- fast_agent/cli/terminal.py +24 -0
- fast_agent/config.py +798 -0
- fast_agent/constants.py +41 -0
- fast_agent/context.py +279 -0
- fast_agent/context_dependent.py +50 -0
- fast_agent/core/__init__.py +92 -0
- fast_agent/core/agent_app.py +448 -0
- fast_agent/core/core_app.py +137 -0
- fast_agent/core/direct_decorators.py +784 -0
- fast_agent/core/direct_factory.py +620 -0
- fast_agent/core/error_handling.py +27 -0
- fast_agent/core/exceptions.py +90 -0
- fast_agent/core/executor/__init__.py +0 -0
- fast_agent/core/executor/executor.py +280 -0
- fast_agent/core/executor/task_registry.py +32 -0
- fast_agent/core/executor/workflow_signal.py +324 -0
- fast_agent/core/fastagent.py +1186 -0
- fast_agent/core/logging/__init__.py +5 -0
- fast_agent/core/logging/events.py +138 -0
- fast_agent/core/logging/json_serializer.py +164 -0
- fast_agent/core/logging/listeners.py +309 -0
- fast_agent/core/logging/logger.py +278 -0
- fast_agent/core/logging/transport.py +481 -0
- fast_agent/core/prompt.py +9 -0
- fast_agent/core/prompt_templates.py +183 -0
- fast_agent/core/validation.py +326 -0
- fast_agent/event_progress.py +62 -0
- fast_agent/history/history_exporter.py +49 -0
- fast_agent/human_input/__init__.py +47 -0
- fast_agent/human_input/elicitation_handler.py +123 -0
- fast_agent/human_input/elicitation_state.py +33 -0
- fast_agent/human_input/form_elements.py +59 -0
- fast_agent/human_input/form_fields.py +256 -0
- fast_agent/human_input/simple_form.py +113 -0
- fast_agent/human_input/types.py +40 -0
- fast_agent/interfaces.py +310 -0
- fast_agent/llm/__init__.py +9 -0
- fast_agent/llm/cancellation.py +22 -0
- fast_agent/llm/fastagent_llm.py +931 -0
- fast_agent/llm/internal/passthrough.py +161 -0
- fast_agent/llm/internal/playback.py +129 -0
- fast_agent/llm/internal/silent.py +41 -0
- fast_agent/llm/internal/slow.py +38 -0
- fast_agent/llm/memory.py +275 -0
- fast_agent/llm/model_database.py +490 -0
- fast_agent/llm/model_factory.py +388 -0
- fast_agent/llm/model_info.py +102 -0
- fast_agent/llm/prompt_utils.py +155 -0
- fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
- fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
- fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
- fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
- fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
- fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
- fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
- fast_agent/llm/provider/google/google_converter.py +466 -0
- fast_agent/llm/provider/google/llm_google_native.py +681 -0
- fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
- fast_agent/llm/provider/openai/llm_azure.py +143 -0
- fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
- fast_agent/llm/provider/openai/llm_generic.py +35 -0
- fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
- fast_agent/llm/provider/openai/llm_groq.py +42 -0
- fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
- fast_agent/llm/provider/openai/llm_openai.py +1195 -0
- fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
- fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
- fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
- fast_agent/llm/provider/openai/llm_xai.py +38 -0
- fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
- fast_agent/llm/provider/openai/openai_multipart.py +169 -0
- fast_agent/llm/provider/openai/openai_utils.py +67 -0
- fast_agent/llm/provider/openai/responses.py +133 -0
- fast_agent/llm/provider_key_manager.py +139 -0
- fast_agent/llm/provider_types.py +34 -0
- fast_agent/llm/request_params.py +61 -0
- fast_agent/llm/sampling_converter.py +98 -0
- fast_agent/llm/stream_types.py +9 -0
- fast_agent/llm/usage_tracking.py +445 -0
- fast_agent/mcp/__init__.py +56 -0
- fast_agent/mcp/common.py +26 -0
- fast_agent/mcp/elicitation_factory.py +84 -0
- fast_agent/mcp/elicitation_handlers.py +164 -0
- fast_agent/mcp/gen_client.py +83 -0
- fast_agent/mcp/helpers/__init__.py +36 -0
- fast_agent/mcp/helpers/content_helpers.py +352 -0
- fast_agent/mcp/helpers/server_config_helpers.py +25 -0
- fast_agent/mcp/hf_auth.py +147 -0
- fast_agent/mcp/interfaces.py +92 -0
- fast_agent/mcp/logger_textio.py +108 -0
- fast_agent/mcp/mcp_agent_client_session.py +411 -0
- fast_agent/mcp/mcp_aggregator.py +2175 -0
- fast_agent/mcp/mcp_connection_manager.py +723 -0
- fast_agent/mcp/mcp_content.py +262 -0
- fast_agent/mcp/mime_utils.py +108 -0
- fast_agent/mcp/oauth_client.py +509 -0
- fast_agent/mcp/prompt.py +159 -0
- fast_agent/mcp/prompt_message_extended.py +155 -0
- fast_agent/mcp/prompt_render.py +84 -0
- fast_agent/mcp/prompt_serialization.py +580 -0
- fast_agent/mcp/prompts/__init__.py +0 -0
- fast_agent/mcp/prompts/__main__.py +7 -0
- fast_agent/mcp/prompts/prompt_constants.py +18 -0
- fast_agent/mcp/prompts/prompt_helpers.py +238 -0
- fast_agent/mcp/prompts/prompt_load.py +186 -0
- fast_agent/mcp/prompts/prompt_server.py +552 -0
- fast_agent/mcp/prompts/prompt_template.py +438 -0
- fast_agent/mcp/resource_utils.py +215 -0
- fast_agent/mcp/sampling.py +200 -0
- fast_agent/mcp/server/__init__.py +4 -0
- fast_agent/mcp/server/agent_server.py +613 -0
- fast_agent/mcp/skybridge.py +44 -0
- fast_agent/mcp/sse_tracking.py +287 -0
- fast_agent/mcp/stdio_tracking_simple.py +59 -0
- fast_agent/mcp/streamable_http_tracking.py +309 -0
- fast_agent/mcp/tool_execution_handler.py +137 -0
- fast_agent/mcp/tool_permission_handler.py +88 -0
- fast_agent/mcp/transport_tracking.py +634 -0
- fast_agent/mcp/types.py +24 -0
- fast_agent/mcp/ui_agent.py +48 -0
- fast_agent/mcp/ui_mixin.py +209 -0
- fast_agent/mcp_server_registry.py +89 -0
- fast_agent/py.typed +0 -0
- fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
- fast_agent/resources/examples/data-analysis/analysis.py +68 -0
- fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
- fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
- fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
- fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
- fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
- fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
- fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
- fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
- fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
- fast_agent/resources/examples/researcher/researcher.py +36 -0
- fast_agent/resources/examples/tensorzero/.env.sample +2 -0
- fast_agent/resources/examples/tensorzero/Makefile +31 -0
- fast_agent/resources/examples/tensorzero/README.md +56 -0
- fast_agent/resources/examples/tensorzero/agent.py +35 -0
- fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
- fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
- fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
- fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
- fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
- fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
- fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
- fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
- fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
- fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
- fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
- fast_agent/resources/examples/workflows/chaining.py +37 -0
- fast_agent/resources/examples/workflows/evaluator.py +77 -0
- fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
- fast_agent/resources/examples/workflows/graded_report.md +89 -0
- fast_agent/resources/examples/workflows/human_input.py +28 -0
- fast_agent/resources/examples/workflows/maker.py +156 -0
- fast_agent/resources/examples/workflows/orchestrator.py +70 -0
- fast_agent/resources/examples/workflows/parallel.py +56 -0
- fast_agent/resources/examples/workflows/router.py +69 -0
- fast_agent/resources/examples/workflows/short_story.md +13 -0
- fast_agent/resources/examples/workflows/short_story.txt +19 -0
- fast_agent/resources/setup/.gitignore +30 -0
- fast_agent/resources/setup/agent.py +28 -0
- fast_agent/resources/setup/fastagent.config.yaml +65 -0
- fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
- fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
- fast_agent/skills/__init__.py +9 -0
- fast_agent/skills/registry.py +235 -0
- fast_agent/tools/elicitation.py +369 -0
- fast_agent/tools/shell_runtime.py +402 -0
- fast_agent/types/__init__.py +59 -0
- fast_agent/types/conversation_summary.py +294 -0
- fast_agent/types/llm_stop_reason.py +78 -0
- fast_agent/types/message_search.py +249 -0
- fast_agent/ui/__init__.py +38 -0
- fast_agent/ui/console.py +59 -0
- fast_agent/ui/console_display.py +1080 -0
- fast_agent/ui/elicitation_form.py +946 -0
- fast_agent/ui/elicitation_style.py +59 -0
- fast_agent/ui/enhanced_prompt.py +1400 -0
- fast_agent/ui/history_display.py +734 -0
- fast_agent/ui/interactive_prompt.py +1199 -0
- fast_agent/ui/markdown_helpers.py +104 -0
- fast_agent/ui/markdown_truncator.py +1004 -0
- fast_agent/ui/mcp_display.py +857 -0
- fast_agent/ui/mcp_ui_utils.py +235 -0
- fast_agent/ui/mermaid_utils.py +169 -0
- fast_agent/ui/message_primitives.py +50 -0
- fast_agent/ui/notification_tracker.py +205 -0
- fast_agent/ui/plain_text_truncator.py +68 -0
- fast_agent/ui/progress_display.py +10 -0
- fast_agent/ui/rich_progress.py +195 -0
- fast_agent/ui/streaming.py +774 -0
- fast_agent/ui/streaming_buffer.py +449 -0
- fast_agent/ui/tool_display.py +422 -0
- fast_agent/ui/usage_display.py +204 -0
- fast_agent/utils/__init__.py +5 -0
- fast_agent/utils/reasoning_stream_parser.py +77 -0
- fast_agent/utils/time.py +22 -0
- fast_agent/workflow_telemetry.py +261 -0
- fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
- fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
- fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
- fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
- fast_agent_mcp-0.4.7.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Custom form elements for elicitation forms."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Sequence, TypeVar
|
|
4
|
+
|
|
5
|
+
from prompt_toolkit.formatted_text import AnyFormattedText
|
|
6
|
+
from prompt_toolkit.validation import ValidationError
|
|
7
|
+
from prompt_toolkit.widgets import CheckboxList
|
|
8
|
+
|
|
9
|
+
_T = TypeVar("_T")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ValidatedCheckboxList(CheckboxList[_T]):
|
|
13
|
+
"""CheckboxList with min/max items validation."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
values: Sequence[tuple[_T, AnyFormattedText]],
|
|
18
|
+
default_values: Optional[Sequence[_T]] = None,
|
|
19
|
+
min_items: Optional[int] = None,
|
|
20
|
+
max_items: Optional[int] = None,
|
|
21
|
+
):
|
|
22
|
+
"""
|
|
23
|
+
Initialize checkbox list with validation.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
values: List of (value, label) tuples
|
|
27
|
+
default_values: Initially selected values
|
|
28
|
+
min_items: Minimum number of items that must be selected
|
|
29
|
+
max_items: Maximum number of items that can be selected
|
|
30
|
+
"""
|
|
31
|
+
super().__init__(values, default_values=default_values)
|
|
32
|
+
self.min_items = min_items
|
|
33
|
+
self.max_items = max_items
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def validation_error(self) -> Optional[ValidationError]:
|
|
37
|
+
"""
|
|
38
|
+
Check if current selection is valid.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
ValidationError if invalid, None if valid
|
|
42
|
+
"""
|
|
43
|
+
selected_count = len(self.current_values)
|
|
44
|
+
|
|
45
|
+
if self.min_items is not None and selected_count < self.min_items:
|
|
46
|
+
if self.min_items == 1:
|
|
47
|
+
message = "At least 1 selection required"
|
|
48
|
+
else:
|
|
49
|
+
message = f"At least {self.min_items} selections required"
|
|
50
|
+
return ValidationError(message=message)
|
|
51
|
+
|
|
52
|
+
if self.max_items is not None and selected_count > self.max_items:
|
|
53
|
+
if self.max_items == 1:
|
|
54
|
+
message = "Only 1 selection allowed"
|
|
55
|
+
else:
|
|
56
|
+
message = f"Maximum {self.max_items} selections allowed"
|
|
57
|
+
return ValidationError(message=message)
|
|
58
|
+
|
|
59
|
+
return None
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""High-level field types for elicitation forms with default support."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Union
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class StringField:
|
|
9
|
+
"""String field with validation and default support."""
|
|
10
|
+
|
|
11
|
+
title: str | None = None
|
|
12
|
+
description: str | None = None
|
|
13
|
+
default: str | None = None
|
|
14
|
+
min_length: int | None = None
|
|
15
|
+
max_length: int | None = None
|
|
16
|
+
pattern: str | None = None
|
|
17
|
+
format: str | None = None # email, uri, date, date-time
|
|
18
|
+
|
|
19
|
+
def to_schema(self) -> dict[str, Any]:
|
|
20
|
+
"""Convert to MCP elicitation schema format."""
|
|
21
|
+
schema: dict[str, Any] = {"type": "string"}
|
|
22
|
+
|
|
23
|
+
if self.title:
|
|
24
|
+
schema["title"] = self.title
|
|
25
|
+
if self.description:
|
|
26
|
+
schema["description"] = self.description
|
|
27
|
+
if self.default is not None:
|
|
28
|
+
schema["default"] = self.default
|
|
29
|
+
if self.min_length is not None:
|
|
30
|
+
schema["minLength"] = self.min_length
|
|
31
|
+
if self.max_length is not None:
|
|
32
|
+
schema["maxLength"] = self.max_length
|
|
33
|
+
if self.pattern is not None:
|
|
34
|
+
schema["pattern"] = self.pattern
|
|
35
|
+
if self.format:
|
|
36
|
+
schema["format"] = self.format
|
|
37
|
+
|
|
38
|
+
return schema
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class IntegerField:
|
|
43
|
+
"""Integer field with validation and default support."""
|
|
44
|
+
|
|
45
|
+
title: str | None = None
|
|
46
|
+
description: str | None = None
|
|
47
|
+
default: int | None = None
|
|
48
|
+
minimum: int | None = None
|
|
49
|
+
maximum: int | None = None
|
|
50
|
+
|
|
51
|
+
def to_schema(self) -> dict[str, Any]:
|
|
52
|
+
"""Convert to MCP elicitation schema format."""
|
|
53
|
+
schema: dict[str, Any] = {"type": "integer"}
|
|
54
|
+
|
|
55
|
+
if self.title:
|
|
56
|
+
schema["title"] = self.title
|
|
57
|
+
if self.description:
|
|
58
|
+
schema["description"] = self.description
|
|
59
|
+
if self.default is not None:
|
|
60
|
+
schema["default"] = self.default
|
|
61
|
+
if self.minimum is not None:
|
|
62
|
+
schema["minimum"] = self.minimum
|
|
63
|
+
if self.maximum is not None:
|
|
64
|
+
schema["maximum"] = self.maximum
|
|
65
|
+
|
|
66
|
+
return schema
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class NumberField:
|
|
71
|
+
"""Number (float) field with validation and default support."""
|
|
72
|
+
|
|
73
|
+
title: str | None = None
|
|
74
|
+
description: str | None = None
|
|
75
|
+
default: float | None = None
|
|
76
|
+
minimum: float | None = None
|
|
77
|
+
maximum: float | None = None
|
|
78
|
+
|
|
79
|
+
def to_schema(self) -> dict[str, Any]:
|
|
80
|
+
"""Convert to MCP elicitation schema format."""
|
|
81
|
+
schema: dict[str, Any] = {"type": "number"}
|
|
82
|
+
|
|
83
|
+
if self.title:
|
|
84
|
+
schema["title"] = self.title
|
|
85
|
+
if self.description:
|
|
86
|
+
schema["description"] = self.description
|
|
87
|
+
if self.default is not None:
|
|
88
|
+
schema["default"] = self.default
|
|
89
|
+
if self.minimum is not None:
|
|
90
|
+
schema["minimum"] = self.minimum
|
|
91
|
+
if self.maximum is not None:
|
|
92
|
+
schema["maximum"] = self.maximum
|
|
93
|
+
|
|
94
|
+
return schema
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class BooleanField:
|
|
99
|
+
"""Boolean field with default support."""
|
|
100
|
+
|
|
101
|
+
title: str | None = None
|
|
102
|
+
description: str | None = None
|
|
103
|
+
default: bool | None = None
|
|
104
|
+
|
|
105
|
+
def to_schema(self) -> dict[str, Any]:
|
|
106
|
+
"""Convert to MCP elicitation schema format."""
|
|
107
|
+
schema: dict[str, Any] = {"type": "boolean"}
|
|
108
|
+
|
|
109
|
+
if self.title:
|
|
110
|
+
schema["title"] = self.title
|
|
111
|
+
if self.description:
|
|
112
|
+
schema["description"] = self.description
|
|
113
|
+
if self.default is not None:
|
|
114
|
+
schema["default"] = self.default
|
|
115
|
+
|
|
116
|
+
return schema
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass
|
|
120
|
+
class EnumField:
|
|
121
|
+
"""Enum/choice field with default support."""
|
|
122
|
+
|
|
123
|
+
choices: list[str]
|
|
124
|
+
choice_names: list[str] | None = None # Human-readable names
|
|
125
|
+
title: str | None = None
|
|
126
|
+
description: str | None = None
|
|
127
|
+
default: str | None = None
|
|
128
|
+
|
|
129
|
+
def to_schema(self) -> dict[str, Any]:
|
|
130
|
+
"""Convert to MCP elicitation schema format."""
|
|
131
|
+
schema: dict[str, Any] = {"type": "string", "enum": self.choices}
|
|
132
|
+
|
|
133
|
+
if self.title:
|
|
134
|
+
schema["title"] = self.title
|
|
135
|
+
if self.description:
|
|
136
|
+
schema["description"] = self.description
|
|
137
|
+
if self.default is not None:
|
|
138
|
+
schema["default"] = self.default
|
|
139
|
+
if self.choice_names:
|
|
140
|
+
schema["enumNames"] = self.choice_names
|
|
141
|
+
|
|
142
|
+
return schema
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# Field type union
|
|
146
|
+
FieldType = Union[StringField, IntegerField, NumberField, BooleanField, EnumField]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class FormSchema:
|
|
150
|
+
"""High-level form schema builder."""
|
|
151
|
+
|
|
152
|
+
def __init__(self, **fields: FieldType):
|
|
153
|
+
"""Create a form schema with named fields."""
|
|
154
|
+
self.fields = fields
|
|
155
|
+
self._required_fields: list[str] = []
|
|
156
|
+
|
|
157
|
+
def required(self, *field_names: str) -> "FormSchema":
|
|
158
|
+
"""Mark fields as required."""
|
|
159
|
+
self._required_fields.extend(field_names)
|
|
160
|
+
return self
|
|
161
|
+
|
|
162
|
+
def to_schema(self) -> dict[str, Any]:
|
|
163
|
+
"""Convert to MCP ElicitRequestedSchema format."""
|
|
164
|
+
properties = {}
|
|
165
|
+
|
|
166
|
+
for field_name, field in self.fields.items():
|
|
167
|
+
properties[field_name] = field.to_schema()
|
|
168
|
+
|
|
169
|
+
schema: dict[str, Any] = {"type": "object", "properties": properties}
|
|
170
|
+
|
|
171
|
+
if self._required_fields:
|
|
172
|
+
schema["required"] = self._required_fields
|
|
173
|
+
|
|
174
|
+
return schema
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# Convenience functions for creating fields
|
|
178
|
+
def string(
|
|
179
|
+
title: str | None = None,
|
|
180
|
+
description: str | None = None,
|
|
181
|
+
default: str | None = None,
|
|
182
|
+
min_length: int | None = None,
|
|
183
|
+
max_length: int | None = None,
|
|
184
|
+
pattern: str | None = None,
|
|
185
|
+
format: str | None = None,
|
|
186
|
+
) -> StringField:
|
|
187
|
+
"""Create a string field."""
|
|
188
|
+
return StringField(title, description, default, min_length, max_length, pattern, format)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def email(
|
|
192
|
+
title: str | None = None, description: str | None = None, default: str | None = None
|
|
193
|
+
) -> StringField:
|
|
194
|
+
"""Create an email field."""
|
|
195
|
+
return StringField(title, description, default, format="email")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def url(
|
|
199
|
+
title: str | None = None, description: str | None = None, default: str | None = None
|
|
200
|
+
) -> StringField:
|
|
201
|
+
"""Create a URL field."""
|
|
202
|
+
return StringField(title, description, default, format="uri")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def date(
|
|
206
|
+
title: str | None = None, description: str | None = None, default: str | None = None
|
|
207
|
+
) -> StringField:
|
|
208
|
+
"""Create a date field."""
|
|
209
|
+
return StringField(title, description, default, format="date")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def datetime(
|
|
213
|
+
title: str | None = None, description: str | None = None, default: str | None = None
|
|
214
|
+
) -> StringField:
|
|
215
|
+
"""Create a datetime field."""
|
|
216
|
+
return StringField(title, description, default, format="date-time")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def integer(
|
|
220
|
+
title: str | None = None,
|
|
221
|
+
description: str | None = None,
|
|
222
|
+
default: int | None = None,
|
|
223
|
+
minimum: int | None = None,
|
|
224
|
+
maximum: int | None = None,
|
|
225
|
+
) -> IntegerField:
|
|
226
|
+
"""Create an integer field."""
|
|
227
|
+
return IntegerField(title, description, default, minimum, maximum)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def number(
|
|
231
|
+
title: str | None = None,
|
|
232
|
+
description: str | None = None,
|
|
233
|
+
default: float | None = None,
|
|
234
|
+
minimum: float | None = None,
|
|
235
|
+
maximum: float | None = None,
|
|
236
|
+
) -> NumberField:
|
|
237
|
+
"""Create a number field."""
|
|
238
|
+
return NumberField(title, description, default, minimum, maximum)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def boolean(
|
|
242
|
+
title: str | None = None, description: str | None = None, default: bool | None = None
|
|
243
|
+
) -> BooleanField:
|
|
244
|
+
"""Create a boolean field."""
|
|
245
|
+
return BooleanField(title, description, default)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def choice(
|
|
249
|
+
choices: list[str],
|
|
250
|
+
choice_names: list[str] | None = None,
|
|
251
|
+
title: str | None = None,
|
|
252
|
+
description: str | None = None,
|
|
253
|
+
default: str | None = None,
|
|
254
|
+
) -> EnumField:
|
|
255
|
+
"""Create a choice/enum field."""
|
|
256
|
+
return EnumField(choices, choice_names, title, description, default)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Simple form API for elicitation schemas without MCP wrappers."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any, Union
|
|
5
|
+
|
|
6
|
+
from mcp.types import ElicitRequestedSchema
|
|
7
|
+
|
|
8
|
+
from fast_agent.human_input.form_fields import FormSchema
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
async def form(
|
|
12
|
+
schema: Union[FormSchema, ElicitRequestedSchema, dict[str, Any]],
|
|
13
|
+
message: str = "Please fill out the form",
|
|
14
|
+
title: str = "Form Input",
|
|
15
|
+
) -> dict[str, Any] | None:
|
|
16
|
+
"""
|
|
17
|
+
Simple form API that presents an elicitation form and returns results.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
schema: FormSchema, ElicitRequestedSchema, or dict schema
|
|
21
|
+
message: Message to display to the user
|
|
22
|
+
title: Title for the form (used as agent_name)
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Dict with form data if accepted, None if cancelled/declined
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
from fast_agent.human_input.form_fields import FormSchema, string, email, integer
|
|
29
|
+
|
|
30
|
+
schema = FormSchema(
|
|
31
|
+
name=string("Name", "Your full name", min_length=2),
|
|
32
|
+
email=email("Email", "Your email address"),
|
|
33
|
+
age=integer("Age", "Your age", minimum=0, maximum=120)
|
|
34
|
+
).required("name", "email")
|
|
35
|
+
|
|
36
|
+
result = await form(schema, "Please enter your information")
|
|
37
|
+
if result:
|
|
38
|
+
print(f"Name: {result['name']}, Email: {result['email']}")
|
|
39
|
+
"""
|
|
40
|
+
# Convert schema to ElicitRequestedSchema format
|
|
41
|
+
if isinstance(schema, FormSchema):
|
|
42
|
+
elicit_schema = schema.to_schema()
|
|
43
|
+
elif isinstance(schema, dict):
|
|
44
|
+
elicit_schema = schema
|
|
45
|
+
else:
|
|
46
|
+
elicit_schema = schema
|
|
47
|
+
|
|
48
|
+
# Import here to avoid import-time cycles via package __init__
|
|
49
|
+
from fast_agent.ui.elicitation_form import show_simple_elicitation_form
|
|
50
|
+
|
|
51
|
+
# Show the form
|
|
52
|
+
action, result = await show_simple_elicitation_form(
|
|
53
|
+
schema=elicit_schema, message=message, agent_name=title, server_name="SimpleForm"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Return results based on action
|
|
57
|
+
if action == "accept":
|
|
58
|
+
return result
|
|
59
|
+
else:
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def form_sync(
|
|
64
|
+
schema: Union[FormSchema, ElicitRequestedSchema, dict[str, Any]],
|
|
65
|
+
message: str = "Please fill out the form",
|
|
66
|
+
title: str = "Form Input",
|
|
67
|
+
) -> dict[str, Any] | None:
|
|
68
|
+
"""
|
|
69
|
+
Synchronous wrapper for the form function.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
schema: FormSchema, ElicitRequestedSchema, or dict schema
|
|
73
|
+
message: Message to display to the user
|
|
74
|
+
title: Title for the form (used as agent_name)
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Dict with form data if accepted, None if cancelled/declined
|
|
78
|
+
"""
|
|
79
|
+
return asyncio.run(form(schema, message, title))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Convenience function with a shorter name
|
|
83
|
+
async def ask(
|
|
84
|
+
schema: Union[FormSchema, ElicitRequestedSchema, dict[str, Any]],
|
|
85
|
+
message: str = "Please provide the requested information",
|
|
86
|
+
) -> dict[str, Any] | None:
|
|
87
|
+
"""
|
|
88
|
+
Short alias for form() function.
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
from fast_agent.human_input.form_fields import FormSchema, string, email
|
|
92
|
+
|
|
93
|
+
schema = FormSchema(
|
|
94
|
+
name=string("Name", "Your name"),
|
|
95
|
+
email=email("Email", "Your email")
|
|
96
|
+
).required("name")
|
|
97
|
+
|
|
98
|
+
result = await ask(schema, "What's your info?")
|
|
99
|
+
"""
|
|
100
|
+
return await form(schema, message)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def ask_sync(
|
|
104
|
+
schema: Union[FormSchema, ElicitRequestedSchema, dict[str, Any]],
|
|
105
|
+
message: str = "Please provide the requested information",
|
|
106
|
+
) -> dict[str, Any] | None:
|
|
107
|
+
"""
|
|
108
|
+
Synchronous version of ask().
|
|
109
|
+
|
|
110
|
+
Example:
|
|
111
|
+
result = ask_sync(schema, "What's your info?")
|
|
112
|
+
"""
|
|
113
|
+
return form_sync(schema, message)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
HUMAN_INPUT_SIGNAL_NAME = "__human_input__"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HumanInputRequest(BaseModel):
|
|
9
|
+
"""Represents a request for human input."""
|
|
10
|
+
|
|
11
|
+
prompt: str
|
|
12
|
+
"""The prompt to show to the user"""
|
|
13
|
+
|
|
14
|
+
description: str | None = None
|
|
15
|
+
"""Optional description of what the input is for"""
|
|
16
|
+
|
|
17
|
+
request_id: str | None = None
|
|
18
|
+
"""Unique identifier for this request"""
|
|
19
|
+
|
|
20
|
+
workflow_id: str | None = None
|
|
21
|
+
"""Optional workflow ID if using workflow engine"""
|
|
22
|
+
|
|
23
|
+
timeout_seconds: int | None = None
|
|
24
|
+
"""Optional timeout in seconds"""
|
|
25
|
+
|
|
26
|
+
metadata: dict | None = None
|
|
27
|
+
"""Additional request payload"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class HumanInputResponse(BaseModel):
|
|
31
|
+
"""Represents a response to a human input request"""
|
|
32
|
+
|
|
33
|
+
request_id: str
|
|
34
|
+
"""ID of the original request"""
|
|
35
|
+
|
|
36
|
+
response: str
|
|
37
|
+
"""The input provided by the human"""
|
|
38
|
+
|
|
39
|
+
metadata: dict[str, Any] | None = None
|
|
40
|
+
"""Additional response payload"""
|