fast-agent-mcp 0.4.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fast_agent/__init__.py +183 -0
- fast_agent/acp/__init__.py +19 -0
- fast_agent/acp/acp_aware_mixin.py +304 -0
- fast_agent/acp/acp_context.py +437 -0
- fast_agent/acp/content_conversion.py +136 -0
- fast_agent/acp/filesystem_runtime.py +427 -0
- fast_agent/acp/permission_store.py +269 -0
- fast_agent/acp/server/__init__.py +5 -0
- fast_agent/acp/server/agent_acp_server.py +1472 -0
- fast_agent/acp/slash_commands.py +1050 -0
- fast_agent/acp/terminal_runtime.py +408 -0
- fast_agent/acp/tool_permission_adapter.py +125 -0
- fast_agent/acp/tool_permissions.py +474 -0
- fast_agent/acp/tool_progress.py +814 -0
- fast_agent/agents/__init__.py +85 -0
- fast_agent/agents/agent_types.py +64 -0
- fast_agent/agents/llm_agent.py +350 -0
- fast_agent/agents/llm_decorator.py +1139 -0
- fast_agent/agents/mcp_agent.py +1337 -0
- fast_agent/agents/tool_agent.py +271 -0
- fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
- fast_agent/agents/workflow/chain_agent.py +212 -0
- fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
- fast_agent/agents/workflow/iterative_planner.py +652 -0
- fast_agent/agents/workflow/maker_agent.py +379 -0
- fast_agent/agents/workflow/orchestrator_models.py +218 -0
- fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
- fast_agent/agents/workflow/parallel_agent.py +250 -0
- fast_agent/agents/workflow/router_agent.py +353 -0
- fast_agent/cli/__init__.py +0 -0
- fast_agent/cli/__main__.py +73 -0
- fast_agent/cli/commands/acp.py +159 -0
- fast_agent/cli/commands/auth.py +404 -0
- fast_agent/cli/commands/check_config.py +783 -0
- fast_agent/cli/commands/go.py +514 -0
- fast_agent/cli/commands/quickstart.py +557 -0
- fast_agent/cli/commands/serve.py +143 -0
- fast_agent/cli/commands/server_helpers.py +114 -0
- fast_agent/cli/commands/setup.py +174 -0
- fast_agent/cli/commands/url_parser.py +190 -0
- fast_agent/cli/constants.py +40 -0
- fast_agent/cli/main.py +115 -0
- fast_agent/cli/terminal.py +24 -0
- fast_agent/config.py +798 -0
- fast_agent/constants.py +41 -0
- fast_agent/context.py +279 -0
- fast_agent/context_dependent.py +50 -0
- fast_agent/core/__init__.py +92 -0
- fast_agent/core/agent_app.py +448 -0
- fast_agent/core/core_app.py +137 -0
- fast_agent/core/direct_decorators.py +784 -0
- fast_agent/core/direct_factory.py +620 -0
- fast_agent/core/error_handling.py +27 -0
- fast_agent/core/exceptions.py +90 -0
- fast_agent/core/executor/__init__.py +0 -0
- fast_agent/core/executor/executor.py +280 -0
- fast_agent/core/executor/task_registry.py +32 -0
- fast_agent/core/executor/workflow_signal.py +324 -0
- fast_agent/core/fastagent.py +1186 -0
- fast_agent/core/logging/__init__.py +5 -0
- fast_agent/core/logging/events.py +138 -0
- fast_agent/core/logging/json_serializer.py +164 -0
- fast_agent/core/logging/listeners.py +309 -0
- fast_agent/core/logging/logger.py +278 -0
- fast_agent/core/logging/transport.py +481 -0
- fast_agent/core/prompt.py +9 -0
- fast_agent/core/prompt_templates.py +183 -0
- fast_agent/core/validation.py +326 -0
- fast_agent/event_progress.py +62 -0
- fast_agent/history/history_exporter.py +49 -0
- fast_agent/human_input/__init__.py +47 -0
- fast_agent/human_input/elicitation_handler.py +123 -0
- fast_agent/human_input/elicitation_state.py +33 -0
- fast_agent/human_input/form_elements.py +59 -0
- fast_agent/human_input/form_fields.py +256 -0
- fast_agent/human_input/simple_form.py +113 -0
- fast_agent/human_input/types.py +40 -0
- fast_agent/interfaces.py +310 -0
- fast_agent/llm/__init__.py +9 -0
- fast_agent/llm/cancellation.py +22 -0
- fast_agent/llm/fastagent_llm.py +931 -0
- fast_agent/llm/internal/passthrough.py +161 -0
- fast_agent/llm/internal/playback.py +129 -0
- fast_agent/llm/internal/silent.py +41 -0
- fast_agent/llm/internal/slow.py +38 -0
- fast_agent/llm/memory.py +275 -0
- fast_agent/llm/model_database.py +490 -0
- fast_agent/llm/model_factory.py +388 -0
- fast_agent/llm/model_info.py +102 -0
- fast_agent/llm/prompt_utils.py +155 -0
- fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
- fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
- fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
- fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
- fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
- fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
- fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
- fast_agent/llm/provider/google/google_converter.py +466 -0
- fast_agent/llm/provider/google/llm_google_native.py +681 -0
- fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
- fast_agent/llm/provider/openai/llm_azure.py +143 -0
- fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
- fast_agent/llm/provider/openai/llm_generic.py +35 -0
- fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
- fast_agent/llm/provider/openai/llm_groq.py +42 -0
- fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
- fast_agent/llm/provider/openai/llm_openai.py +1195 -0
- fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
- fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
- fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
- fast_agent/llm/provider/openai/llm_xai.py +38 -0
- fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
- fast_agent/llm/provider/openai/openai_multipart.py +169 -0
- fast_agent/llm/provider/openai/openai_utils.py +67 -0
- fast_agent/llm/provider/openai/responses.py +133 -0
- fast_agent/llm/provider_key_manager.py +139 -0
- fast_agent/llm/provider_types.py +34 -0
- fast_agent/llm/request_params.py +61 -0
- fast_agent/llm/sampling_converter.py +98 -0
- fast_agent/llm/stream_types.py +9 -0
- fast_agent/llm/usage_tracking.py +445 -0
- fast_agent/mcp/__init__.py +56 -0
- fast_agent/mcp/common.py +26 -0
- fast_agent/mcp/elicitation_factory.py +84 -0
- fast_agent/mcp/elicitation_handlers.py +164 -0
- fast_agent/mcp/gen_client.py +83 -0
- fast_agent/mcp/helpers/__init__.py +36 -0
- fast_agent/mcp/helpers/content_helpers.py +352 -0
- fast_agent/mcp/helpers/server_config_helpers.py +25 -0
- fast_agent/mcp/hf_auth.py +147 -0
- fast_agent/mcp/interfaces.py +92 -0
- fast_agent/mcp/logger_textio.py +108 -0
- fast_agent/mcp/mcp_agent_client_session.py +411 -0
- fast_agent/mcp/mcp_aggregator.py +2175 -0
- fast_agent/mcp/mcp_connection_manager.py +723 -0
- fast_agent/mcp/mcp_content.py +262 -0
- fast_agent/mcp/mime_utils.py +108 -0
- fast_agent/mcp/oauth_client.py +509 -0
- fast_agent/mcp/prompt.py +159 -0
- fast_agent/mcp/prompt_message_extended.py +155 -0
- fast_agent/mcp/prompt_render.py +84 -0
- fast_agent/mcp/prompt_serialization.py +580 -0
- fast_agent/mcp/prompts/__init__.py +0 -0
- fast_agent/mcp/prompts/__main__.py +7 -0
- fast_agent/mcp/prompts/prompt_constants.py +18 -0
- fast_agent/mcp/prompts/prompt_helpers.py +238 -0
- fast_agent/mcp/prompts/prompt_load.py +186 -0
- fast_agent/mcp/prompts/prompt_server.py +552 -0
- fast_agent/mcp/prompts/prompt_template.py +438 -0
- fast_agent/mcp/resource_utils.py +215 -0
- fast_agent/mcp/sampling.py +200 -0
- fast_agent/mcp/server/__init__.py +4 -0
- fast_agent/mcp/server/agent_server.py +613 -0
- fast_agent/mcp/skybridge.py +44 -0
- fast_agent/mcp/sse_tracking.py +287 -0
- fast_agent/mcp/stdio_tracking_simple.py +59 -0
- fast_agent/mcp/streamable_http_tracking.py +309 -0
- fast_agent/mcp/tool_execution_handler.py +137 -0
- fast_agent/mcp/tool_permission_handler.py +88 -0
- fast_agent/mcp/transport_tracking.py +634 -0
- fast_agent/mcp/types.py +24 -0
- fast_agent/mcp/ui_agent.py +48 -0
- fast_agent/mcp/ui_mixin.py +209 -0
- fast_agent/mcp_server_registry.py +89 -0
- fast_agent/py.typed +0 -0
- fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
- fast_agent/resources/examples/data-analysis/analysis.py +68 -0
- fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
- fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
- fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
- fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
- fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
- fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
- fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
- fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
- fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
- fast_agent/resources/examples/researcher/researcher.py +36 -0
- fast_agent/resources/examples/tensorzero/.env.sample +2 -0
- fast_agent/resources/examples/tensorzero/Makefile +31 -0
- fast_agent/resources/examples/tensorzero/README.md +56 -0
- fast_agent/resources/examples/tensorzero/agent.py +35 -0
- fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
- fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
- fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
- fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
- fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
- fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
- fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
- fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
- fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
- fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
- fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
- fast_agent/resources/examples/workflows/chaining.py +37 -0
- fast_agent/resources/examples/workflows/evaluator.py +77 -0
- fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
- fast_agent/resources/examples/workflows/graded_report.md +89 -0
- fast_agent/resources/examples/workflows/human_input.py +28 -0
- fast_agent/resources/examples/workflows/maker.py +156 -0
- fast_agent/resources/examples/workflows/orchestrator.py +70 -0
- fast_agent/resources/examples/workflows/parallel.py +56 -0
- fast_agent/resources/examples/workflows/router.py +69 -0
- fast_agent/resources/examples/workflows/short_story.md +13 -0
- fast_agent/resources/examples/workflows/short_story.txt +19 -0
- fast_agent/resources/setup/.gitignore +30 -0
- fast_agent/resources/setup/agent.py +28 -0
- fast_agent/resources/setup/fastagent.config.yaml +65 -0
- fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
- fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
- fast_agent/skills/__init__.py +9 -0
- fast_agent/skills/registry.py +235 -0
- fast_agent/tools/elicitation.py +369 -0
- fast_agent/tools/shell_runtime.py +402 -0
- fast_agent/types/__init__.py +59 -0
- fast_agent/types/conversation_summary.py +294 -0
- fast_agent/types/llm_stop_reason.py +78 -0
- fast_agent/types/message_search.py +249 -0
- fast_agent/ui/__init__.py +38 -0
- fast_agent/ui/console.py +59 -0
- fast_agent/ui/console_display.py +1080 -0
- fast_agent/ui/elicitation_form.py +946 -0
- fast_agent/ui/elicitation_style.py +59 -0
- fast_agent/ui/enhanced_prompt.py +1400 -0
- fast_agent/ui/history_display.py +734 -0
- fast_agent/ui/interactive_prompt.py +1199 -0
- fast_agent/ui/markdown_helpers.py +104 -0
- fast_agent/ui/markdown_truncator.py +1004 -0
- fast_agent/ui/mcp_display.py +857 -0
- fast_agent/ui/mcp_ui_utils.py +235 -0
- fast_agent/ui/mermaid_utils.py +169 -0
- fast_agent/ui/message_primitives.py +50 -0
- fast_agent/ui/notification_tracker.py +205 -0
- fast_agent/ui/plain_text_truncator.py +68 -0
- fast_agent/ui/progress_display.py +10 -0
- fast_agent/ui/rich_progress.py +195 -0
- fast_agent/ui/streaming.py +774 -0
- fast_agent/ui/streaming_buffer.py +449 -0
- fast_agent/ui/tool_display.py +422 -0
- fast_agent/ui/usage_display.py +204 -0
- fast_agent/utils/__init__.py +5 -0
- fast_agent/utils/reasoning_stream_parser.py +77 -0
- fast_agent/utils/time.py +22 -0
- fast_agent/workflow_telemetry.py +261 -0
- fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
- fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
- fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
- fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
- fast_agent_mcp-0.4.7.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,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
|