a3s_code_core/agent_api/capability_facade.rs
1use super::*;
2
3impl AgentSession {
4 // Advanced optional Queue API
5 // ========================================================================
6
7 /// Returns whether this session has an advanced lane queue configured.
8 pub fn has_queue(&self) -> bool {
9 QueueControl::from_session(self).has_queue()
10 }
11
12 /// Configure a lane's handler mode for explicit external/hybrid dispatch.
13 ///
14 /// Only effective when a queue is configured via `SessionOptions::with_queue_config`.
15 pub async fn set_lane_handler(
16 &self,
17 lane: SessionLane,
18 config: LaneHandlerConfig,
19 ) -> crate::error::Result<()> {
20 let _mutation = self.close_handle.extension_mutation.lock().await;
21 if self.is_closed() {
22 return Err(crate::error::CodeError::SessionClosed {
23 session_id: self.session_id.clone(),
24 });
25 }
26 QueueControl::from_session(self)
27 .set_lane_handler(lane, config)
28 .await;
29 if self.is_closed() {
30 return Err(crate::error::CodeError::SessionClosed {
31 session_id: self.session_id.clone(),
32 });
33 }
34 Ok(())
35 }
36
37 /// Complete an external queue task by ID.
38 ///
39 /// Returns `true` if the task was found and completed, `false` if not found.
40 pub async fn complete_external_task(&self, task_id: &str, result: ExternalTaskResult) -> bool {
41 QueueControl::from_session(self)
42 .complete_external_task(task_id, result)
43 .await
44 }
45
46 /// Get pending external queue tasks awaiting completion by an external handler.
47 pub async fn pending_external_tasks(&self) -> Vec<ExternalTask> {
48 QueueControl::from_session(self)
49 .pending_external_tasks()
50 .await
51 }
52
53 /// Get optional queue statistics (pending, active, external counts per lane).
54 pub async fn queue_stats(&self) -> SessionQueueStats {
55 QueueControl::from_session(self).stats().await
56 }
57
58 /// Get a metrics snapshot from the optional queue (if metrics are enabled).
59 pub async fn queue_metrics(&self) -> Option<MetricsSnapshot> {
60 QueueControl::from_session(self).metrics().await
61 }
62
63 /// Get dead letters from the optional queue's DLQ (if DLQ is enabled).
64 pub async fn dead_letters(&self) -> Vec<DeadLetter> {
65 QueueControl::from_session(self).dead_letters().await
66 }
67
68 // ========================================================================
69 // MCP API
70 // ========================================================================
71
72 /// Register all agents found in a directory with the live session.
73 ///
74 /// Scans `dir` for `*.yaml`, `*.yml`, and `*.md` agent definition files,
75 /// parses them, and adds each one to the shared `AgentRegistry` used by the
76 /// `task` tool. New agents are immediately usable via `task(agent="…")` in
77 /// the same session — no restart required.
78 ///
79 /// Returns the number of agents successfully loaded from the directory.
80 pub fn register_agent_dir(&self, dir: &std::path::Path) -> crate::error::Result<usize> {
81 let agents = crate::subagent::load_agents_from_dir(dir);
82 self.close_handle.mutate_immediate(|| {
83 let count = agents.len();
84 for agent in agents {
85 tracing::info!(
86 session_id = %self.session_id,
87 agent = agent.name,
88 dir = %dir.display(),
89 "Dynamically registered agent"
90 );
91 self.agent_registry.register(agent);
92 }
93 count
94 })
95 }
96
97 /// Register a disposable worker agent with the live session.
98 ///
99 /// The returned definition is immediately available to the `task` tool by
100 /// worker name, so callers can create many reproducible workers without
101 /// writing temporary agent files or restarting the session.
102 pub fn register_worker_agent(
103 &self,
104 spec: crate::subagent::WorkerAgentSpec,
105 ) -> crate::error::Result<crate::subagent::AgentDefinition> {
106 self.close_handle.mutate_immediate(|| {
107 SessionExtensionRuntime::from_session(self).register_worker_agent(spec)
108 })
109 }
110
111 /// Register multiple disposable worker agents with the live session.
112 pub fn register_worker_agents<I>(
113 &self,
114 specs: I,
115 ) -> crate::error::Result<Vec<crate::subagent::AgentDefinition>>
116 where
117 I: IntoIterator<Item = crate::subagent::WorkerAgentSpec>,
118 {
119 self.close_handle.mutate_immediate(|| {
120 SessionExtensionRuntime::from_session(self).register_worker_agents(specs)
121 })
122 }
123
124 /// Add an MCP server to this session.
125 ///
126 /// Registers, connects, and makes all tools immediately available for the
127 /// agent to call. Tool names follow the convention `mcp__<name>__<tool>`.
128 ///
129 /// Returns the number of tools registered from the server.
130 pub async fn add_mcp_server(
131 &self,
132 config: crate::mcp::McpServerConfig,
133 ) -> crate::error::Result<usize> {
134 SessionExtensionRuntime::from_session(self)
135 .add_mcp_server(config)
136 .await
137 }
138
139 /// The session's tool executor, for installing agent-dir `tools/` entries
140 /// (e.g. a `kind = "script"` tool) into the live registry. Internal seam used
141 /// by [`serve::install_agent_dir_tools`](crate::serve::install_agent_dir_tools)
142 /// (the only caller, hence the `serve` gate).
143 #[cfg(feature = "serve")]
144 pub(crate) fn tool_executor(&self) -> &Arc<crate::tools::ToolExecutor> {
145 &self.tool_executor
146 }
147
148 /// Register a host-provided dynamic tool into the live session. Enables an
149 /// embedding app (e.g. the a3s-code CLI's login-gated `runtime` A3S Runtime
150 /// offload tool) to add a native tool at runtime; it enters the LLM's toolset
151 /// on the next run (`build_agent_loop` re-snapshots `definitions()` per run),
152 /// the same way MCP tools surface after `add_mcp_server`. Idempotent by name.
153 pub fn register_dynamic_tool(
154 &self,
155 tool: Arc<dyn crate::tools::Tool>,
156 ) -> crate::error::Result<()> {
157 self.close_handle
158 .mutate_immediate(|| self.tool_executor.register_dynamic_tool(tool))
159 }
160
161 /// Register the A3S Flow-backed dynamic workflow tool for this live session.
162 ///
163 /// The tool is named `dynamic_workflow`. It accepts a sandboxed JavaScript
164 /// PTC workflow script and executes it through
165 /// [`crate::DynamicWorkflowRuntime`], so A3S Flow owns workflow replay while
166 /// the script can still call A3S Code tools.
167 pub fn register_dynamic_workflow_runtime(&self) -> crate::error::Result<()> {
168 self.close_handle.mutate_immediate(|| {
169 crate::tools::register_dynamic_workflow(self.tool_executor.registry())
170 })
171 }
172
173 /// Remove a previously host-registered dynamic tool by name (e.g. on logout).
174 /// No-op if no tool of that name is registered.
175 pub fn unregister_dynamic_tool(&self, name: &str) -> crate::error::Result<()> {
176 self.close_handle
177 .mutate_immediate(|| self.tool_executor.unregister_dynamic_tool(name))
178 }
179
180 /// Remove an MCP server from this session.
181 ///
182 /// Disconnects the server and unregisters all its tools from the executor.
183 /// No-op if the server was never added.
184 pub async fn remove_mcp_server(&self, server_name: &str) -> crate::error::Result<()> {
185 SessionExtensionRuntime::from_session(self)
186 .remove_mcp_server(server_name)
187 .await
188 }
189
190 /// Return the connection status of all MCP servers registered with this session.
191 pub async fn mcp_status(
192 &self,
193 ) -> std::collections::HashMap<String, crate::mcp::McpServerStatus> {
194 SessionExtensionRuntime::from_session(self)
195 .mcp_status()
196 .await
197 }
198}