Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions dare_framework/agent/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
from dare_framework.skill._internal.action_handler import SkillsActionHandler
from dare_framework.skill._internal.filesystem_skill_loader import FileSystemSkillLoader
from dare_framework.tool import ToolResult, CapabilityDescriptor
from dare_framework.tool._internal.tools.ask_user import AskUserTool, IUserInputHandler
from dare_framework.tool.action_handler import ApprovalsActionHandler, ToolsActionHandler
from dare_framework.tool._internal.control.approval_manager import ToolApprovalManager
from dare_framework.tool.interfaces import IExecutionControl
Expand Down Expand Up @@ -124,6 +125,9 @@ def __init__(self, name: str) -> None:
# Optional transport channel (agent-facing).
self._agent_channel: AgentChannel | None = None

# User input handler for the built-in ask_user tool.
self._user_input_handler: IUserInputHandler | None = None

self._sys_skill: Skill | None = None
self._enable_skill_tool: bool = True
self._skill_loaders: list[ISkillLoader] = []
Expand Down Expand Up @@ -242,6 +246,15 @@ def with_plan_provider(self: TBuilder, plan_provider: IToolProvider) -> TBuilder
self._plan_provider = plan_provider
return self

def with_user_input_handler(self: TBuilder, handler: IUserInputHandler) -> TBuilder:
"""Inject a custom user-input handler for the built-in ``ask_user`` tool.

If not called, the default ``CLIUserInputHandler`` (stdin/stdout) is
used. Pass a custom implementation for web UIs, Slack bots, etc.
"""
self._user_input_handler = handler
return self

def with_agent_channel(self: TBuilder, channel: AgentChannel) -> TBuilder:
"""Attach an AgentChannel for transport-backed output/hook streaming."""
self._agent_channel = channel
Expand Down Expand Up @@ -481,6 +494,10 @@ def _resolve_tools(self, knowledge: IKnowledge | None, skill_store: ISkillStore
if knowledge is not None:
explicit.append(KnowledgeGetTool(knowledge))
explicit.append(KnowledgeAddTool(knowledge))
# Built-in ask_user tool — always available so the LLM can request user input.
ask_user_tool = AskUserTool(handler=self._user_input_handler)
if ask_user_tool.name not in explicit_names:
explicit.append(ask_user_tool)
return explicit

def _build_context(
Expand Down
19 changes: 16 additions & 3 deletions dare_framework/tool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,24 @@
"IToolProvider",
# Supported defaults
"ToolManager",
# Built-in ask_user
"AskUserTool",
"CLIUserInputHandler",
"IUserInputHandler",
]


_LAZY_ATTRS: dict[str, tuple[str, str]] = {
"ToolManager": ("dare_framework.tool.tool_manager", "ToolManager"),
"AskUserTool": ("dare_framework.tool._internal.tools.ask_user", "AskUserTool"),
"CLIUserInputHandler": ("dare_framework.tool._internal.tools.ask_user", "CLIUserInputHandler"),
"IUserInputHandler": ("dare_framework.tool._internal.tools.ask_user", "IUserInputHandler"),
}


def __getattr__(name: str) -> Any:
if name == "ToolManager":
from dare_framework.tool.tool_manager import ToolManager as _ToolManager
return _ToolManager
if name in _LAZY_ATTRS:
module_name, attr_name = _LAZY_ATTRS[name]
module = __import__(module_name, fromlist=[attr_name])
return getattr(module, attr_name)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
6 changes: 6 additions & 0 deletions dare_framework/tool/_internal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
from typing import Any

__all__ = [
"AskUserTool",
"CLIUserInputHandler",
"IUserInputHandler",
"Checkpoint",
"DefaultExecutionControl",
"FileExecutionControl",
Expand All @@ -24,6 +27,9 @@
]

_LAZY_EXPORTS: dict[str, tuple[str, str]] = {
"AskUserTool": ("dare_framework.tool._internal.tools.ask_user", "AskUserTool"),
"CLIUserInputHandler": ("dare_framework.tool._internal.tools.ask_user", "CLIUserInputHandler"),
"IUserInputHandler": ("dare_framework.tool._internal.tools.ask_user", "IUserInputHandler"),
"Checkpoint": (
"dare_framework.tool._internal.control.default_execution_control",
"Checkpoint",
Expand Down
8 changes: 8 additions & 0 deletions dare_framework/tool/_internal/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
"""Built-in tool implementations for the tool domain."""

from dare_framework.tool._internal.tools.ask_user import (
AskUserTool,
CLIUserInputHandler,
IUserInputHandler,
)
from dare_framework.tool._internal.tools.echo_tool import EchoTool
from dare_framework.tool._internal.tools.noop_tool import NoopTool
from dare_framework.tool._internal.tools.read_code import ReadCodeTool
Expand All @@ -13,6 +18,9 @@
from dare_framework.tool._internal.tools.edit_line import EditLineTool

__all__ = [
"AskUserTool",
"CLIUserInputHandler",
"IUserInputHandler",
"EchoTool",
"NoopTool",
"ReadCodeTool",
Expand Down
Loading
Loading