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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. fast_agent/__init__.py +183 -0
  2. fast_agent/acp/__init__.py +19 -0
  3. fast_agent/acp/acp_aware_mixin.py +304 -0
  4. fast_agent/acp/acp_context.py +437 -0
  5. fast_agent/acp/content_conversion.py +136 -0
  6. fast_agent/acp/filesystem_runtime.py +427 -0
  7. fast_agent/acp/permission_store.py +269 -0
  8. fast_agent/acp/server/__init__.py +5 -0
  9. fast_agent/acp/server/agent_acp_server.py +1472 -0
  10. fast_agent/acp/slash_commands.py +1050 -0
  11. fast_agent/acp/terminal_runtime.py +408 -0
  12. fast_agent/acp/tool_permission_adapter.py +125 -0
  13. fast_agent/acp/tool_permissions.py +474 -0
  14. fast_agent/acp/tool_progress.py +814 -0
  15. fast_agent/agents/__init__.py +85 -0
  16. fast_agent/agents/agent_types.py +64 -0
  17. fast_agent/agents/llm_agent.py +350 -0
  18. fast_agent/agents/llm_decorator.py +1139 -0
  19. fast_agent/agents/mcp_agent.py +1337 -0
  20. fast_agent/agents/tool_agent.py +271 -0
  21. fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
  22. fast_agent/agents/workflow/chain_agent.py +212 -0
  23. fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
  24. fast_agent/agents/workflow/iterative_planner.py +652 -0
  25. fast_agent/agents/workflow/maker_agent.py +379 -0
  26. fast_agent/agents/workflow/orchestrator_models.py +218 -0
  27. fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
  28. fast_agent/agents/workflow/parallel_agent.py +250 -0
  29. fast_agent/agents/workflow/router_agent.py +353 -0
  30. fast_agent/cli/__init__.py +0 -0
  31. fast_agent/cli/__main__.py +73 -0
  32. fast_agent/cli/commands/acp.py +159 -0
  33. fast_agent/cli/commands/auth.py +404 -0
  34. fast_agent/cli/commands/check_config.py +783 -0
  35. fast_agent/cli/commands/go.py +514 -0
  36. fast_agent/cli/commands/quickstart.py +557 -0
  37. fast_agent/cli/commands/serve.py +143 -0
  38. fast_agent/cli/commands/server_helpers.py +114 -0
  39. fast_agent/cli/commands/setup.py +174 -0
  40. fast_agent/cli/commands/url_parser.py +190 -0
  41. fast_agent/cli/constants.py +40 -0
  42. fast_agent/cli/main.py +115 -0
  43. fast_agent/cli/terminal.py +24 -0
  44. fast_agent/config.py +798 -0
  45. fast_agent/constants.py +41 -0
  46. fast_agent/context.py +279 -0
  47. fast_agent/context_dependent.py +50 -0
  48. fast_agent/core/__init__.py +92 -0
  49. fast_agent/core/agent_app.py +448 -0
  50. fast_agent/core/core_app.py +137 -0
  51. fast_agent/core/direct_decorators.py +784 -0
  52. fast_agent/core/direct_factory.py +620 -0
  53. fast_agent/core/error_handling.py +27 -0
  54. fast_agent/core/exceptions.py +90 -0
  55. fast_agent/core/executor/__init__.py +0 -0
  56. fast_agent/core/executor/executor.py +280 -0
  57. fast_agent/core/executor/task_registry.py +32 -0
  58. fast_agent/core/executor/workflow_signal.py +324 -0
  59. fast_agent/core/fastagent.py +1186 -0
  60. fast_agent/core/logging/__init__.py +5 -0
  61. fast_agent/core/logging/events.py +138 -0
  62. fast_agent/core/logging/json_serializer.py +164 -0
  63. fast_agent/core/logging/listeners.py +309 -0
  64. fast_agent/core/logging/logger.py +278 -0
  65. fast_agent/core/logging/transport.py +481 -0
  66. fast_agent/core/prompt.py +9 -0
  67. fast_agent/core/prompt_templates.py +183 -0
  68. fast_agent/core/validation.py +326 -0
  69. fast_agent/event_progress.py +62 -0
  70. fast_agent/history/history_exporter.py +49 -0
  71. fast_agent/human_input/__init__.py +47 -0
  72. fast_agent/human_input/elicitation_handler.py +123 -0
  73. fast_agent/human_input/elicitation_state.py +33 -0
  74. fast_agent/human_input/form_elements.py +59 -0
  75. fast_agent/human_input/form_fields.py +256 -0
  76. fast_agent/human_input/simple_form.py +113 -0
  77. fast_agent/human_input/types.py +40 -0
  78. fast_agent/interfaces.py +310 -0
  79. fast_agent/llm/__init__.py +9 -0
  80. fast_agent/llm/cancellation.py +22 -0
  81. fast_agent/llm/fastagent_llm.py +931 -0
  82. fast_agent/llm/internal/passthrough.py +161 -0
  83. fast_agent/llm/internal/playback.py +129 -0
  84. fast_agent/llm/internal/silent.py +41 -0
  85. fast_agent/llm/internal/slow.py +38 -0
  86. fast_agent/llm/memory.py +275 -0
  87. fast_agent/llm/model_database.py +490 -0
  88. fast_agent/llm/model_factory.py +388 -0
  89. fast_agent/llm/model_info.py +102 -0
  90. fast_agent/llm/prompt_utils.py +155 -0
  91. fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
  92. fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
  93. fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
  94. fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
  95. fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
  96. fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
  97. fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
  98. fast_agent/llm/provider/google/google_converter.py +466 -0
  99. fast_agent/llm/provider/google/llm_google_native.py +681 -0
  100. fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
  101. fast_agent/llm/provider/openai/llm_azure.py +143 -0
  102. fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
  103. fast_agent/llm/provider/openai/llm_generic.py +35 -0
  104. fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
  105. fast_agent/llm/provider/openai/llm_groq.py +42 -0
  106. fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
  107. fast_agent/llm/provider/openai/llm_openai.py +1195 -0
  108. fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
  109. fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
  110. fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
  111. fast_agent/llm/provider/openai/llm_xai.py +38 -0
  112. fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
  113. fast_agent/llm/provider/openai/openai_multipart.py +169 -0
  114. fast_agent/llm/provider/openai/openai_utils.py +67 -0
  115. fast_agent/llm/provider/openai/responses.py +133 -0
  116. fast_agent/llm/provider_key_manager.py +139 -0
  117. fast_agent/llm/provider_types.py +34 -0
  118. fast_agent/llm/request_params.py +61 -0
  119. fast_agent/llm/sampling_converter.py +98 -0
  120. fast_agent/llm/stream_types.py +9 -0
  121. fast_agent/llm/usage_tracking.py +445 -0
  122. fast_agent/mcp/__init__.py +56 -0
  123. fast_agent/mcp/common.py +26 -0
  124. fast_agent/mcp/elicitation_factory.py +84 -0
  125. fast_agent/mcp/elicitation_handlers.py +164 -0
  126. fast_agent/mcp/gen_client.py +83 -0
  127. fast_agent/mcp/helpers/__init__.py +36 -0
  128. fast_agent/mcp/helpers/content_helpers.py +352 -0
  129. fast_agent/mcp/helpers/server_config_helpers.py +25 -0
  130. fast_agent/mcp/hf_auth.py +147 -0
  131. fast_agent/mcp/interfaces.py +92 -0
  132. fast_agent/mcp/logger_textio.py +108 -0
  133. fast_agent/mcp/mcp_agent_client_session.py +411 -0
  134. fast_agent/mcp/mcp_aggregator.py +2175 -0
  135. fast_agent/mcp/mcp_connection_manager.py +723 -0
  136. fast_agent/mcp/mcp_content.py +262 -0
  137. fast_agent/mcp/mime_utils.py +108 -0
  138. fast_agent/mcp/oauth_client.py +509 -0
  139. fast_agent/mcp/prompt.py +159 -0
  140. fast_agent/mcp/prompt_message_extended.py +155 -0
  141. fast_agent/mcp/prompt_render.py +84 -0
  142. fast_agent/mcp/prompt_serialization.py +580 -0
  143. fast_agent/mcp/prompts/__init__.py +0 -0
  144. fast_agent/mcp/prompts/__main__.py +7 -0
  145. fast_agent/mcp/prompts/prompt_constants.py +18 -0
  146. fast_agent/mcp/prompts/prompt_helpers.py +238 -0
  147. fast_agent/mcp/prompts/prompt_load.py +186 -0
  148. fast_agent/mcp/prompts/prompt_server.py +552 -0
  149. fast_agent/mcp/prompts/prompt_template.py +438 -0
  150. fast_agent/mcp/resource_utils.py +215 -0
  151. fast_agent/mcp/sampling.py +200 -0
  152. fast_agent/mcp/server/__init__.py +4 -0
  153. fast_agent/mcp/server/agent_server.py +613 -0
  154. fast_agent/mcp/skybridge.py +44 -0
  155. fast_agent/mcp/sse_tracking.py +287 -0
  156. fast_agent/mcp/stdio_tracking_simple.py +59 -0
  157. fast_agent/mcp/streamable_http_tracking.py +309 -0
  158. fast_agent/mcp/tool_execution_handler.py +137 -0
  159. fast_agent/mcp/tool_permission_handler.py +88 -0
  160. fast_agent/mcp/transport_tracking.py +634 -0
  161. fast_agent/mcp/types.py +24 -0
  162. fast_agent/mcp/ui_agent.py +48 -0
  163. fast_agent/mcp/ui_mixin.py +209 -0
  164. fast_agent/mcp_server_registry.py +89 -0
  165. fast_agent/py.typed +0 -0
  166. fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
  167. fast_agent/resources/examples/data-analysis/analysis.py +68 -0
  168. fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
  169. fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
  170. fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
  171. fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
  172. fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
  173. fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
  174. fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
  175. fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
  176. fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
  177. fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
  178. fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
  179. fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
  180. fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
  181. fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
  182. fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
  183. fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
  184. fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
  185. fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
  186. fast_agent/resources/examples/researcher/researcher.py +36 -0
  187. fast_agent/resources/examples/tensorzero/.env.sample +2 -0
  188. fast_agent/resources/examples/tensorzero/Makefile +31 -0
  189. fast_agent/resources/examples/tensorzero/README.md +56 -0
  190. fast_agent/resources/examples/tensorzero/agent.py +35 -0
  191. fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
  192. fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
  193. fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
  194. fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
  195. fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
  196. fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
  197. fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
  198. fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
  199. fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
  200. fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
  201. fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
  202. fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
  203. fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
  204. fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
  205. fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
  206. fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
  207. fast_agent/resources/examples/workflows/chaining.py +37 -0
  208. fast_agent/resources/examples/workflows/evaluator.py +77 -0
  209. fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
  210. fast_agent/resources/examples/workflows/graded_report.md +89 -0
  211. fast_agent/resources/examples/workflows/human_input.py +28 -0
  212. fast_agent/resources/examples/workflows/maker.py +156 -0
  213. fast_agent/resources/examples/workflows/orchestrator.py +70 -0
  214. fast_agent/resources/examples/workflows/parallel.py +56 -0
  215. fast_agent/resources/examples/workflows/router.py +69 -0
  216. fast_agent/resources/examples/workflows/short_story.md +13 -0
  217. fast_agent/resources/examples/workflows/short_story.txt +19 -0
  218. fast_agent/resources/setup/.gitignore +30 -0
  219. fast_agent/resources/setup/agent.py +28 -0
  220. fast_agent/resources/setup/fastagent.config.yaml +65 -0
  221. fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
  222. fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
  223. fast_agent/skills/__init__.py +9 -0
  224. fast_agent/skills/registry.py +235 -0
  225. fast_agent/tools/elicitation.py +369 -0
  226. fast_agent/tools/shell_runtime.py +402 -0
  227. fast_agent/types/__init__.py +59 -0
  228. fast_agent/types/conversation_summary.py +294 -0
  229. fast_agent/types/llm_stop_reason.py +78 -0
  230. fast_agent/types/message_search.py +249 -0
  231. fast_agent/ui/__init__.py +38 -0
  232. fast_agent/ui/console.py +59 -0
  233. fast_agent/ui/console_display.py +1080 -0
  234. fast_agent/ui/elicitation_form.py +946 -0
  235. fast_agent/ui/elicitation_style.py +59 -0
  236. fast_agent/ui/enhanced_prompt.py +1400 -0
  237. fast_agent/ui/history_display.py +734 -0
  238. fast_agent/ui/interactive_prompt.py +1199 -0
  239. fast_agent/ui/markdown_helpers.py +104 -0
  240. fast_agent/ui/markdown_truncator.py +1004 -0
  241. fast_agent/ui/mcp_display.py +857 -0
  242. fast_agent/ui/mcp_ui_utils.py +235 -0
  243. fast_agent/ui/mermaid_utils.py +169 -0
  244. fast_agent/ui/message_primitives.py +50 -0
  245. fast_agent/ui/notification_tracker.py +205 -0
  246. fast_agent/ui/plain_text_truncator.py +68 -0
  247. fast_agent/ui/progress_display.py +10 -0
  248. fast_agent/ui/rich_progress.py +195 -0
  249. fast_agent/ui/streaming.py +774 -0
  250. fast_agent/ui/streaming_buffer.py +449 -0
  251. fast_agent/ui/tool_display.py +422 -0
  252. fast_agent/ui/usage_display.py +204 -0
  253. fast_agent/utils/__init__.py +5 -0
  254. fast_agent/utils/reasoning_stream_parser.py +77 -0
  255. fast_agent/utils/time.py +22 -0
  256. fast_agent/workflow_telemetry.py +261 -0
  257. fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
  258. fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
  259. fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
  260. fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
  261. fast_agent_mcp-0.4.7.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,437 @@
