fast-agent-mcp 0.2.57__py3-none-any.whl → 0.3.0__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.
- fast_agent/__init__.py +127 -0
- fast_agent/agents/__init__.py +36 -0
- {mcp_agent/core → fast_agent/agents}/agent_types.py +2 -1
- fast_agent/agents/llm_agent.py +217 -0
- fast_agent/agents/llm_decorator.py +486 -0
- mcp_agent/agents/base_agent.py → fast_agent/agents/mcp_agent.py +377 -385
- fast_agent/agents/tool_agent.py +168 -0
- {mcp_agent → fast_agent}/agents/workflow/chain_agent.py +43 -33
- {mcp_agent → fast_agent}/agents/workflow/evaluator_optimizer.py +31 -35
- {mcp_agent → fast_agent}/agents/workflow/iterative_planner.py +56 -47
- {mcp_agent → fast_agent}/agents/workflow/orchestrator_models.py +4 -4
- {mcp_agent → fast_agent}/agents/workflow/parallel_agent.py +34 -41
- {mcp_agent → fast_agent}/agents/workflow/router_agent.py +54 -39
- {mcp_agent → fast_agent}/cli/__main__.py +5 -3
- {mcp_agent → fast_agent}/cli/commands/check_config.py +95 -66
- {mcp_agent → fast_agent}/cli/commands/go.py +20 -11
- {mcp_agent → fast_agent}/cli/commands/quickstart.py +4 -4
- {mcp_agent → fast_agent}/cli/commands/server_helpers.py +1 -1
- {mcp_agent → fast_agent}/cli/commands/setup.py +64 -134
- {mcp_agent → fast_agent}/cli/commands/url_parser.py +9 -8
- {mcp_agent → fast_agent}/cli/main.py +36 -16
- {mcp_agent → fast_agent}/cli/terminal.py +2 -2
- {mcp_agent → fast_agent}/config.py +13 -2
- fast_agent/constants.py +8 -0
- {mcp_agent → fast_agent}/context.py +24 -19
- {mcp_agent → fast_agent}/context_dependent.py +9 -5
- fast_agent/core/__init__.py +17 -0
- {mcp_agent → fast_agent}/core/agent_app.py +39 -36
- fast_agent/core/core_app.py +135 -0
- {mcp_agent → fast_agent}/core/direct_decorators.py +12 -26
- {mcp_agent → fast_agent}/core/direct_factory.py +95 -73
- {mcp_agent → fast_agent/core}/executor/executor.py +4 -5
- {mcp_agent → fast_agent}/core/fastagent.py +32 -32
- fast_agent/core/logging/__init__.py +5 -0
- {mcp_agent → fast_agent/core}/logging/events.py +3 -3
- {mcp_agent → fast_agent/core}/logging/json_serializer.py +1 -1
- {mcp_agent → fast_agent/core}/logging/listeners.py +85 -7
- {mcp_agent → fast_agent/core}/logging/logger.py +7 -7
- {mcp_agent → fast_agent/core}/logging/transport.py +10 -11
- fast_agent/core/prompt.py +9 -0
- {mcp_agent → fast_agent}/core/validation.py +4 -4
- fast_agent/event_progress.py +61 -0
- fast_agent/history/history_exporter.py +44 -0
- {mcp_agent → fast_agent}/human_input/__init__.py +9 -12
- {mcp_agent → fast_agent}/human_input/elicitation_handler.py +26 -8
- {mcp_agent → fast_agent}/human_input/elicitation_state.py +7 -7
- {mcp_agent → fast_agent}/human_input/simple_form.py +6 -4
- {mcp_agent → fast_agent}/human_input/types.py +1 -18
- fast_agent/interfaces.py +228 -0
- fast_agent/llm/__init__.py +9 -0
- mcp_agent/llm/augmented_llm.py → fast_agent/llm/fastagent_llm.py +128 -218
- fast_agent/llm/internal/passthrough.py +137 -0
- mcp_agent/llm/augmented_llm_playback.py → fast_agent/llm/internal/playback.py +29 -25
- mcp_agent/llm/augmented_llm_silent.py → fast_agent/llm/internal/silent.py +10 -17
- fast_agent/llm/internal/slow.py +38 -0
- {mcp_agent → fast_agent}/llm/memory.py +40 -30
- {mcp_agent → fast_agent}/llm/model_database.py +35 -2
- {mcp_agent → fast_agent}/llm/model_factory.py +103 -77
- fast_agent/llm/model_info.py +126 -0
- {mcp_agent/llm/providers → fast_agent/llm/provider/anthropic}/anthropic_utils.py +7 -7
- fast_agent/llm/provider/anthropic/llm_anthropic.py +603 -0
- {mcp_agent/llm/providers → fast_agent/llm/provider/anthropic}/multipart_converter_anthropic.py +79 -86
- fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
- fast_agent/llm/provider/bedrock/llm_bedrock.py +2192 -0
- {mcp_agent/llm/providers → fast_agent/llm/provider/google}/google_converter.py +66 -14
- fast_agent/llm/provider/google/llm_google_native.py +431 -0
- mcp_agent/llm/providers/augmented_llm_aliyun.py → fast_agent/llm/provider/openai/llm_aliyun.py +6 -7
- mcp_agent/llm/providers/augmented_llm_azure.py → fast_agent/llm/provider/openai/llm_azure.py +4 -4
- mcp_agent/llm/providers/augmented_llm_deepseek.py → fast_agent/llm/provider/openai/llm_deepseek.py +10 -11
- mcp_agent/llm/providers/augmented_llm_generic.py → fast_agent/llm/provider/openai/llm_generic.py +4 -4
- mcp_agent/llm/providers/augmented_llm_google_oai.py → fast_agent/llm/provider/openai/llm_google_oai.py +4 -4
- mcp_agent/llm/providers/augmented_llm_groq.py → fast_agent/llm/provider/openai/llm_groq.py +14 -16
- mcp_agent/llm/providers/augmented_llm_openai.py → fast_agent/llm/provider/openai/llm_openai.py +133 -206
- mcp_agent/llm/providers/augmented_llm_openrouter.py → fast_agent/llm/provider/openai/llm_openrouter.py +6 -6
- mcp_agent/llm/providers/augmented_llm_tensorzero_openai.py → fast_agent/llm/provider/openai/llm_tensorzero_openai.py +17 -16
- mcp_agent/llm/providers/augmented_llm_xai.py → fast_agent/llm/provider/openai/llm_xai.py +6 -6
- {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/multipart_converter_openai.py +125 -63
- {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/openai_multipart.py +12 -12
- {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/openai_utils.py +18 -16
- {mcp_agent → fast_agent}/llm/provider_key_manager.py +2 -2
- {mcp_agent → fast_agent}/llm/provider_types.py +2 -0
- {mcp_agent → fast_agent}/llm/sampling_converter.py +15 -12
- {mcp_agent → fast_agent}/llm/usage_tracking.py +23 -5
- fast_agent/mcp/__init__.py +43 -0
- {mcp_agent → fast_agent}/mcp/elicitation_factory.py +3 -3
- {mcp_agent → fast_agent}/mcp/elicitation_handlers.py +19 -10
- {mcp_agent → fast_agent}/mcp/gen_client.py +3 -3
- fast_agent/mcp/helpers/__init__.py +36 -0
- fast_agent/mcp/helpers/content_helpers.py +183 -0
- {mcp_agent → fast_agent}/mcp/helpers/server_config_helpers.py +8 -8
- {mcp_agent → fast_agent}/mcp/hf_auth.py +25 -23
- fast_agent/mcp/interfaces.py +93 -0
- {mcp_agent → fast_agent}/mcp/logger_textio.py +4 -4
- {mcp_agent → fast_agent}/mcp/mcp_agent_client_session.py +49 -44
- {mcp_agent → fast_agent}/mcp/mcp_aggregator.py +66 -115
- {mcp_agent → fast_agent}/mcp/mcp_connection_manager.py +16 -23
- {mcp_agent/core → fast_agent/mcp}/mcp_content.py +23 -15
- {mcp_agent → fast_agent}/mcp/mime_utils.py +39 -0
- fast_agent/mcp/prompt.py +159 -0
- mcp_agent/mcp/prompt_message_multipart.py → fast_agent/mcp/prompt_message_extended.py +27 -20
- {mcp_agent → fast_agent}/mcp/prompt_render.py +21 -19
- {mcp_agent → fast_agent}/mcp/prompt_serialization.py +46 -46
- fast_agent/mcp/prompts/__main__.py +7 -0
- {mcp_agent → fast_agent}/mcp/prompts/prompt_helpers.py +31 -30
- {mcp_agent → fast_agent}/mcp/prompts/prompt_load.py +8 -8
- {mcp_agent → fast_agent}/mcp/prompts/prompt_server.py +11 -19
- {mcp_agent → fast_agent}/mcp/prompts/prompt_template.py +18 -18
- {mcp_agent → fast_agent}/mcp/resource_utils.py +1 -1
- {mcp_agent → fast_agent}/mcp/sampling.py +31 -26
- {mcp_agent/mcp_server → fast_agent/mcp/server}/__init__.py +1 -1
- {mcp_agent/mcp_server → fast_agent/mcp/server}/agent_server.py +5 -6
- fast_agent/mcp/ui_agent.py +48 -0
- fast_agent/mcp/ui_mixin.py +209 -0
- fast_agent/mcp_server_registry.py +90 -0
- {mcp_agent → fast_agent}/resources/examples/data-analysis/analysis-campaign.py +5 -4
- {mcp_agent → fast_agent}/resources/examples/data-analysis/analysis.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_forms_server.py +25 -3
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/forms_demo.py +3 -3
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character.py +2 -2
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character_handler.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/tool_call.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_one.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_two.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/researcher/researcher-eval.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/researcher/researcher-imp.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/researcher/researcher.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/tensorzero/agent.py +2 -2
- {mcp_agent → fast_agent}/resources/examples/tensorzero/image_demo.py +3 -3
- {mcp_agent → fast_agent}/resources/examples/tensorzero/simple_agent.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/workflows/chaining.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/workflows/evaluator.py +3 -3
- {mcp_agent → fast_agent}/resources/examples/workflows/human_input.py +5 -3
- {mcp_agent → fast_agent}/resources/examples/workflows/orchestrator.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/workflows/parallel.py +2 -2
- {mcp_agent → fast_agent}/resources/examples/workflows/router.py +5 -2
- fast_agent/resources/setup/.gitignore +24 -0
- fast_agent/resources/setup/agent.py +18 -0
- fast_agent/resources/setup/fastagent.config.yaml +44 -0
- fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
- fast_agent/tools/elicitation.py +369 -0
- fast_agent/types/__init__.py +32 -0
- fast_agent/types/llm_stop_reason.py +77 -0
- fast_agent/ui/__init__.py +38 -0
- fast_agent/ui/console_display.py +1005 -0
- {mcp_agent/human_input → fast_agent/ui}/elicitation_form.py +56 -39
- mcp_agent/human_input/elicitation_forms.py → fast_agent/ui/elicitation_style.py +1 -1
- {mcp_agent/core → fast_agent/ui}/enhanced_prompt.py +96 -25
- {mcp_agent/core → fast_agent/ui}/interactive_prompt.py +330 -125
- fast_agent/ui/mcp_ui_utils.py +224 -0
- {mcp_agent → fast_agent/ui}/progress_display.py +2 -2
- {mcp_agent/logging → fast_agent/ui}/rich_progress.py +4 -4
- {mcp_agent/core → fast_agent/ui}/usage_display.py +3 -8
- {fast_agent_mcp-0.2.57.dist-info → fast_agent_mcp-0.3.0.dist-info}/METADATA +7 -7
- fast_agent_mcp-0.3.0.dist-info/RECORD +202 -0
- fast_agent_mcp-0.3.0.dist-info/entry_points.txt +5 -0
- fast_agent_mcp-0.2.57.dist-info/RECORD +0 -192
- fast_agent_mcp-0.2.57.dist-info/entry_points.txt +0 -6
- mcp_agent/__init__.py +0 -114
- mcp_agent/agents/agent.py +0 -92
- mcp_agent/agents/workflow/__init__.py +0 -1
- mcp_agent/agents/workflow/orchestrator_agent.py +0 -597
- mcp_agent/app.py +0 -175
- mcp_agent/core/__init__.py +0 -26
- mcp_agent/core/prompt.py +0 -191
- mcp_agent/event_progress.py +0 -134
- mcp_agent/human_input/handler.py +0 -81
- mcp_agent/llm/__init__.py +0 -2
- mcp_agent/llm/augmented_llm_passthrough.py +0 -232
- mcp_agent/llm/augmented_llm_slow.py +0 -53
- mcp_agent/llm/providers/__init__.py +0 -8
- mcp_agent/llm/providers/augmented_llm_anthropic.py +0 -717
- mcp_agent/llm/providers/augmented_llm_bedrock.py +0 -1788
- mcp_agent/llm/providers/augmented_llm_google_native.py +0 -495
- mcp_agent/llm/providers/sampling_converter_anthropic.py +0 -57
- mcp_agent/llm/providers/sampling_converter_openai.py +0 -26
- mcp_agent/llm/sampling_format_converter.py +0 -37
- mcp_agent/logging/__init__.py +0 -0
- mcp_agent/mcp/__init__.py +0 -50
- mcp_agent/mcp/helpers/__init__.py +0 -25
- mcp_agent/mcp/helpers/content_helpers.py +0 -187
- mcp_agent/mcp/interfaces.py +0 -266
- mcp_agent/mcp/prompts/__init__.py +0 -0
- mcp_agent/mcp/prompts/__main__.py +0 -10
- mcp_agent/mcp_server_registry.py +0 -343
- mcp_agent/tools/tool_definition.py +0 -14
- mcp_agent/ui/console_display.py +0 -790
- mcp_agent/ui/console_display_legacy.py +0 -401
- {mcp_agent → fast_agent}/agents/workflow/orchestrator_prompts.py +0 -0
- {mcp_agent/agents → fast_agent/cli}/__init__.py +0 -0
- {mcp_agent → fast_agent}/cli/constants.py +0 -0
- {mcp_agent → fast_agent}/core/error_handling.py +0 -0
- {mcp_agent → fast_agent}/core/exceptions.py +0 -0
- {mcp_agent/cli → fast_agent/core/executor}/__init__.py +0 -0
- {mcp_agent → fast_agent/core}/executor/task_registry.py +0 -0
- {mcp_agent → fast_agent/core}/executor/workflow_signal.py +0 -0
- {mcp_agent → fast_agent}/human_input/form_fields.py +0 -0
- {mcp_agent → fast_agent}/llm/prompt_utils.py +0 -0
- {mcp_agent/core → fast_agent/llm}/request_params.py +0 -0
- {mcp_agent → fast_agent}/mcp/common.py +0 -0
- {mcp_agent/executor → fast_agent/mcp/prompts}/__init__.py +0 -0
- {mcp_agent → fast_agent}/mcp/prompts/prompt_constants.py +0 -0
- {mcp_agent → fast_agent}/py.typed +0 -0
- {mcp_agent → fast_agent}/resources/examples/data-analysis/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_account_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_game_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +0 -0
- {mcp_agent → fast_agent}/resources/examples/researcher/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/.env.sample +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/Makefile +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/README.md +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/crab.png +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/docker-compose.yml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/Dockerfile +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/entrypoint.sh +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/mcp_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/pyproject.toml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/system_schema.json +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/graded_report.md +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/short_story.md +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/short_story.txt +0 -0
- {mcp_agent → fast_agent/ui}/console.py +0 -0
- {mcp_agent/core → fast_agent/ui}/mermaid_utils.py +0 -0
- {fast_agent_mcp-0.2.57.dist-info → fast_agent_mcp-0.3.0.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.57.dist-info → fast_agent_mcp-0.3.0.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)
|