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
fast_agent/config.py
ADDED
|
@@ -0,0 +1,798 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Reading settings from environment variables and providing a settings object
|
|
3
|
+
for the application configuration.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
10
|
+
|
|
11
|
+
# Importing the MCP Implementation type eagerly pulls in the full MCP server
|
|
12
|
+
# stack (uvicorn, Starlette, etc.) which slows down startup. We only need the
|
|
13
|
+
# type for annotations, so avoid the runtime import.
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from mcp import Implementation
|
|
16
|
+
else: # pragma: no cover - used only to satisfy type checkers
|
|
17
|
+
Implementation = Any
|
|
18
|
+
from pydantic import BaseModel, ConfigDict, field_validator, model_validator
|
|
19
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MCPServerAuthSettings(BaseModel):
|
|
23
|
+
"""Represents authentication configuration for a server.
|
|
24
|
+
|
|
25
|
+
Minimal OAuth v2.1 support with sensible defaults.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# Enable OAuth for SSE/HTTP transports. If None is provided for the auth block,
|
|
29
|
+
# the system will assume OAuth is enabled by default.
|
|
30
|
+
oauth: bool = True
|
|
31
|
+
|
|
32
|
+
# Local callback server configuration
|
|
33
|
+
redirect_port: int = 3030
|
|
34
|
+
redirect_path: str = "/callback"
|
|
35
|
+
|
|
36
|
+
# Optional scope override. If set to a list, values are space-joined.
|
|
37
|
+
scope: str | list[str] | None = None
|
|
38
|
+
|
|
39
|
+
# Token persistence: use OS keychain via 'keyring' by default; fallback to 'memory'.
|
|
40
|
+
persist: Literal["keyring", "memory"] = "keyring"
|
|
41
|
+
|
|
42
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class MCPSamplingSettings(BaseModel):
|
|
46
|
+
model: str = "gpt-5-mini.low"
|
|
47
|
+
|
|
48
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class MCPElicitationSettings(BaseModel):
|
|
52
|
+
mode: Literal["forms", "auto-cancel", "none"] = "none"
|
|
53
|
+
"""Elicitation mode: 'forms' (default UI), 'auto-cancel', 'none' (no capability)"""
|
|
54
|
+
|
|
55
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class MCPTimelineSettings(BaseModel):
|
|
59
|
+
"""Configuration for MCP activity timeline display."""
|
|
60
|
+
|
|
61
|
+
steps: int = 20
|
|
62
|
+
"""Number of timeline buckets to render."""
|
|
63
|
+
|
|
64
|
+
step_seconds: int = 30
|
|
65
|
+
"""Duration of each timeline bucket in seconds."""
|
|
66
|
+
|
|
67
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def _parse_duration(value: str) -> int:
|
|
71
|
+
"""Parse simple duration strings like '30s', '2m', '1h' into seconds."""
|
|
72
|
+
pattern = re.compile(r"^\s*(\d+)\s*([smhd]?)\s*$", re.IGNORECASE)
|
|
73
|
+
match = pattern.match(value)
|
|
74
|
+
if not match:
|
|
75
|
+
raise ValueError("Expected duration in seconds (e.g. 30, '45s', '2m').")
|
|
76
|
+
amount = int(match.group(1))
|
|
77
|
+
unit = match.group(2).lower()
|
|
78
|
+
multiplier = {
|
|
79
|
+
"": 1,
|
|
80
|
+
"s": 1,
|
|
81
|
+
"m": 60,
|
|
82
|
+
"h": 3600,
|
|
83
|
+
"d": 86400,
|
|
84
|
+
}.get(unit)
|
|
85
|
+
if multiplier is None:
|
|
86
|
+
raise ValueError("Duration unit must be one of s, m, h, or d.")
|
|
87
|
+
return amount * multiplier
|
|
88
|
+
|
|
89
|
+
@field_validator("steps", mode="before")
|
|
90
|
+
@classmethod
|
|
91
|
+
def _coerce_steps(cls, value: Any) -> int:
|
|
92
|
+
if isinstance(value, str):
|
|
93
|
+
if not value.strip().isdigit():
|
|
94
|
+
raise ValueError("Timeline steps must be a positive integer.")
|
|
95
|
+
value = int(value.strip())
|
|
96
|
+
elif isinstance(value, float):
|
|
97
|
+
value = int(value)
|
|
98
|
+
if not isinstance(value, int):
|
|
99
|
+
raise TypeError("Timeline steps must be an integer.")
|
|
100
|
+
if value <= 0:
|
|
101
|
+
raise ValueError("Timeline steps must be greater than zero.")
|
|
102
|
+
return value
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class SkillsSettings(BaseModel):
|
|
106
|
+
"""Configuration for the skills directory override."""
|
|
107
|
+
|
|
108
|
+
directory: str | None = None
|
|
109
|
+
|
|
110
|
+
model_config = ConfigDict(extra="ignore")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class ShellSettings(BaseModel):
|
|
114
|
+
"""Configuration for shell execution behavior."""
|
|
115
|
+
|
|
116
|
+
timeout_seconds: int = 90
|
|
117
|
+
"""Maximum seconds to wait for command output before terminating (default: 90s)"""
|
|
118
|
+
|
|
119
|
+
warning_interval_seconds: int = 30
|
|
120
|
+
"""Show timeout warnings every N seconds (default: 30s)"""
|
|
121
|
+
|
|
122
|
+
model_config = ConfigDict(extra="ignore")
|
|
123
|
+
|
|
124
|
+
@field_validator("timeout_seconds", mode="before")
|
|
125
|
+
@classmethod
|
|
126
|
+
def _coerce_timeout(cls, value: Any) -> int:
|
|
127
|
+
"""Support duration strings like '90s', '2m', '1h'"""
|
|
128
|
+
if isinstance(value, str):
|
|
129
|
+
return MCPTimelineSettings._parse_duration(value)
|
|
130
|
+
return int(value)
|
|
131
|
+
|
|
132
|
+
@field_validator("warning_interval_seconds", mode="before")
|
|
133
|
+
@classmethod
|
|
134
|
+
def _coerce_warning_interval(cls, value: Any) -> int:
|
|
135
|
+
"""Support duration strings like '30s', '1m'"""
|
|
136
|
+
if isinstance(value, str):
|
|
137
|
+
return MCPTimelineSettings._parse_duration(value)
|
|
138
|
+
return int(value)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class MCPRootSettings(BaseModel):
|
|
142
|
+
"""Represents a root directory configuration for an MCP server."""
|
|
143
|
+
|
|
144
|
+
uri: str
|
|
145
|
+
"""The URI identifying the root. Must start with file://"""
|
|
146
|
+
|
|
147
|
+
name: str | None = None
|
|
148
|
+
"""Optional name for the root."""
|
|
149
|
+
|
|
150
|
+
server_uri_alias: str | None = None
|
|
151
|
+
"""Optional URI alias for presentation to the server"""
|
|
152
|
+
|
|
153
|
+
@field_validator("uri", "server_uri_alias")
|
|
154
|
+
@classmethod
|
|
155
|
+
def validate_uri(cls, v: str) -> str:
|
|
156
|
+
"""Validate that the URI starts with file:// (required by specification 2024-11-05)"""
|
|
157
|
+
if v and not v.startswith("file://"):
|
|
158
|
+
raise ValueError("Root URI must start with file://")
|
|
159
|
+
return v
|
|
160
|
+
|
|
161
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class MCPServerSettings(BaseModel):
|
|
165
|
+
"""
|
|
166
|
+
Represents the configuration for an individual server.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
name: str | None = None
|
|
170
|
+
"""The name of the server."""
|
|
171
|
+
|
|
172
|
+
description: str | None = None
|
|
173
|
+
"""The description of the server."""
|
|
174
|
+
|
|
175
|
+
transport: Literal["stdio", "sse", "http"] = "stdio"
|
|
176
|
+
"""The transport mechanism."""
|
|
177
|
+
|
|
178
|
+
command: str | None = None
|
|
179
|
+
"""The command to execute the server (e.g. npx)."""
|
|
180
|
+
|
|
181
|
+
args: list[str] | None = None
|
|
182
|
+
"""The arguments for the server command."""
|
|
183
|
+
|
|
184
|
+
read_timeout_seconds: int | None = None
|
|
185
|
+
"""The timeout in seconds for the session."""
|
|
186
|
+
|
|
187
|
+
read_transport_sse_timeout_seconds: int = 300
|
|
188
|
+
"""The timeout in seconds for the server connection."""
|
|
189
|
+
|
|
190
|
+
url: str | None = None
|
|
191
|
+
"""The URL for the server (e.g. for SSE/SHTTP transport)."""
|
|
192
|
+
|
|
193
|
+
headers: dict[str, str] | None = None
|
|
194
|
+
"""Headers dictionary for HTTP connections"""
|
|
195
|
+
|
|
196
|
+
auth: MCPServerAuthSettings | None = None
|
|
197
|
+
"""The authentication configuration for the server."""
|
|
198
|
+
|
|
199
|
+
roots: list[MCPRootSettings] | None = None
|
|
200
|
+
"""Root directories this server has access to."""
|
|
201
|
+
|
|
202
|
+
env: dict[str, str] | None = None
|
|
203
|
+
"""Environment variables to pass to the server process."""
|
|
204
|
+
|
|
205
|
+
sampling: MCPSamplingSettings | None = None
|
|
206
|
+
"""Sampling settings for this Client/Server pair"""
|
|
207
|
+
|
|
208
|
+
elicitation: MCPElicitationSettings | None = None
|
|
209
|
+
"""Elicitation settings for this Client/Server pair"""
|
|
210
|
+
|
|
211
|
+
cwd: str | None = None
|
|
212
|
+
"""Working directory for the executed server command."""
|
|
213
|
+
|
|
214
|
+
load_on_start: bool = True
|
|
215
|
+
"""Whether to connect to this server automatically when the agent starts."""
|
|
216
|
+
|
|
217
|
+
include_instructions: bool = True
|
|
218
|
+
"""Whether to include this server's instructions in the system prompt (default: True)."""
|
|
219
|
+
|
|
220
|
+
reconnect_on_disconnect: bool = True
|
|
221
|
+
"""Whether to automatically reconnect when the server session is terminated (e.g., 404).
|
|
222
|
+
|
|
223
|
+
When enabled, if a remote StreamableHTTP server returns a 404 indicating the session
|
|
224
|
+
has been terminated (e.g., due to server restart), the client will automatically
|
|
225
|
+
attempt to re-initialize the connection and retry the operation.
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
implementation: Implementation | None = None
|
|
229
|
+
|
|
230
|
+
@model_validator(mode="before")
|
|
231
|
+
@classmethod
|
|
232
|
+
def validate_transport_inference(cls, values):
|
|
233
|
+
"""Automatically infer transport type based on url/command presence."""
|
|
234
|
+
import warnings
|
|
235
|
+
|
|
236
|
+
if isinstance(values, dict):
|
|
237
|
+
# Check if transport was explicitly provided in the input
|
|
238
|
+
transport_explicit = "transport" in values
|
|
239
|
+
url = values.get("url")
|
|
240
|
+
command = values.get("command")
|
|
241
|
+
|
|
242
|
+
# Only infer if transport was not explicitly set
|
|
243
|
+
if not transport_explicit:
|
|
244
|
+
# Check if we have both url and command specified
|
|
245
|
+
has_url = url is not None and str(url).strip()
|
|
246
|
+
has_command = command is not None and str(command).strip()
|
|
247
|
+
|
|
248
|
+
if has_url and has_command:
|
|
249
|
+
warnings.warn(
|
|
250
|
+
f"MCP Server config has both 'url' ({url}) and 'command' ({command}) specified. "
|
|
251
|
+
"Preferring HTTP transport and ignoring command.",
|
|
252
|
+
UserWarning,
|
|
253
|
+
stacklevel=4,
|
|
254
|
+
)
|
|
255
|
+
values["transport"] = "http"
|
|
256
|
+
values["command"] = None # Clear command to avoid confusion
|
|
257
|
+
elif has_url and not has_command:
|
|
258
|
+
values["transport"] = "http"
|
|
259
|
+
elif has_command and not has_url:
|
|
260
|
+
# Keep default "stdio" for command-based servers
|
|
261
|
+
values["transport"] = "stdio"
|
|
262
|
+
# If neither url nor command is specified, keep default "stdio"
|
|
263
|
+
|
|
264
|
+
return values
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class MCPSettings(BaseModel):
|
|
268
|
+
"""Configuration for all MCP servers."""
|
|
269
|
+
|
|
270
|
+
servers: dict[str, MCPServerSettings] = {}
|
|
271
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class AnthropicSettings(BaseModel):
|
|
275
|
+
"""
|
|
276
|
+
Settings for using Anthropic models in the fast-agent application.
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
api_key: str | None = None
|
|
280
|
+
|
|
281
|
+
base_url: str | None = None
|
|
282
|
+
|
|
283
|
+
cache_mode: Literal["off", "prompt", "auto"] = "auto"
|
|
284
|
+
"""
|
|
285
|
+
Controls how caching is applied for Anthropic models when prompt_caching is enabled globally.
|
|
286
|
+
- "off": No caching, even if global prompt_caching is true.
|
|
287
|
+
- "prompt": Caches tools+system prompt (1 block) and template content. Useful for large, static prompts.
|
|
288
|
+
- "auto": Currently same as "prompt" - caches tools+system prompt (1 block) and template content.
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class OpenAISettings(BaseModel):
|
|
295
|
+
"""
|
|
296
|
+
Settings for using OpenAI models in the fast-agent application.
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
api_key: str | None = None
|
|
300
|
+
reasoning_effort: Literal["minimal", "low", "medium", "high"] = "medium"
|
|
301
|
+
|
|
302
|
+
base_url: str | None = None
|
|
303
|
+
|
|
304
|
+
default_headers: dict[str, str] | None = None
|
|
305
|
+
"""Custom headers to include in all requests to the OpenAI API."""
|
|
306
|
+
|
|
307
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class DeepSeekSettings(BaseModel):
|
|
311
|
+
"""
|
|
312
|
+
Settings for using DeepSeek models in the fast-agent application.
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
api_key: str | None = None
|
|
316
|
+
# reasoning_effort: Literal["low", "medium", "high"] = "medium"
|
|
317
|
+
|
|
318
|
+
base_url: str | None = None
|
|
319
|
+
|
|
320
|
+
default_headers: dict[str, str] | None = None
|
|
321
|
+
"""Custom headers to include in all requests to the DeepSeek API."""
|
|
322
|
+
|
|
323
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class GoogleSettings(BaseModel):
|
|
327
|
+
"""
|
|
328
|
+
Settings for using Google models (via OpenAI-compatible API) in the fast-agent application.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
api_key: str | None = None
|
|
332
|
+
# reasoning_effort: Literal["low", "medium", "high"] = "medium"
|
|
333
|
+
|
|
334
|
+
base_url: str | None = None
|
|
335
|
+
|
|
336
|
+
default_headers: dict[str, str] | None = None
|
|
337
|
+
"""Custom headers to include in all requests to the Google API."""
|
|
338
|
+
|
|
339
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class XAISettings(BaseModel):
|
|
343
|
+
"""
|
|
344
|
+
Settings for using xAI Grok models in the fast-agent application.
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
api_key: str | None = None
|
|
348
|
+
base_url: str | None = "https://api.x.ai/v1"
|
|
349
|
+
|
|
350
|
+
default_headers: dict[str, str] | None = None
|
|
351
|
+
"""Custom headers to include in all requests to the xAI API."""
|
|
352
|
+
|
|
353
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class GenericSettings(BaseModel):
|
|
357
|
+
"""
|
|
358
|
+
Settings for using generic OpenAI-compatible models in the fast-agent application.
|
|
359
|
+
"""
|
|
360
|
+
|
|
361
|
+
api_key: str | None = None
|
|
362
|
+
|
|
363
|
+
base_url: str | None = None
|
|
364
|
+
|
|
365
|
+
default_headers: dict[str, str] | None = None
|
|
366
|
+
"""Custom headers to include in all requests to the API."""
|
|
367
|
+
|
|
368
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
class OpenRouterSettings(BaseModel):
|
|
372
|
+
"""
|
|
373
|
+
Settings for using OpenRouter models via its OpenAI-compatible API.
|
|
374
|
+
"""
|
|
375
|
+
|
|
376
|
+
api_key: str | None = None
|
|
377
|
+
|
|
378
|
+
base_url: str | None = None # Optional override, defaults handled in provider
|
|
379
|
+
|
|
380
|
+
default_headers: dict[str, str] | None = None
|
|
381
|
+
"""Custom headers to include in all requests to the OpenRouter API."""
|
|
382
|
+
|
|
383
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class AzureSettings(BaseModel):
|
|
387
|
+
"""
|
|
388
|
+
Settings for using Azure OpenAI Service in the fast-agent application.
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
api_key: str | None = None
|
|
392
|
+
resource_name: str | None = None
|
|
393
|
+
azure_deployment: str | None = None
|
|
394
|
+
api_version: str | None = None
|
|
395
|
+
base_url: str | None = None # Optional, can be constructed from resource_name
|
|
396
|
+
|
|
397
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class GroqSettings(BaseModel):
|
|
401
|
+
"""
|
|
402
|
+
Settings for using Groq models in the fast-agent application.
|
|
403
|
+
"""
|
|
404
|
+
|
|
405
|
+
api_key: str | None = None
|
|
406
|
+
base_url: str | None = "https://api.groq.com/openai/v1"
|
|
407
|
+
|
|
408
|
+
default_headers: dict[str, str] | None = None
|
|
409
|
+
"""Custom headers to include in all requests to the Groq API."""
|
|
410
|
+
|
|
411
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class OpenTelemetrySettings(BaseModel):
|
|
415
|
+
"""
|
|
416
|
+
OTEL settings for the fast-agent application.
|
|
417
|
+
"""
|
|
418
|
+
|
|
419
|
+
enabled: bool = False
|
|
420
|
+
|
|
421
|
+
service_name: str = "fast-agent"
|
|
422
|
+
|
|
423
|
+
otlp_endpoint: str = "http://localhost:4318/v1/traces"
|
|
424
|
+
"""OTLP endpoint for OpenTelemetry tracing"""
|
|
425
|
+
|
|
426
|
+
console_debug: bool = False
|
|
427
|
+
"""Log spans to console"""
|
|
428
|
+
|
|
429
|
+
sample_rate: float = 1.0
|
|
430
|
+
"""Sample rate for tracing (1.0 = sample everything)"""
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
class TensorZeroSettings(BaseModel):
|
|
434
|
+
"""
|
|
435
|
+
Settings for using TensorZero via its OpenAI-compatible API.
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
base_url: str | None = None
|
|
439
|
+
api_key: str | None = None
|
|
440
|
+
|
|
441
|
+
default_headers: dict[str, str] | None = None
|
|
442
|
+
"""Custom headers to include in all requests to the TensorZero API."""
|
|
443
|
+
|
|
444
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
class BedrockSettings(BaseModel):
|
|
448
|
+
"""
|
|
449
|
+
Settings for using AWS Bedrock models in the fast-agent application.
|
|
450
|
+
"""
|
|
451
|
+
|
|
452
|
+
region: str | None = None
|
|
453
|
+
"""AWS region for Bedrock service"""
|
|
454
|
+
|
|
455
|
+
profile: str | None = None
|
|
456
|
+
"""AWS profile to use for authentication"""
|
|
457
|
+
|
|
458
|
+
reasoning_effort: Literal["minimal", "low", "medium", "high"] = "minimal"
|
|
459
|
+
"""Default reasoning effort for Bedrock models. Can be overridden in model string (e.g., bedrock.claude-sonnet-4-0.high)"""
|
|
460
|
+
|
|
461
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
class HuggingFaceSettings(BaseModel):
|
|
465
|
+
"""
|
|
466
|
+
Settings for HuggingFace authentication (used for MCP connections).
|
|
467
|
+
"""
|
|
468
|
+
|
|
469
|
+
# Leave unset to allow the provider to use its default router endpoint.
|
|
470
|
+
base_url: str | None = None
|
|
471
|
+
api_key: str | None = None
|
|
472
|
+
default_provider: str | None = None
|
|
473
|
+
|
|
474
|
+
default_headers: dict[str, str] | None = None
|
|
475
|
+
"""Custom headers to include in all requests to the HuggingFace API."""
|
|
476
|
+
|
|
477
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
class LoggerSettings(BaseModel):
|
|
481
|
+
"""
|
|
482
|
+
Logger settings for the fast-agent application.
|
|
483
|
+
"""
|
|
484
|
+
|
|
485
|
+
type: Literal["none", "console", "file", "http"] = "file"
|
|
486
|
+
|
|
487
|
+
level: Literal["debug", "info", "warning", "error"] = "warning"
|
|
488
|
+
"""Minimum logging level"""
|
|
489
|
+
|
|
490
|
+
progress_display: bool = True
|
|
491
|
+
"""Enable or disable the progress display"""
|
|
492
|
+
|
|
493
|
+
path: str = "fastagent.jsonl"
|
|
494
|
+
"""Path to log file, if logger 'type' is 'file'."""
|
|
495
|
+
|
|
496
|
+
batch_size: int = 100
|
|
497
|
+
"""Number of events to accumulate before processing"""
|
|
498
|
+
|
|
499
|
+
flush_interval: float = 2.0
|
|
500
|
+
"""How often to flush events in seconds"""
|
|
501
|
+
|
|
502
|
+
max_queue_size: int = 2048
|
|
503
|
+
"""Maximum queue size for event processing"""
|
|
504
|
+
|
|
505
|
+
# HTTP transport settings
|
|
506
|
+
http_endpoint: str | None = None
|
|
507
|
+
"""HTTP endpoint for event transport"""
|
|
508
|
+
|
|
509
|
+
http_headers: dict[str, str] | None = None
|
|
510
|
+
"""HTTP headers for event transport"""
|
|
511
|
+
|
|
512
|
+
http_timeout: float = 5.0
|
|
513
|
+
"""HTTP timeout seconds for event transport"""
|
|
514
|
+
|
|
515
|
+
show_chat: bool = True
|
|
516
|
+
"""Show chat User/Assistant on the console"""
|
|
517
|
+
show_tools: bool = True
|
|
518
|
+
"""Show MCP Sever tool calls on the console"""
|
|
519
|
+
truncate_tools: bool = True
|
|
520
|
+
"""Truncate display of long tool calls"""
|
|
521
|
+
enable_markup: bool = True
|
|
522
|
+
"""Enable markup in console output. Disable for outputs that may conflict with rich console formatting"""
|
|
523
|
+
streaming: Literal["markdown", "plain", "none"] = "markdown"
|
|
524
|
+
"""Streaming renderer for assistant responses"""
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def find_fastagent_config_files(start_path: Path) -> tuple[Path | None, Path | None]:
|
|
528
|
+
"""
|
|
529
|
+
Find FastAgent configuration files with standardized behavior.
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
Tuple of (config_path, secrets_path) where either can be None if not found.
|
|
533
|
+
|
|
534
|
+
Strategy:
|
|
535
|
+
1. Find config file recursively from start_path upward
|
|
536
|
+
2. Prefer secrets file in same directory as config file
|
|
537
|
+
3. If no secrets file next to config, search recursively from start_path
|
|
538
|
+
"""
|
|
539
|
+
config_path = None
|
|
540
|
+
secrets_path = None
|
|
541
|
+
|
|
542
|
+
# First, find the config file with recursive search
|
|
543
|
+
current = start_path.resolve()
|
|
544
|
+
while current != current.parent:
|
|
545
|
+
potential_config = current / "fastagent.config.yaml"
|
|
546
|
+
if potential_config.exists():
|
|
547
|
+
config_path = potential_config
|
|
548
|
+
break
|
|
549
|
+
current = current.parent
|
|
550
|
+
|
|
551
|
+
# If config file found, prefer secrets file in the same directory
|
|
552
|
+
if config_path:
|
|
553
|
+
potential_secrets = config_path.parent / "fastagent.secrets.yaml"
|
|
554
|
+
if potential_secrets.exists():
|
|
555
|
+
secrets_path = potential_secrets
|
|
556
|
+
else:
|
|
557
|
+
# If no secrets file next to config, do recursive search from start
|
|
558
|
+
current = start_path.resolve()
|
|
559
|
+
while current != current.parent:
|
|
560
|
+
potential_secrets = current / "fastagent.secrets.yaml"
|
|
561
|
+
if potential_secrets.exists():
|
|
562
|
+
secrets_path = potential_secrets
|
|
563
|
+
break
|
|
564
|
+
current = current.parent
|
|
565
|
+
else:
|
|
566
|
+
# No config file found, just search for secrets file
|
|
567
|
+
current = start_path.resolve()
|
|
568
|
+
while current != current.parent:
|
|
569
|
+
potential_secrets = current / "fastagent.secrets.yaml"
|
|
570
|
+
if potential_secrets.exists():
|
|
571
|
+
secrets_path = potential_secrets
|
|
572
|
+
break
|
|
573
|
+
current = current.parent
|
|
574
|
+
|
|
575
|
+
return config_path, secrets_path
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
class Settings(BaseSettings):
|
|
579
|
+
"""
|
|
580
|
+
Settings class for the fast-agent application.
|
|
581
|
+
"""
|
|
582
|
+
|
|
583
|
+
model_config = SettingsConfigDict(
|
|
584
|
+
env_nested_delimiter="__",
|
|
585
|
+
env_file=".env",
|
|
586
|
+
env_file_encoding="utf-8",
|
|
587
|
+
extra="allow",
|
|
588
|
+
nested_model_default_partial_update=True,
|
|
589
|
+
) # Customize the behavior of settings here
|
|
590
|
+
|
|
591
|
+
mcp: MCPSettings | None = MCPSettings()
|
|
592
|
+
"""MCP config, such as MCP servers"""
|
|
593
|
+
|
|
594
|
+
execution_engine: Literal["asyncio"] = "asyncio"
|
|
595
|
+
"""Execution engine for the fast-agent application"""
|
|
596
|
+
|
|
597
|
+
default_model: str | None = None
|
|
598
|
+
"""
|
|
599
|
+
Default model for agents. Format is provider.model_name.<reasoning_effort>, for example openai.o3-mini.low
|
|
600
|
+
Aliases are provided for common models e.g. sonnet, haiku, gpt-4.1, o3-mini etc.
|
|
601
|
+
If not set, falls back to FAST_AGENT_MODEL env var, then to "gpt-5-mini.low".
|
|
602
|
+
"""
|
|
603
|
+
|
|
604
|
+
auto_sampling: bool = True
|
|
605
|
+
"""Enable automatic sampling model selection if not explicitly configured"""
|
|
606
|
+
|
|
607
|
+
anthropic: AnthropicSettings | None = None
|
|
608
|
+
"""Settings for using Anthropic models in the fast-agent application"""
|
|
609
|
+
|
|
610
|
+
otel: OpenTelemetrySettings | None = OpenTelemetrySettings()
|
|
611
|
+
"""OpenTelemetry logging settings for the fast-agent application"""
|
|
612
|
+
|
|
613
|
+
openai: OpenAISettings | None = None
|
|
614
|
+
"""Settings for using OpenAI models in the fast-agent application"""
|
|
615
|
+
|
|
616
|
+
deepseek: DeepSeekSettings | None = None
|
|
617
|
+
"""Settings for using DeepSeek models in the fast-agent application"""
|
|
618
|
+
|
|
619
|
+
google: GoogleSettings | None = None
|
|
620
|
+
"""Settings for using DeepSeek models in the fast-agent application"""
|
|
621
|
+
|
|
622
|
+
xai: XAISettings | None = None
|
|
623
|
+
"""Settings for using xAI Grok models in the fast-agent application"""
|
|
624
|
+
|
|
625
|
+
openrouter: OpenRouterSettings | None = None
|
|
626
|
+
"""Settings for using OpenRouter models in the fast-agent application"""
|
|
627
|
+
|
|
628
|
+
generic: GenericSettings | None = None
|
|
629
|
+
"""Settings for using Generic models in the fast-agent application"""
|
|
630
|
+
|
|
631
|
+
tensorzero: TensorZeroSettings | None = None
|
|
632
|
+
"""Settings for using TensorZero inference gateway"""
|
|
633
|
+
|
|
634
|
+
azure: AzureSettings | None = None
|
|
635
|
+
"""Settings for using Azure OpenAI Service in the fast-agent application"""
|
|
636
|
+
|
|
637
|
+
aliyun: OpenAISettings | None = None
|
|
638
|
+
"""Settings for using Aliyun OpenAI Service in the fast-agent application"""
|
|
639
|
+
|
|
640
|
+
bedrock: BedrockSettings | None = None
|
|
641
|
+
"""Settings for using AWS Bedrock models in the fast-agent application"""
|
|
642
|
+
|
|
643
|
+
hf: HuggingFaceSettings | None = None
|
|
644
|
+
"""Settings for HuggingFace authentication (used for MCP connections)"""
|
|
645
|
+
|
|
646
|
+
groq: GroqSettings | None = None
|
|
647
|
+
"""Settings for using the Groq provider in the fast-agent application"""
|
|
648
|
+
|
|
649
|
+
logger: LoggerSettings = LoggerSettings()
|
|
650
|
+
"""Logger settings for the fast-agent application"""
|
|
651
|
+
|
|
652
|
+
# MCP UI integration mode for handling ui:// embedded resources from MCP tool results
|
|
653
|
+
mcp_ui_mode: Literal["disabled", "enabled", "auto"] = "enabled"
|
|
654
|
+
"""Controls handling of MCP UI embedded resources:
|
|
655
|
+
- "disabled": Do not process ui:// resources
|
|
656
|
+
- "enabled": Always extract ui:// resources into message channels (default)
|
|
657
|
+
- "auto": Extract and automatically open ui:// resources.
|
|
658
|
+
"""
|
|
659
|
+
|
|
660
|
+
# Output directory for MCP-UI generated HTML files (relative to CWD if not absolute)
|
|
661
|
+
mcp_ui_output_dir: str = ".fast-agent/ui"
|
|
662
|
+
"""Directory where MCP-UI HTML files are written. Relative paths are resolved from CWD."""
|
|
663
|
+
|
|
664
|
+
mcp_timeline: MCPTimelineSettings = MCPTimelineSettings()
|
|
665
|
+
"""Display settings for MCP activity timelines."""
|
|
666
|
+
|
|
667
|
+
skills: SkillsSettings = SkillsSettings()
|
|
668
|
+
"""Local skills discovery and selection settings."""
|
|
669
|
+
|
|
670
|
+
shell_execution: ShellSettings = ShellSettings()
|
|
671
|
+
"""Shell execution timeout and warning settings."""
|
|
672
|
+
|
|
673
|
+
llm_retries: int = 0
|
|
674
|
+
"""
|
|
675
|
+
Number of times to retry transient LLM API errors.
|
|
676
|
+
Defaults to 0; can be overridden via config or FAST_AGENT_RETRIES env.
|
|
677
|
+
"""
|
|
678
|
+
|
|
679
|
+
@classmethod
|
|
680
|
+
def find_config(cls) -> Path | None:
|
|
681
|
+
"""Find the config file in the current directory or parent directories."""
|
|
682
|
+
current_dir = Path.cwd()
|
|
683
|
+
|
|
684
|
+
# Check current directory and parent directories
|
|
685
|
+
while current_dir != current_dir.parent:
|
|
686
|
+
for filename in [
|
|
687
|
+
"fastagent.config.yaml",
|
|
688
|
+
]:
|
|
689
|
+
config_path = current_dir / filename
|
|
690
|
+
if config_path.exists():
|
|
691
|
+
return config_path
|
|
692
|
+
current_dir = current_dir.parent
|
|
693
|
+
|
|
694
|
+
return None
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
# Global settings object
|
|
698
|
+
_settings: Settings | None = None
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def get_settings(config_path: str | os.PathLike[str] | None = None) -> Settings:
|
|
702
|
+
"""Get settings instance, automatically loading from config file if available."""
|
|
703
|
+
|
|
704
|
+
def resolve_env_vars(config_item: Any) -> Any:
|
|
705
|
+
"""Recursively resolve environment variables in config data."""
|
|
706
|
+
if isinstance(config_item, dict):
|
|
707
|
+
return {k: resolve_env_vars(v) for k, v in config_item.items()}
|
|
708
|
+
elif isinstance(config_item, list):
|
|
709
|
+
return [resolve_env_vars(i) for i in config_item]
|
|
710
|
+
elif isinstance(config_item, str):
|
|
711
|
+
# Regex to find ${ENV_VAR} or ${ENV_VAR:default_value}
|
|
712
|
+
pattern = re.compile(r"\$\{([^}]+)\}")
|
|
713
|
+
|
|
714
|
+
def replace_match(match: re.Match) -> str:
|
|
715
|
+
var_name_with_default = match.group(1)
|
|
716
|
+
if ":" in var_name_with_default:
|
|
717
|
+
var_name, default_value = var_name_with_default.split(":", 1)
|
|
718
|
+
return os.getenv(var_name, default_value)
|
|
719
|
+
else:
|
|
720
|
+
var_name = var_name_with_default
|
|
721
|
+
env_value = os.getenv(var_name)
|
|
722
|
+
if env_value is None:
|
|
723
|
+
# Optionally, raise an error or return the placeholder if the env var is not set
|
|
724
|
+
# For now, returning the placeholder to avoid breaking if not set and no default
|
|
725
|
+
# print(f"Warning: Environment variable {var_name} not set and no default provided.")
|
|
726
|
+
return match.group(0)
|
|
727
|
+
return env_value
|
|
728
|
+
|
|
729
|
+
# Replace all occurrences
|
|
730
|
+
resolved_value = pattern.sub(replace_match, config_item)
|
|
731
|
+
return resolved_value
|
|
732
|
+
return config_item
|
|
733
|
+
|
|
734
|
+
def deep_merge(base: dict, update: dict) -> dict:
|
|
735
|
+
"""Recursively merge two dictionaries, preserving nested structures."""
|
|
736
|
+
merged = base.copy()
|
|
737
|
+
for key, value in update.items():
|
|
738
|
+
if key in merged and isinstance(merged[key], dict) and isinstance(value, dict):
|
|
739
|
+
merged[key] = deep_merge(merged[key], value)
|
|
740
|
+
else:
|
|
741
|
+
merged[key] = value
|
|
742
|
+
return merged
|
|
743
|
+
|
|
744
|
+
global _settings
|
|
745
|
+
|
|
746
|
+
# If we have a specific config path, always reload settings
|
|
747
|
+
# This ensures each test gets its own config
|
|
748
|
+
if config_path:
|
|
749
|
+
# Reset for the new path
|
|
750
|
+
_settings = None
|
|
751
|
+
elif _settings:
|
|
752
|
+
# Use cached settings only for no specific path
|
|
753
|
+
return _settings
|
|
754
|
+
|
|
755
|
+
# Handle config path - convert string to Path if needed
|
|
756
|
+
config_file: Path | None
|
|
757
|
+
secrets_file: Path | None
|
|
758
|
+
if config_path:
|
|
759
|
+
config_file = Path(config_path)
|
|
760
|
+
# If it's a relative path and doesn't exist, try finding it
|
|
761
|
+
if not config_file.is_absolute() and not config_file.exists():
|
|
762
|
+
# Try resolving against current directory first
|
|
763
|
+
resolved_path = Path.cwd() / config_file.name
|
|
764
|
+
if resolved_path.exists():
|
|
765
|
+
config_file = resolved_path
|
|
766
|
+
|
|
767
|
+
# When config path is explicitly provided, find secrets using standardized logic
|
|
768
|
+
secrets_file = None
|
|
769
|
+
if config_file.exists():
|
|
770
|
+
_, secrets_file = find_fastagent_config_files(config_file.parent)
|
|
771
|
+
else:
|
|
772
|
+
# Use standardized discovery for both config and secrets
|
|
773
|
+
config_file, secrets_file = find_fastagent_config_files(Path.cwd())
|
|
774
|
+
|
|
775
|
+
merged_settings = {}
|
|
776
|
+
|
|
777
|
+
import yaml # pylint: disable=C0415
|
|
778
|
+
|
|
779
|
+
# Load main config if it exists
|
|
780
|
+
if config_file and config_file.exists():
|
|
781
|
+
with open(config_file, "r", encoding="utf-8") as f:
|
|
782
|
+
yaml_settings = yaml.safe_load(f) or {}
|
|
783
|
+
# Resolve environment variables in the loaded YAML settings
|
|
784
|
+
resolved_yaml_settings = resolve_env_vars(yaml_settings)
|
|
785
|
+
merged_settings = resolved_yaml_settings
|
|
786
|
+
elif config_file and not config_file.exists():
|
|
787
|
+
print(f"Warning: Specified config file does not exist: {config_file}")
|
|
788
|
+
|
|
789
|
+
# Load secrets file if found (regardless of whether config file exists)
|
|
790
|
+
if secrets_file and secrets_file.exists():
|
|
791
|
+
with open(secrets_file, "r", encoding="utf-8") as f:
|
|
792
|
+
yaml_secrets = yaml.safe_load(f) or {}
|
|
793
|
+
# Resolve environment variables in the loaded secrets YAML
|
|
794
|
+
resolved_secrets_yaml = resolve_env_vars(yaml_secrets)
|
|
795
|
+
merged_settings = deep_merge(merged_settings, resolved_secrets_yaml)
|
|
796
|
+
|
|
797
|
+
_settings = Settings(**merged_settings)
|
|
798
|
+
return _settings
|