1
+ """
2
+ ACPContext - Centralized ACP runtime state for context-aware agents.
3
+
4
+ Provides a unified interface for agents to access ACP capabilities including:
5
+ - Session information and mode management
6
+ - Terminal and filesystem runtimes
7
+ - Tool permissions and progress tracking
8
+ - Slash command management
9
+ - Client capability queries
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import asyncio
15
+ from dataclasses import dataclass, field
16
+ from typing import TYPE_CHECKING, Any
17
+
18
+ from acp.schema import (
19
+ AvailableCommandsUpdate,
20
+ CurrentModeUpdate,
21
+ SessionMode,
22
+ )
23
+
24
+ from fast_agent.core.logging.logger import get_logger
25
+
26
+ if TYPE_CHECKING:
27
+ from acp import AgentSideConnection
28
+
29
+ from fast_agent.acp.filesystem_runtime import ACPFilesystemRuntime
30
+ from fast_agent.acp.slash_commands import SlashCommandHandler
31
+ from fast_agent.acp.terminal_runtime import ACPTerminalRuntime
32
+ from fast_agent.acp.tool_permission_adapter import ACPToolPermissionAdapter
33
+ from fast_agent.acp.tool_progress import ACPToolProgressManager
34
+
35
+ logger = get_logger(__name__)
36
+
37
+
38
+ @dataclass
39
+ class ClientCapabilities:
40
+ """Client capabilities from ACP initialization."""
41
+
42
+ terminal: bool = False
43
+ fs_read: bool = False
44
+ fs_write: bool = False
45
+ _meta: dict[str, Any] = field(default_factory=dict)
46
+
47
+ @classmethod
48
+ def from_acp_capabilities(cls, caps: Any) -> "ClientCapabilities":
49
+ """Create from ACP ClientCapabilities object."""
50
+ result = cls()
51
+ if caps is None:
52
+ return result
53
+
54
+ result.terminal = bool(getattr(caps, "terminal", False))
55
+
56
+ if hasattr(caps, "fs") and caps.fs:
57
+ fs_caps = caps.fs
58
+ result.fs_read = bool(getattr(fs_caps, "readTextFile", False))
59
+ result.fs_write = bool(getattr(fs_caps, "writeTextFile", False))
60
+
61
+ if hasattr(caps, "_meta") and caps._meta:
62
+ result._meta = dict(caps._meta) if isinstance(caps._meta, dict) else {}
63
+
64
+ return result
65
+
66
+
67
+ @dataclass
68
+ class ClientInfo:
69
+ """Client information from ACP initialization."""
70
+
71
+ name: str = "unknown"
72
+ version: str = "unknown"
73
+ title: str | None = None
74
+
75
+ @classmethod
76
+ def from_acp_info(cls, info: Any) -> "ClientInfo":
77
+ """Create from ACP Implementation object."""
78
+ if info is None:
79
+ return cls()
80
+ return cls(
81
+ name=getattr(info, "name", "unknown"),
82
+ version=getattr(info, "version", "unknown"),
83
+ title=getattr(info, "title", None),
84
+ )
85
+
86
+
87
+ class ACPContext:
88
+ """
89
+ Centralized ACP runtime context.
90
+
91
+ This class provides agents with access to all ACP-related capabilities
92
+ when running in ACP mode. It centralizes:
93
+ - Session and connection state
94
+ - Mode management (current mode, switching)
95
+ - Runtimes (terminal, filesystem)
96
+ - Handlers (permissions, progress, slash commands)
97
+ - Client capabilities
98
+
99
+ Usage:
100
+ if agent.is_acp_mode:
101
+ # Check capabilities
102
+ if agent.acp.supports_terminal:
103
+ ...
104
+
105
+ # Access current mode
106
+ current = agent.acp.current_mode
107
+
108
+ # Switch modes
109
+ await agent.acp.switch_mode("specialist_agent")
110
+ """
111
+
112
+ def __init__(
113
+ self,
114
+ connection: "AgentSideConnection",
115
+ session_id: str,
116
+ *,
117
+ client_capabilities: ClientCapabilities | None = None,
118
+ client_info: ClientInfo | None = None,
119
+ protocol_version: int | None = None,
120
+ ) -> None:
121
+ """
122
+ Initialize the ACP context.
123
+
124
+ Args:
125
+ connection: The ACP connection for sending requests/notifications
126
+ session_id: The ACP session ID
127
+ client_capabilities: Client capabilities from initialization
128
+ client_info: Client information from initialization
129
+ protocol_version: ACP protocol version
130
+ """
131
+ self._connection = connection
132
+ self._session_id = session_id
133
+ self._client_capabilities = client_capabilities or ClientCapabilities()
134
+ self._client_info = client_info or ClientInfo()
135
+ self._protocol_version = protocol_version
136
+
137
+ # Mode management
138
+ self._current_mode: str = "default"
139
+ self._available_modes: dict[str, SessionMode] = {}
140
+
141
+ # Runtimes (set by AgentACPServer during session setup)
142
+ self._terminal_runtime: "ACPTerminalRuntime | None" = None
143
+ self._filesystem_runtime: "ACPFilesystemRuntime | None" = None
144
+
145
+ # Handlers (set by AgentACPServer during session setup)
146
+ self._permission_handler: "ACPToolPermissionAdapter | None" = None
147
+ self._progress_manager: "ACPToolProgressManager | None" = None
148
+ self._slash_handler: "SlashCommandHandler | None" = None
149
+
150
+ # Lock for async operations
151
+ self._lock = asyncio.Lock()
152
+
153
+ logger.debug(
154
+ "ACPContext initialized",
155
+ name="acp_context_init",
156
+ session_id=session_id,
157
+ supports_terminal=self._client_capabilities.terminal,
158
+ supports_fs_read=self._client_capabilities.fs_read,
159
+ supports_fs_write=self._client_capabilities.fs_write,
160
+ )
161
+
162
+ # =========================================================================
163
+ # Properties - Session Info
164
+ # =========================================================================
165
+
166
+ @property
167
+ def session_id(self) -> str:
168
+ """Get the ACP session ID."""
169
+ return self._session_id
170
+
171
+ @property
172
+ def connection(self) -> "AgentSideConnection":
173
+ """Get the ACP connection (for advanced use cases)."""
174
+ return self._connection
175
+
176
+ @property
177
+ def protocol_version(self) -> int | None:
178
+ """Get the ACP protocol version."""
179
+ return self._protocol_version
180
+
181
+ # =========================================================================
182
+ # Properties - Client Info
183
+ # =========================================================================
184
+
185
+ @property
186
+ def client_info(self) -> ClientInfo:
187
+ """Get client information."""
188
+ return self._client_info
189
+
190
+ @property
191
+ def client_capabilities(self) -> ClientCapabilities:
192
+ """Get client capabilities."""
193
+ return self._client_capabilities
194
+
195
+ @property
196
+ def supports_terminal(self) -> bool:
197
+ """Check if the client supports terminal operations."""
198
+ return self._client_capabilities.terminal
199
+
200
+ @property
201
+ def supports_fs_read(self) -> bool:
202
+ """Check if the client supports file reading."""
203
+ return self._client_capabilities.fs_read
204
+
205
+ @property
206
+ def supports_fs_write(self) -> bool:
207
+ """Check if the client supports file writing."""
208
+ return self._client_capabilities.fs_write
209
+
210
+ @property
211
+ def supports_filesystem(self) -> bool:
212
+ """Check if the client supports any filesystem operations."""
213
+ return self._client_capabilities.fs_read or self._client_capabilities.fs_write
214
+
215
+ # =========================================================================
216
+ # Properties - Mode Management
217
+ # =========================================================================
218
+
219
+ @property
220
+ def current_mode(self) -> str:
221
+ """Get the current mode (agent) ID."""
222
+ return self._current_mode
223
+
224
+ @property
225
+ def available_modes(self) -> dict[str, SessionMode]:
226
+ """Get available modes (agents) for this session."""
227
+ return self._available_modes.copy()
228
+
229
+ def set_current_mode(self, mode_id: str) -> None:
230
+ """
231
+ Set the current mode (called by server when mode changes).
232
+
233
+ Args:
234
+ mode_id: The mode ID to set as current
235
+ """
236
+ self._current_mode = mode_id
237
+
238
+ def set_available_modes(self, modes: list[SessionMode]) -> None:
239
+ """
240
+ Set available modes (called by server during session setup).
241
+
242
+ Args:
243
+ modes: List of available session modes
244
+ """
245
+ self._available_modes = {mode.id: mode for mode in modes}
246
+
247
+ async def switch_mode(self, mode_id: str) -> None:
248
+ """
249
+ Force-switch to a different mode/agent.
250
+
251
+ This sends a CurrentModeUpdate notification to the client,
252
+ telling it that the agent has autonomously switched modes.
253
+
254
+ Args:
255
+ mode_id: The mode ID to switch to
256
+
257
+ Raises:
258
+ ValueError: If the mode_id is not in available modes
259
+ """
260
+ if mode_id not in self._available_modes:
261
+ raise ValueError(
262
+ f"Invalid mode ID '{mode_id}'. Available modes: {list(self._available_modes.keys())}"
263
+ )
264
+
265
+ async with self._lock:
266
+ old_mode = self._current_mode
267
+ self._current_mode = mode_id
268
+
269
+ # Send CurrentModeUpdate notification to client
270
+ mode_update = CurrentModeUpdate(
271
+ session_update="current_mode_update",
272
+ current_mode_id=mode_id,
273
+ )
274
+
275
+ try:
276
+ await self._connection.session_update(
277
+ session_id=self._session_id,
278
+ update=mode_update,
279
+ )
280
+
281
+ # Keep server-side slash command routing and command lists consistent with
282
+ # agent-initiated mode switches.
283
+ if self._slash_handler:
284
+ try:
285
+ self._slash_handler.set_current_agent(mode_id)
286
+ except Exception:
287
+ pass
288
+ await self.send_available_commands_update()
289
+
290
+ logger.info(
291
+ "Mode switched via agent request",
292
+ name="acp_mode_switch",
293
+ session_id=self._session_id,
294
+ old_mode=old_mode,
295
+ new_mode=mode_id,
296
+ )
297
+ except Exception as e:
298
+ # Revert on failure
299
+ self._current_mode = old_mode
300
+ logger.error(
301
+ f"Failed to switch mode: {e}",
302
+ name="acp_mode_switch_error",
303
+ exc_info=True,
304
+ )
305
+ raise
306
+
307
+ # =========================================================================
308
+ # Properties - Runtimes
309
+ # =========================================================================
310
+
311
+ @property
312
+ def terminal_runtime(self) -> "ACPTerminalRuntime | None":
313
+ """Get the terminal runtime (if available)."""
314
+ return self._terminal_runtime
315
+
316
+ @property
317
+ def filesystem_runtime(self) -> "ACPFilesystemRuntime | None":
318
+ """Get the filesystem runtime (if available)."""
319
+ return self._filesystem_runtime
320
+
321
+ def set_terminal_runtime(self, runtime: "ACPTerminalRuntime") -> None:
322
+ """Set the terminal runtime (called by server)."""
323
+ self._terminal_runtime = runtime
324
+
325
+ def set_filesystem_runtime(self, runtime: "ACPFilesystemRuntime") -> None:
326
+ """Set the filesystem runtime (called by server)."""
327
+ self._filesystem_runtime = runtime
328
+
329
+ # =========================================================================
330
+ # Properties - Handlers
331
+ # =========================================================================
332
+
333
+ @property
334
+ def permission_handler(self) -> "ACPToolPermissionAdapter | None":
335
+ """Get the permission handler (if available)."""
336
+ return self._permission_handler
337
+
338
+ @property
339
+ def progress_manager(self) -> "ACPToolProgressManager | None":
340
+ """Get the progress manager (if available)."""
341
+ return self._progress_manager
342
+
343
+ @property
344
+ def slash_handler(self) -> "SlashCommandHandler | None":
345
+ """Get the slash command handler."""
346
+ return self._slash_handler
347
+
348
+ def set_permission_handler(self, handler: "ACPToolPermissionAdapter") -> None:
349
+ """Set the permission handler (called by server)."""
350
+ self._permission_handler = handler
351
+
352
+ def set_progress_manager(self, manager: "ACPToolProgressManager") -> None:
353
+ """Set the progress manager (called by server)."""
354
+ self._progress_manager = manager
355
+
356
+ def set_slash_handler(self, handler: "SlashCommandHandler") -> None:
357
+ """Set the slash command handler (called by server)."""
358
+ self._slash_handler = handler
359
+
360
+ # =========================================================================
361
+ # Slash Command Updates
362
+ # =========================================================================
363
+
364
+ async def send_available_commands_update(self) -> None:
365
+ """
366
+ Send AvailableCommandsUpdate notification to client.
367
+
368
+ Call this when the available commands may have changed (e.g., after mode switch).
369
+ Commands are queried from the SlashCommandHandler which combines session
370
+ commands with agent-specific commands.
371
+ """
372
+ if not self._slash_handler:
373
+ return
374
+
375
+ all_commands = self._slash_handler.get_available_commands()
376
+
377
+ commands_update = AvailableCommandsUpdate(
378
+ session_update="available_commands_update",
379
+ available_commands=all_commands,
380
+ )
381
+
382
+ try:
383
+ await self._connection.session_update(
384
+ session_id=self._session_id,
385
+ update=commands_update,
386
+ )
387
+ logger.debug(
388
+ "Sent available_commands_update",
389
+ name="acp_commands_update_sent",
390
+ session_id=self._session_id,
391
+ command_count=len(all_commands),
392
+ )
393
+ except Exception as e:
394
+ logger.error(
395
+ f"Error sending available_commands_update: {e}",
396
+ name="acp_commands_update_error",
397
+ exc_info=True,
398
+ )
399
+
400
+ # =========================================================================
401
+ # Session Updates
402
+ # =========================================================================
403
+
404
+ async def send_session_update(self, update: Any) -> None:
405
+ """
406
+ Send a session update notification to the client.
407
+
408
+ This is a low-level method for sending arbitrary session updates.
409
+ Prefer using higher-level methods like switch_mode() when available.
410
+
411
+ Args:
412
+ update: The session update payload (must be a valid ACP session update type)
413
+ """
414
+ await self._connection.session_update(
415
+ session_id=self._session_id,
416
+ update=update,
417
+ )
418
+
419
+ # =========================================================================
420
+ # Cleanup
421
+ # =========================================================================
422
+
423
+ async def cleanup(self) -> None:
424
+ """Clean up ACP context resources."""
425
+ async with self._lock:
426
+ # Clear permission cache if handler exists
427
+ if self._permission_handler:
428
+ try:
429
+ await self._permission_handler.clear_session_cache()
430
+ except Exception as e:
431
+ logger.error(f"Error clearing permission cache: {e}", exc_info=True)
432
+
433
+ logger.debug(
434
+ "ACPContext cleaned up",
435
+ name="acp_context_cleanup",
436
+ session_id=self._session_id,
437
+ )
@@ -0,0 +1,136 @@
1
+ """
2
+ Content block conversion from ACP to MCP format.
3
+
4
+ This module handles conversion of content blocks from the Agent Client Protocol (ACP)
5
+ to Model Context Protocol (MCP) format for processing by fast-agent.
6
+ """
7
+
8
+ from typing import cast
9
+
10
+ import acp.schema as acp_schema
11
+ import mcp.types as mcp_types
12
+ from acp.helpers import ContentBlock as ACPContentBlock
13
+ from mcp.types import ContentBlock as MCPContentBlock
14
+ from pydantic import AnyUrl
15
+
16
+
17
+ def convert_acp_content_to_mcp(acp_content: ACPContentBlock) -> MCPContentBlock | None:
18
+ """
19
+ Convert an ACP content block to MCP format.
20
+
21
+ Args:
22
+ acp_content: Content block from ACP (Agent Client Protocol)
23
+
24
+ Returns:
25
+ Corresponding MCP content block, or None if conversion is not supported
26
+
27
+ Supported conversions:
28
+ - TextContentBlock -> TextContent
29
+ - ImageContentBlock -> ImageContent
30
+ - EmbeddedResourceContentBlock -> EmbeddedResource
31
+ """
32
+ match acp_content:
33
+ case acp_schema.TextContentBlock():
34
+ return _convert_text_content(acp_content)
35
+ case acp_schema.ImageContentBlock():
36
+ return _convert_image_content(acp_content)
37
+ case acp_schema.EmbeddedResourceContentBlock():
38
+ return _convert_embedded_resource(acp_content)
39
+ case _:
40
+ # Unsupported content types (audio, resource links, etc.)
41
+ return None
42
+
43
+
44
+ def _convert_text_content(
45
+ acp_text: acp_schema.TextContentBlock,
46
+ ) -> mcp_types.TextContent:
47
+ """Convert ACP TextContentBlock to MCP TextContent."""
48
+ return mcp_types.TextContent(
49
+ type="text",
50
+ text=acp_text.text,
51
+ annotations=_convert_annotations(acp_text.annotations),
52
+ )
53
+
54
+
55
+ def _convert_image_content(
56
+ acp_image: acp_schema.ImageContentBlock,
57
+ ) -> mcp_types.ImageContent:
58
+ """Convert ACP ImageContentBlock to MCP ImageContent."""
59
+ return mcp_types.ImageContent(
60
+ type="image",
61
+ data=acp_image.data,
62
+ mimeType=acp_image.mimeType,
63
+ annotations=_convert_annotations(acp_image.annotations),
64
+ )
65
+
66
+
67
+ def _convert_embedded_resource(
68
+ acp_resource: acp_schema.EmbeddedResourceContentBlock,
69
+ ) -> mcp_types.EmbeddedResource:
70
+ """Convert ACP EmbeddedResourceContentBlock to MCP EmbeddedResource."""
71
+ return mcp_types.EmbeddedResource(
72
+ type="resource",
73
+ resource=_convert_resource_contents(acp_resource.resource),
74
+ annotations=_convert_annotations(acp_resource.annotations),
75
+ )
76
+
77
+
78
+ def _convert_resource_contents(
79
+ acp_resource: acp_schema.TextResourceContents | acp_schema.BlobResourceContents,
80
+ ) -> mcp_types.TextResourceContents | mcp_types.BlobResourceContents:
81
+ """Convert ACP resource contents to MCP resource contents."""
82
+ match acp_resource:
83
+ case acp_schema.TextResourceContents():
84
+ return mcp_types.TextResourceContents(
85
+ uri=AnyUrl(acp_resource.uri),
86
+ mimeType=acp_resource.mimeType or None,
87
+ text=acp_resource.text,
88
+ )
89
+ case acp_schema.BlobResourceContents():
90
+ return mcp_types.BlobResourceContents(
91
+ uri=AnyUrl(acp_resource.uri),
92
+ mimeType=acp_resource.mimeType or None,
93
+ blob=acp_resource.blob,
94
+ )
95
+ case _:
96
+ raise ValueError(f"Unsupported resource type: {type(acp_resource)}")
97
+
98
+
99
+ def _convert_annotations(
100
+ acp_annotations: acp_schema.Annotations | None,
101
+ ) -> mcp_types.Annotations | None:
102
+ """Convert ACP annotations to MCP annotations."""
103
+ if not acp_annotations:
104
+ return None
105
+
106
+ audience = (
107
+ cast("list[mcp_types.Role]", list(acp_annotations.audience))
108
+ if acp_annotations.audience
109
+ else None
110
+ )
111
+ return mcp_types.Annotations(
112
+ audience=audience,
113
+ priority=getattr(acp_annotations, "priority", None),
114
+ )
115
+
116
+
117
+ def convert_acp_prompt_to_mcp_content_blocks(
118
+ acp_prompt: list[ACPContentBlock],
119
+ ) -> list[MCPContentBlock]:
120
+ """
121
+ Convert a list of ACP content blocks to MCP content blocks.
122
+
123
+ Args:
124
+ acp_prompt: List of content blocks from ACP prompt
125
+
126
+ Returns:
127
+ List of MCP content blocks (only supported types are converted)
128
+ """
129
+ mcp_blocks = []
130
+
131
+ for acp_block in acp_prompt:
132
+ mcp_block = convert_acp_content_to_mcp(acp_block)
133
+ if mcp_block is not None:
134
+ mcp_blocks.append(mcp_block)
135
+
136
+ return mcp_blocks