mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-06 20:15:17 +02:00
refactor(agents): move tools package to app/agents/shared (slice 6)
Relocate the entire new_chat/tools/ package (62 files incl. registry, hitl, MCP cluster, and all connector subpackages: gmail/slack/discord/teams/drive/etc.) to the shared kernel. The package turned out to be a clean cohesive cluster: its only references to non-tools new_chat modules were comments, and its middleware deps were already flipped to shared in slice 5c. Flip 33 live importers (multi-agent, flows, routes, services, anonymous_agent, tests). Re-export shims remain for the frozen single-agent stack: a package __init__ mirroring the public surface (new_chat.__init__ imports it) plus invalid_tool + registry submodule shims (chat_deepagent imports those). Resolves slice 5c's two transient back-edges: shared/middleware/action_log (TYPE_CHECKING ToolDefinition) and tool_call_repair (local INVALID_TOOL_NAME) now point at app.agents.shared.tools.
This commit is contained in:
parent
a7fde2a48e
commit
aab95b9130
98 changed files with 1232 additions and 1152 deletions
|
|
@ -27,8 +27,8 @@ from app.agents.shared.filesystem_backends import build_backend_resolver
|
||||||
from app.agents.shared.filesystem_selection import FilesystemMode, FilesystemSelection
|
from app.agents.shared.filesystem_selection import FilesystemMode, FilesystemSelection
|
||||||
from app.agents.shared.llm_config import AgentConfig
|
from app.agents.shared.llm_config import AgentConfig
|
||||||
from app.agents.shared.prompt_caching import apply_litellm_prompt_caching
|
from app.agents.shared.prompt_caching import apply_litellm_prompt_caching
|
||||||
from app.agents.new_chat.tools.invalid_tool import INVALID_TOOL_NAME, invalid_tool
|
from app.agents.shared.tools.invalid_tool import INVALID_TOOL_NAME, invalid_tool
|
||||||
from app.agents.new_chat.tools.registry import build_tools_async
|
from app.agents.shared.tools.registry import build_tools_async
|
||||||
from app.db import ChatVisibility
|
from app.db import ChatVisibility
|
||||||
from app.services.connector_service import ConnectorService
|
from app.services.connector_service import ConnectorService
|
||||||
from app.services.user_tool_allowlist import (
|
from app.services.user_tool_allowlist import (
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import logging
|
||||||
|
|
||||||
from app.agents.shared.feature_flags import AgentFeatureFlags
|
from app.agents.shared.feature_flags import AgentFeatureFlags
|
||||||
from app.agents.shared.middleware import ActionLogMiddleware
|
from app.agents.shared.middleware import ActionLogMiddleware
|
||||||
from app.agents.new_chat.tools.registry import BUILTIN_TOOLS
|
from app.agents.shared.tools.registry import BUILTIN_TOOLS
|
||||||
|
|
||||||
from ..shared.flags import enabled
|
from ..shared.flags import enabled
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
from app.agents.new_chat.tools.google_calendar.create_event import (
|
from app.agents.shared.tools.google_calendar.create_event import (
|
||||||
create_create_calendar_event_tool,
|
create_create_calendar_event_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.google_calendar.delete_event import (
|
from app.agents.shared.tools.google_calendar.delete_event import (
|
||||||
create_delete_calendar_event_tool,
|
create_delete_calendar_event_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.google_calendar.search_events import (
|
from app.agents.shared.tools.google_calendar.search_events import (
|
||||||
create_search_calendar_events_tool,
|
create_search_calendar_events_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.google_calendar.update_event import (
|
from app.agents.shared.tools.google_calendar.update_event import (
|
||||||
create_update_calendar_event_tool,
|
create_update_calendar_event_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.future import select
|
from sqlalchemy.future import select
|
||||||
|
|
||||||
from app.agents.new_chat.tools.gmail.search_emails import _build_credentials
|
from app.agents.shared.tools.gmail.search_emails import _build_credentials
|
||||||
from app.db import SearchSourceConnector, SearchSourceConnectorType
|
from app.db import SearchSourceConnector, SearchSourceConnectorType
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
from app.agents.new_chat.tools.discord.list_channels import (
|
from app.agents.shared.tools.discord.list_channels import (
|
||||||
create_list_discord_channels_tool,
|
create_list_discord_channels_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.discord.read_messages import (
|
from app.agents.shared.tools.discord.read_messages import (
|
||||||
create_read_discord_messages_tool,
|
create_read_discord_messages_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.discord.send_message import (
|
from app.agents.shared.tools.discord.send_message import (
|
||||||
create_send_discord_message_tool,
|
create_send_discord_message_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from app.agents.new_chat.tools.dropbox.create_file import (
|
from app.agents.shared.tools.dropbox.create_file import (
|
||||||
create_create_dropbox_file_tool,
|
create_create_dropbox_file_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.dropbox.trash_file import (
|
from app.agents.shared.tools.dropbox.trash_file import (
|
||||||
create_delete_dropbox_file_tool,
|
create_delete_dropbox_file_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
from app.agents.new_chat.tools.gmail.create_draft import (
|
from app.agents.shared.tools.gmail.create_draft import (
|
||||||
create_create_gmail_draft_tool,
|
create_create_gmail_draft_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.gmail.read_email import (
|
from app.agents.shared.tools.gmail.read_email import (
|
||||||
create_read_gmail_email_tool,
|
create_read_gmail_email_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.gmail.search_emails import (
|
from app.agents.shared.tools.gmail.search_emails import (
|
||||||
create_search_gmail_tool,
|
create_search_gmail_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.gmail.send_email import (
|
from app.agents.shared.tools.gmail.send_email import (
|
||||||
create_send_gmail_email_tool,
|
create_send_gmail_email_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.gmail.trash_email import (
|
from app.agents.shared.tools.gmail.trash_email import (
|
||||||
create_trash_gmail_email_tool,
|
create_trash_gmail_email_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.gmail.update_draft import (
|
from app.agents.shared.tools.gmail.update_draft import (
|
||||||
create_update_gmail_draft_tool,
|
create_update_gmail_draft_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ def create_read_gmail_email_tool(
|
||||||
"message": "Composio connected account ID not found for this Gmail connector.",
|
"message": "Composio connected account ID not found for this Gmail connector.",
|
||||||
}
|
}
|
||||||
|
|
||||||
from app.agents.new_chat.tools.gmail.search_emails import (
|
from app.agents.shared.tools.gmail.search_emails import (
|
||||||
_format_gmail_summary,
|
_format_gmail_summary,
|
||||||
)
|
)
|
||||||
from app.services.composio_service import ComposioService
|
from app.services.composio_service import ComposioService
|
||||||
|
|
@ -97,7 +97,7 @@ def create_read_gmail_email_tool(
|
||||||
"content": content,
|
"content": content,
|
||||||
}
|
}
|
||||||
|
|
||||||
from app.agents.new_chat.tools.gmail.search_emails import (
|
from app.agents.shared.tools.gmail.search_emails import (
|
||||||
_build_credentials,
|
_build_credentials,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ def create_search_gmail_tool(
|
||||||
"message": "Composio connected account ID not found for this Gmail connector.",
|
"message": "Composio connected account ID not found for this Gmail connector.",
|
||||||
}
|
}
|
||||||
|
|
||||||
from app.agents.new_chat.tools.gmail.search_emails import (
|
from app.agents.shared.tools.gmail.search_emails import (
|
||||||
_format_gmail_summary,
|
_format_gmail_summary,
|
||||||
)
|
)
|
||||||
from app.services.composio_service import ComposioService
|
from app.services.composio_service import ComposioService
|
||||||
|
|
@ -98,7 +98,7 @@ def create_search_gmail_tool(
|
||||||
}
|
}
|
||||||
return {"status": "success", "emails": emails, "total": len(emails)}
|
return {"status": "success", "emails": emails, "total": len(emails)}
|
||||||
|
|
||||||
from app.agents.new_chat.tools.gmail.search_emails import (
|
from app.agents.shared.tools.gmail.search_emails import (
|
||||||
_build_credentials,
|
_build_credentials,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from app.agents.new_chat.tools.google_drive.create_file import (
|
from app.agents.shared.tools.google_drive.create_file import (
|
||||||
create_create_google_drive_file_tool,
|
create_create_google_drive_file_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.google_drive.trash_file import (
|
from app.agents.shared.tools.google_drive.trash_file import (
|
||||||
create_delete_google_drive_file_tool,
|
create_delete_google_drive_file_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
from app.agents.new_chat.tools.luma.create_event import (
|
from app.agents.shared.tools.luma.create_event import (
|
||||||
create_create_luma_event_tool,
|
create_create_luma_event_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.luma.list_events import (
|
from app.agents.shared.tools.luma.list_events import (
|
||||||
create_list_luma_events_tool,
|
create_list_luma_events_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.luma.read_event import (
|
from app.agents.shared.tools.luma.read_event import (
|
||||||
create_read_luma_event_tool,
|
create_read_luma_event_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from app.agents.new_chat.tools.onedrive.create_file import (
|
from app.agents.shared.tools.onedrive.create_file import (
|
||||||
create_create_onedrive_file_tool,
|
create_create_onedrive_file_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.onedrive.trash_file import (
|
from app.agents.shared.tools.onedrive.trash_file import (
|
||||||
create_delete_onedrive_file_tool,
|
create_delete_onedrive_file_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
from app.agents.new_chat.tools.teams.list_channels import (
|
from app.agents.shared.tools.teams.list_channels import (
|
||||||
create_list_teams_channels_tool,
|
create_list_teams_channels_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.teams.read_messages import (
|
from app.agents.shared.tools.teams.read_messages import (
|
||||||
create_read_teams_messages_tool,
|
create_read_teams_messages_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.teams.send_message import (
|
from app.agents.shared.tools.teams.send_message import (
|
||||||
create_send_teams_message_tool,
|
create_send_teams_message_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from app.agents.multi_agent_chat.constants import (
|
from app.agents.multi_agent_chat.constants import (
|
||||||
CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS,
|
CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.mcp_tool import load_mcp_tools
|
from app.agents.shared.tools.mcp_tool import load_mcp_tools
|
||||||
from app.db import SearchSourceConnector
|
from app.db import SearchSourceConnector
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ from app.agents.shared.middleware import (
|
||||||
RetryAfterMiddleware,
|
RetryAfterMiddleware,
|
||||||
create_surfsense_compaction_middleware,
|
create_surfsense_compaction_middleware,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.web_search import create_web_search_tool
|
from app.agents.shared.tools.web_search import create_web_search_tool
|
||||||
|
|
||||||
# Cap how much of an uploaded document we inline into the system prompt. The
|
# Cap how much of an uploaded document we inline into the system prompt. The
|
||||||
# upload endpoint allows files up to several MB, but the doc is re-sent on
|
# upload endpoint allows files up to several MB, but the doc is re-sent on
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,35 @@
|
||||||
"""
|
"""Backward-compatible shim package.
|
||||||
Tools module for SurfSense deep agent.
|
|
||||||
|
|
||||||
This module contains all the tools available to the SurfSense agent.
|
The agent tools now live in the shared kernel at ``app.agents.shared.tools``.
|
||||||
To add a new tool, see the documentation in registry.py.
|
This package re-exports the public surface (and keeps ``invalid_tool`` /
|
||||||
|
``registry`` submodule shims) so the frozen single-agent stack
|
||||||
Available tools:
|
(``new_chat.__init__`` and ``chat_deepagent``) keeps working until that stack is
|
||||||
- generate_podcast: Generate audio podcasts from content
|
retired. All live code imports from ``app.agents.shared.tools`` directly.
|
||||||
- generate_video_presentation: Generate video presentations with slides and narration
|
|
||||||
- generate_image: Generate images from text descriptions using AI models
|
|
||||||
- scrape_webpage: Extract content from webpages
|
|
||||||
- update_memory: Update the user's / team's memory document
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Registry exports
|
from app.agents.shared.tools import (
|
||||||
# Tool factory exports (for direct use)
|
|
||||||
from .generate_image import create_generate_image_tool
|
|
||||||
from .knowledge_base import (
|
|
||||||
CONNECTOR_DESCRIPTIONS,
|
|
||||||
format_documents_for_context,
|
|
||||||
search_knowledge_base_async,
|
|
||||||
)
|
|
||||||
from .podcast import create_generate_podcast_tool
|
|
||||||
from .registry import (
|
|
||||||
BUILTIN_TOOLS,
|
BUILTIN_TOOLS,
|
||||||
|
CONNECTOR_DESCRIPTIONS,
|
||||||
ToolDefinition,
|
ToolDefinition,
|
||||||
build_tools,
|
build_tools,
|
||||||
|
create_generate_image_tool,
|
||||||
|
create_generate_podcast_tool,
|
||||||
|
create_generate_video_presentation_tool,
|
||||||
|
create_scrape_webpage_tool,
|
||||||
|
create_update_memory_tool,
|
||||||
|
create_update_team_memory_tool,
|
||||||
|
format_documents_for_context,
|
||||||
get_all_tool_names,
|
get_all_tool_names,
|
||||||
get_default_enabled_tools,
|
get_default_enabled_tools,
|
||||||
get_tool_by_name,
|
get_tool_by_name,
|
||||||
|
search_knowledge_base_async,
|
||||||
)
|
)
|
||||||
from .scrape_webpage import create_scrape_webpage_tool
|
|
||||||
from .update_memory import create_update_memory_tool, create_update_team_memory_tool
|
|
||||||
from .video_presentation import create_generate_video_presentation_tool
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Registry
|
|
||||||
"BUILTIN_TOOLS",
|
"BUILTIN_TOOLS",
|
||||||
# Knowledge base utilities
|
|
||||||
"CONNECTOR_DESCRIPTIONS",
|
"CONNECTOR_DESCRIPTIONS",
|
||||||
"ToolDefinition",
|
"ToolDefinition",
|
||||||
"build_tools",
|
"build_tools",
|
||||||
# Tool factories
|
|
||||||
"create_generate_image_tool",
|
"create_generate_image_tool",
|
||||||
"create_generate_podcast_tool",
|
"create_generate_podcast_tool",
|
||||||
"create_generate_video_presentation_tool",
|
"create_generate_video_presentation_tool",
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,15 @@
|
||||||
"""
|
"""Backward-compatible shim.
|
||||||
The ``invalid`` fallback tool.
|
|
||||||
|
|
||||||
When the model emits a tool call whose name doesn't match any registered
|
Moved to ``app.agents.shared.tools.invalid_tool``. Re-exported here for the
|
||||||
tool, :class:`ToolCallNameRepairMiddleware` rewrites the call to ``invalid``
|
frozen single-agent stack (``chat_deepagent``) until that stack is retired.
|
||||||
with the original name and a parser/validation error string. This tool's
|
|
||||||
execution then returns that error to the model so it can self-correct.
|
|
||||||
|
|
||||||
Ported from OpenCode's ``packages/opencode/src/tool/invalid.ts`` —
|
|
||||||
LangChain has no equivalent fallback path; the default behavior on an
|
|
||||||
unknown tool name is a hard ``ToolNotFoundError`` which kills the turn.
|
|
||||||
|
|
||||||
Critically, the :class:`ToolDefinition` for this tool is **excluded** from
|
|
||||||
the system-prompt tool list and from ``LLMToolSelectorMiddleware`` selection
|
|
||||||
(see ``ToolDefinition.always_include`` filtering in the registry) — the
|
|
||||||
model never advertises ``invalid`` as a callable. It only ever shows up
|
|
||||||
in the tool registry so LangGraph can dispatch the rewritten call.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from app.agents.shared.tools.invalid_tool import (
|
||||||
|
INVALID_TOOL_DESCRIPTION,
|
||||||
from langchain_core.tools import tool
|
INVALID_TOOL_NAME,
|
||||||
|
invalid_tool,
|
||||||
INVALID_TOOL_NAME = "invalid"
|
|
||||||
INVALID_TOOL_DESCRIPTION = "Do not use"
|
|
||||||
|
|
||||||
|
|
||||||
def _format_invalid_message(tool: str | None, error: str | None) -> str:
|
|
||||||
"""Return the user-visible error string. Mirrors ``invalid.ts``."""
|
|
||||||
name = tool or "<unknown>"
|
|
||||||
detail = error or "(no error message provided)"
|
|
||||||
return (
|
|
||||||
f"The arguments provided to the tool `{name}` are invalid: {detail}\n"
|
|
||||||
f"Read the tool's docstring carefully and try again with valid arguments."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@tool(name_or_callable=INVALID_TOOL_NAME, description=INVALID_TOOL_DESCRIPTION)
|
|
||||||
def invalid_tool(tool: str | None = None, error: str | None = None) -> str:
|
|
||||||
"""Return a human-readable explanation of a tool-call validation failure.
|
|
||||||
|
|
||||||
Activated only when :class:`ToolCallNameRepairMiddleware` rewrites a
|
|
||||||
failed tool call to ``invalid`` with the original tool name and the
|
|
||||||
error message produced during validation.
|
|
||||||
"""
|
|
||||||
return _format_invalid_message(tool, error)
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"INVALID_TOOL_DESCRIPTION",
|
"INVALID_TOOL_DESCRIPTION",
|
||||||
"INVALID_TOOL_NAME",
|
"INVALID_TOOL_NAME",
|
||||||
|
|
|
||||||
|
|
@ -1,962 +1,19 @@
|
||||||
"""Tools registry for SurfSense deep agent.
|
"""Backward-compatible shim.
|
||||||
|
|
||||||
This module provides a registry pattern for managing tools in the SurfSense agent.
|
Moved to ``app.agents.shared.tools.registry``. Re-exported here for the frozen
|
||||||
It makes it easy for OSS contributors to add new tools by:
|
single-agent stack (``chat_deepagent``) until that stack is retired.
|
||||||
1. Creating a tool factory function in a new file in this directory
|
|
||||||
2. Registering the tool in the BUILTIN_TOOLS list below
|
|
||||||
|
|
||||||
Example of adding a new tool:
|
|
||||||
------------------------------
|
|
||||||
1. Create your tool file (e.g., `tools/my_tool.py`):
|
|
||||||
|
|
||||||
from langchain_core.tools import tool
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
def create_my_tool(search_space_id: int, db_session: AsyncSession):
|
|
||||||
@tool
|
|
||||||
async def my_tool(param: str) -> dict:
|
|
||||||
'''My tool description.'''
|
|
||||||
# Your implementation
|
|
||||||
return {"result": "success"}
|
|
||||||
return my_tool
|
|
||||||
|
|
||||||
2. Import and register in this file:
|
|
||||||
|
|
||||||
from .my_tool import create_my_tool
|
|
||||||
|
|
||||||
# Add to BUILTIN_TOOLS list:
|
|
||||||
ToolDefinition(
|
|
||||||
name="my_tool",
|
|
||||||
description="Description of what your tool does",
|
|
||||||
factory=lambda deps: create_my_tool(
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
),
|
|
||||||
requires=["search_space_id", "db_session"],
|
|
||||||
),
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
from app.agents.shared.tools.registry import (
|
||||||
from collections.abc import Callable
|
BUILTIN_TOOLS,
|
||||||
from dataclasses import dataclass, field
|
ToolDefinition,
|
||||||
from typing import Any
|
build_tools_async,
|
||||||
|
get_connector_gated_tools,
|
||||||
from langchain_core.tools import BaseTool
|
|
||||||
|
|
||||||
from app.agents.shared.middleware.dedup_tool_calls import (
|
|
||||||
wrap_dedup_key_by_arg_name,
|
|
||||||
)
|
|
||||||
from app.db import ChatVisibility
|
|
||||||
|
|
||||||
from .confluence import (
|
|
||||||
create_create_confluence_page_tool,
|
|
||||||
create_delete_confluence_page_tool,
|
|
||||||
create_update_confluence_page_tool,
|
|
||||||
)
|
|
||||||
from .connected_accounts import create_get_connected_accounts_tool
|
|
||||||
from .discord import (
|
|
||||||
create_list_discord_channels_tool,
|
|
||||||
create_read_discord_messages_tool,
|
|
||||||
create_send_discord_message_tool,
|
|
||||||
)
|
|
||||||
from .dropbox import (
|
|
||||||
create_create_dropbox_file_tool,
|
|
||||||
create_delete_dropbox_file_tool,
|
|
||||||
)
|
|
||||||
from .generate_image import create_generate_image_tool
|
|
||||||
from .gmail import (
|
|
||||||
create_create_gmail_draft_tool,
|
|
||||||
create_read_gmail_email_tool,
|
|
||||||
create_search_gmail_tool,
|
|
||||||
create_send_gmail_email_tool,
|
|
||||||
create_trash_gmail_email_tool,
|
|
||||||
create_update_gmail_draft_tool,
|
|
||||||
)
|
|
||||||
from .google_calendar import (
|
|
||||||
create_create_calendar_event_tool,
|
|
||||||
create_delete_calendar_event_tool,
|
|
||||||
create_search_calendar_events_tool,
|
|
||||||
create_update_calendar_event_tool,
|
|
||||||
)
|
|
||||||
from .google_drive import (
|
|
||||||
create_create_google_drive_file_tool,
|
|
||||||
create_delete_google_drive_file_tool,
|
|
||||||
)
|
|
||||||
from .luma import (
|
|
||||||
create_create_luma_event_tool,
|
|
||||||
create_list_luma_events_tool,
|
|
||||||
create_read_luma_event_tool,
|
|
||||||
)
|
|
||||||
from .mcp_tool import load_mcp_tools
|
|
||||||
from .notion import (
|
|
||||||
create_create_notion_page_tool,
|
|
||||||
create_delete_notion_page_tool,
|
|
||||||
create_update_notion_page_tool,
|
|
||||||
)
|
|
||||||
from .onedrive import (
|
|
||||||
create_create_onedrive_file_tool,
|
|
||||||
create_delete_onedrive_file_tool,
|
|
||||||
)
|
|
||||||
from .podcast import create_generate_podcast_tool
|
|
||||||
from .report import create_generate_report_tool
|
|
||||||
from .resume import create_generate_resume_tool
|
|
||||||
from .scrape_webpage import create_scrape_webpage_tool
|
|
||||||
from .teams import (
|
|
||||||
create_list_teams_channels_tool,
|
|
||||||
create_read_teams_messages_tool,
|
|
||||||
create_send_teams_message_tool,
|
|
||||||
)
|
|
||||||
from .update_memory import create_update_memory_tool, create_update_team_memory_tool
|
|
||||||
from .video_presentation import create_generate_video_presentation_tool
|
|
||||||
from .web_search import create_web_search_tool
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Tool Definition
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ToolDefinition:
|
|
||||||
"""Definition of a tool that can be added to the agent.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
name: Unique identifier for the tool
|
|
||||||
description: Human-readable description of what the tool does
|
|
||||||
factory: Callable that creates the tool. Receives a dict of dependencies.
|
|
||||||
requires: List of dependency names this tool needs (e.g., "search_space_id", "db_session")
|
|
||||||
enabled_by_default: Whether the tool is enabled when no explicit config is provided
|
|
||||||
required_connector: Searchable type string (e.g. ``"LINEAR_CONNECTOR"``)
|
|
||||||
that must be in ``available_connectors`` for the tool to be enabled.
|
|
||||||
dedup_key: Optional callable that maps a tool's ``args`` dict to a
|
|
||||||
string signature used by :class:`DedupHITLToolCallsMiddleware`
|
|
||||||
to drop duplicate calls within a single LLM response.
|
|
||||||
reverse: Optional callable that, given the tool's ``(args, result)``,
|
|
||||||
returns a ``ReverseDescriptor`` describing the inverse tool
|
|
||||||
invocation. Consumed by the snapshot/revert pipeline.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
name: str
|
|
||||||
description: str
|
|
||||||
factory: Callable[[dict[str, Any]], BaseTool]
|
|
||||||
requires: list[str] = field(default_factory=list)
|
|
||||||
enabled_by_default: bool = True
|
|
||||||
hidden: bool = False
|
|
||||||
required_connector: str | None = None
|
|
||||||
dedup_key: Callable[[dict[str, Any]], str] | None = None
|
|
||||||
reverse: Callable[[dict[str, Any], Any], dict[str, Any]] | None = None
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Deferred-import factories
|
|
||||||
# =============================================================================
|
|
||||||
# Used for tools whose impls live under ``multi_agent_chat``. Importing those
|
|
||||||
# at module-load time would cycle (``multi_agent_chat`` middleware imports
|
|
||||||
# this registry). The import inside the factory runs only when
|
|
||||||
# ``build_tools`` is called, by which point ``multi_agent_chat`` is fully
|
|
||||||
# initialised.
|
|
||||||
|
|
||||||
|
|
||||||
def _build_create_automation_tool(deps: dict[str, Any]) -> BaseTool:
|
|
||||||
from app.agents.multi_agent_chat.main_agent.tools.automation import (
|
|
||||||
create_create_automation_tool,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return create_create_automation_tool(
|
__all__ = [
|
||||||
search_space_id=deps["search_space_id"],
|
"BUILTIN_TOOLS",
|
||||||
user_id=deps["user_id"],
|
"ToolDefinition",
|
||||||
llm=deps["llm"],
|
"build_tools_async",
|
||||||
)
|
"get_connector_gated_tools",
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Built-in Tools Registry
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Registry of all built-in tools
|
|
||||||
# Contributors: Add your new tools here!
|
|
||||||
BUILTIN_TOOLS: list[ToolDefinition] = [
|
|
||||||
# Podcast generation tool
|
|
||||||
ToolDefinition(
|
|
||||||
name="generate_podcast",
|
|
||||||
description="Generate an audio podcast from provided content",
|
|
||||||
factory=lambda deps: create_generate_podcast_tool(
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
thread_id=deps["thread_id"],
|
|
||||||
),
|
|
||||||
requires=["search_space_id", "db_session", "thread_id"],
|
|
||||||
),
|
|
||||||
# Video presentation generation tool
|
|
||||||
ToolDefinition(
|
|
||||||
name="generate_video_presentation",
|
|
||||||
description="Generate a video presentation with slides and narration from provided content",
|
|
||||||
factory=lambda deps: create_generate_video_presentation_tool(
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
thread_id=deps["thread_id"],
|
|
||||||
),
|
|
||||||
requires=["search_space_id", "db_session", "thread_id"],
|
|
||||||
),
|
|
||||||
# Report generation tool (inline, short-lived sessions for DB ops)
|
|
||||||
# Supports internal KB search via source_strategy so the agent does not
|
|
||||||
# need a separate search step before generating.
|
|
||||||
ToolDefinition(
|
|
||||||
name="generate_report",
|
|
||||||
description="Generate a structured report from provided content and export it",
|
|
||||||
factory=lambda deps: create_generate_report_tool(
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
thread_id=deps["thread_id"],
|
|
||||||
connector_service=deps.get("connector_service"),
|
|
||||||
available_connectors=deps.get("available_connectors"),
|
|
||||||
available_document_types=deps.get("available_document_types"),
|
|
||||||
),
|
|
||||||
requires=["search_space_id", "thread_id"],
|
|
||||||
# connector_service, available_connectors, and available_document_types
|
|
||||||
# are optional — when missing, source_strategy="kb_search" degrades
|
|
||||||
# gracefully to "provided"
|
|
||||||
),
|
|
||||||
# Resume generation tool (Typst-based, uses rendercv package)
|
|
||||||
ToolDefinition(
|
|
||||||
name="generate_resume",
|
|
||||||
description="Generate a professional resume as a Typst document",
|
|
||||||
factory=lambda deps: create_generate_resume_tool(
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
thread_id=deps["thread_id"],
|
|
||||||
),
|
|
||||||
requires=["search_space_id", "thread_id"],
|
|
||||||
),
|
|
||||||
# Generate image tool - creates images using AI models (DALL-E, GPT Image, etc.)
|
|
||||||
ToolDefinition(
|
|
||||||
name="generate_image",
|
|
||||||
description="Generate images from text descriptions using AI image models",
|
|
||||||
factory=lambda deps: create_generate_image_tool(
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
),
|
|
||||||
requires=["search_space_id", "db_session"],
|
|
||||||
),
|
|
||||||
# Web scraping tool - extracts content from webpages
|
|
||||||
ToolDefinition(
|
|
||||||
name="scrape_webpage",
|
|
||||||
description="Scrape and extract the main content from a webpage",
|
|
||||||
factory=lambda deps: create_scrape_webpage_tool(
|
|
||||||
firecrawl_api_key=deps.get("firecrawl_api_key"),
|
|
||||||
),
|
|
||||||
requires=[], # firecrawl_api_key is optional
|
|
||||||
),
|
|
||||||
# Web search tool — real-time web search via SearXNG + user-configured engines
|
|
||||||
ToolDefinition(
|
|
||||||
name="web_search",
|
|
||||||
description="Search the web for real-time information using configured search engines",
|
|
||||||
factory=lambda deps: create_web_search_tool(
|
|
||||||
search_space_id=deps.get("search_space_id"),
|
|
||||||
available_connectors=deps.get("available_connectors"),
|
|
||||||
),
|
|
||||||
requires=[],
|
|
||||||
),
|
|
||||||
# =========================================================================
|
|
||||||
# SERVICE ACCOUNT DISCOVERY
|
|
||||||
# Generic tool for the LLM to discover connected accounts and resolve
|
|
||||||
# service-specific identifiers (e.g. Jira cloudId, Slack team, etc.)
|
|
||||||
# =========================================================================
|
|
||||||
ToolDefinition(
|
|
||||||
name="get_connected_accounts",
|
|
||||||
description="Discover connected accounts for a service and their metadata",
|
|
||||||
factory=lambda deps: create_get_connected_accounts_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
),
|
|
||||||
# =========================================================================
|
|
||||||
# AUTOMATION AUTHORING - single HITL tool. The tool takes an NL ``intent``
|
|
||||||
# from the main agent, drafts the full AutomationCreate JSON via a focused
|
|
||||||
# sub-LLM, surfaces it on an approval card, and persists on approval. The
|
|
||||||
# factory defers its import because the impl lives under ``multi_agent_chat``
|
|
||||||
# and that package transitively pulls this registry via middleware;
|
|
||||||
# deferring to ``build_tools`` call-time breaks the cycle without a
|
|
||||||
# parallel registry.
|
|
||||||
# =========================================================================
|
|
||||||
ToolDefinition(
|
|
||||||
name="create_automation",
|
|
||||||
description="Draft an automation from an NL intent; user approves the card; tool saves",
|
|
||||||
factory=_build_create_automation_tool,
|
|
||||||
requires=["search_space_id", "user_id", "llm"],
|
|
||||||
),
|
|
||||||
# =========================================================================
|
|
||||||
# MEMORY TOOL - single update_memory, private or team by thread_visibility
|
|
||||||
# =========================================================================
|
|
||||||
ToolDefinition(
|
|
||||||
name="update_memory",
|
|
||||||
description="Save important long-term facts, preferences, and instructions to the (personal or team) memory",
|
|
||||||
factory=lambda deps: (
|
|
||||||
create_update_team_memory_tool(
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
llm=deps.get("llm"),
|
|
||||||
)
|
|
||||||
if deps["thread_visibility"] == ChatVisibility.SEARCH_SPACE
|
|
||||||
else create_update_memory_tool(
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
llm=deps.get("llm"),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
requires=[
|
|
||||||
"user_id",
|
|
||||||
"search_space_id",
|
|
||||||
"db_session",
|
|
||||||
"thread_visibility",
|
|
||||||
"llm",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
# =========================================================================
|
|
||||||
# NOTION TOOLS - create, update, delete pages
|
|
||||||
# Auto-disabled when no Notion connector is configured (see chat_deepagent.py)
|
|
||||||
# =========================================================================
|
|
||||||
ToolDefinition(
|
|
||||||
name="create_notion_page",
|
|
||||||
description="Create a new page in the user's Notion workspace",
|
|
||||||
factory=lambda deps: create_create_notion_page_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="NOTION_CONNECTOR",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("title"),
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="update_notion_page",
|
|
||||||
description="Append new content to an existing Notion page",
|
|
||||||
factory=lambda deps: create_update_notion_page_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="NOTION_CONNECTOR",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("page_title"),
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="delete_notion_page",
|
|
||||||
description="Delete an existing Notion page",
|
|
||||||
factory=lambda deps: create_delete_notion_page_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="NOTION_CONNECTOR",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("page_title"),
|
|
||||||
),
|
|
||||||
# =========================================================================
|
|
||||||
# GOOGLE DRIVE TOOLS - create files, delete files
|
|
||||||
# Auto-disabled when no Google Drive connector is configured (see chat_deepagent.py)
|
|
||||||
# =========================================================================
|
|
||||||
ToolDefinition(
|
|
||||||
name="create_google_drive_file",
|
|
||||||
description="Create a new Google Doc or Google Sheet in Google Drive",
|
|
||||||
factory=lambda deps: create_create_google_drive_file_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="GOOGLE_DRIVE_FILE",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("file_name"),
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="delete_google_drive_file",
|
|
||||||
description="Move an indexed Google Drive file to trash",
|
|
||||||
factory=lambda deps: create_delete_google_drive_file_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="GOOGLE_DRIVE_FILE",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("file_name"),
|
|
||||||
),
|
|
||||||
# =========================================================================
|
|
||||||
# DROPBOX TOOLS - create and trash files
|
|
||||||
# Auto-disabled when no Dropbox connector is configured (see chat_deepagent.py)
|
|
||||||
# =========================================================================
|
|
||||||
ToolDefinition(
|
|
||||||
name="create_dropbox_file",
|
|
||||||
description="Create a new file in Dropbox",
|
|
||||||
factory=lambda deps: create_create_dropbox_file_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="DROPBOX_FILE",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("file_name"),
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="delete_dropbox_file",
|
|
||||||
description="Delete a file from Dropbox",
|
|
||||||
factory=lambda deps: create_delete_dropbox_file_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="DROPBOX_FILE",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("file_name"),
|
|
||||||
),
|
|
||||||
# =========================================================================
|
|
||||||
# ONEDRIVE TOOLS - create and trash files
|
|
||||||
# Auto-disabled when no OneDrive connector is configured (see chat_deepagent.py)
|
|
||||||
# =========================================================================
|
|
||||||
ToolDefinition(
|
|
||||||
name="create_onedrive_file",
|
|
||||||
description="Create a new file in Microsoft OneDrive",
|
|
||||||
factory=lambda deps: create_create_onedrive_file_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="ONEDRIVE_FILE",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("file_name"),
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="delete_onedrive_file",
|
|
||||||
description="Move a OneDrive file to the recycle bin",
|
|
||||||
factory=lambda deps: create_delete_onedrive_file_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="ONEDRIVE_FILE",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("file_name"),
|
|
||||||
),
|
|
||||||
# =========================================================================
|
|
||||||
# GOOGLE CALENDAR TOOLS - search, create, update, delete events
|
|
||||||
# Auto-disabled when no Google Calendar connector is configured
|
|
||||||
# =========================================================================
|
|
||||||
ToolDefinition(
|
|
||||||
name="search_calendar_events",
|
|
||||||
description="Search Google Calendar events within a date range",
|
|
||||||
factory=lambda deps: create_search_calendar_events_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="GOOGLE_CALENDAR_CONNECTOR",
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="create_calendar_event",
|
|
||||||
description="Create a new event on Google Calendar",
|
|
||||||
factory=lambda deps: create_create_calendar_event_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="GOOGLE_CALENDAR_CONNECTOR",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("title"),
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="update_calendar_event",
|
|
||||||
description="Update an existing indexed Google Calendar event",
|
|
||||||
factory=lambda deps: create_update_calendar_event_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="GOOGLE_CALENDAR_CONNECTOR",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("event_title_or_id"),
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="delete_calendar_event",
|
|
||||||
description="Delete an existing indexed Google Calendar event",
|
|
||||||
factory=lambda deps: create_delete_calendar_event_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="GOOGLE_CALENDAR_CONNECTOR",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("event_title_or_id"),
|
|
||||||
),
|
|
||||||
# =========================================================================
|
|
||||||
# GMAIL TOOLS - search, read, create drafts, update drafts, send, trash
|
|
||||||
# Auto-disabled when no Gmail connector is configured
|
|
||||||
# =========================================================================
|
|
||||||
ToolDefinition(
|
|
||||||
name="search_gmail",
|
|
||||||
description="Search emails in Gmail using Gmail search syntax",
|
|
||||||
factory=lambda deps: create_search_gmail_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="GOOGLE_GMAIL_CONNECTOR",
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="read_gmail_email",
|
|
||||||
description="Read the full content of a specific Gmail email",
|
|
||||||
factory=lambda deps: create_read_gmail_email_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="GOOGLE_GMAIL_CONNECTOR",
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="create_gmail_draft",
|
|
||||||
description="Create a draft email in Gmail",
|
|
||||||
factory=lambda deps: create_create_gmail_draft_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="GOOGLE_GMAIL_CONNECTOR",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("subject"),
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="send_gmail_email",
|
|
||||||
description="Send an email via Gmail",
|
|
||||||
factory=lambda deps: create_send_gmail_email_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="GOOGLE_GMAIL_CONNECTOR",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("subject"),
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="trash_gmail_email",
|
|
||||||
description="Move an indexed email to trash in Gmail",
|
|
||||||
factory=lambda deps: create_trash_gmail_email_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="GOOGLE_GMAIL_CONNECTOR",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("email_subject_or_id"),
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="update_gmail_draft",
|
|
||||||
description="Update an existing Gmail draft",
|
|
||||||
factory=lambda deps: create_update_gmail_draft_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="GOOGLE_GMAIL_CONNECTOR",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("draft_subject_or_id"),
|
|
||||||
),
|
|
||||||
# =========================================================================
|
|
||||||
# CONFLUENCE TOOLS - create, update, delete pages
|
|
||||||
# Auto-disabled when no Confluence connector is configured (see chat_deepagent.py)
|
|
||||||
# =========================================================================
|
|
||||||
ToolDefinition(
|
|
||||||
name="create_confluence_page",
|
|
||||||
description="Create a new page in the user's Confluence space",
|
|
||||||
factory=lambda deps: create_create_confluence_page_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="CONFLUENCE_CONNECTOR",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("title"),
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="update_confluence_page",
|
|
||||||
description="Update an existing indexed Confluence page",
|
|
||||||
factory=lambda deps: create_update_confluence_page_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="CONFLUENCE_CONNECTOR",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("page_title_or_id"),
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="delete_confluence_page",
|
|
||||||
description="Delete an existing indexed Confluence page",
|
|
||||||
factory=lambda deps: create_delete_confluence_page_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="CONFLUENCE_CONNECTOR",
|
|
||||||
dedup_key=wrap_dedup_key_by_arg_name("page_title_or_id"),
|
|
||||||
),
|
|
||||||
# =========================================================================
|
|
||||||
# DISCORD TOOLS - list channels, read messages, send messages
|
|
||||||
# Auto-disabled when no Discord connector is configured
|
|
||||||
# =========================================================================
|
|
||||||
ToolDefinition(
|
|
||||||
name="list_discord_channels",
|
|
||||||
description="List text channels in the connected Discord server",
|
|
||||||
factory=lambda deps: create_list_discord_channels_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="DISCORD_CONNECTOR",
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="read_discord_messages",
|
|
||||||
description="Read recent messages from a Discord text channel",
|
|
||||||
factory=lambda deps: create_read_discord_messages_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="DISCORD_CONNECTOR",
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="send_discord_message",
|
|
||||||
description="Send a message to a Discord text channel",
|
|
||||||
factory=lambda deps: create_send_discord_message_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="DISCORD_CONNECTOR",
|
|
||||||
),
|
|
||||||
# =========================================================================
|
|
||||||
# TEAMS TOOLS - list channels, read messages, send messages
|
|
||||||
# Auto-disabled when no Teams connector is configured
|
|
||||||
# =========================================================================
|
|
||||||
ToolDefinition(
|
|
||||||
name="list_teams_channels",
|
|
||||||
description="List Microsoft Teams and their channels",
|
|
||||||
factory=lambda deps: create_list_teams_channels_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="TEAMS_CONNECTOR",
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="read_teams_messages",
|
|
||||||
description="Read recent messages from a Microsoft Teams channel",
|
|
||||||
factory=lambda deps: create_read_teams_messages_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="TEAMS_CONNECTOR",
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="send_teams_message",
|
|
||||||
description="Send a message to a Microsoft Teams channel",
|
|
||||||
factory=lambda deps: create_send_teams_message_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="TEAMS_CONNECTOR",
|
|
||||||
),
|
|
||||||
# =========================================================================
|
|
||||||
# LUMA TOOLS - list events, read event details, create events
|
|
||||||
# Auto-disabled when no Luma connector is configured
|
|
||||||
# =========================================================================
|
|
||||||
ToolDefinition(
|
|
||||||
name="list_luma_events",
|
|
||||||
description="List upcoming and recent Luma events",
|
|
||||||
factory=lambda deps: create_list_luma_events_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="LUMA_CONNECTOR",
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="read_luma_event",
|
|
||||||
description="Read detailed information about a specific Luma event",
|
|
||||||
factory=lambda deps: create_read_luma_event_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="LUMA_CONNECTOR",
|
|
||||||
),
|
|
||||||
ToolDefinition(
|
|
||||||
name="create_luma_event",
|
|
||||||
description="Create a new event on Luma",
|
|
||||||
factory=lambda deps: create_create_luma_event_tool(
|
|
||||||
db_session=deps["db_session"],
|
|
||||||
search_space_id=deps["search_space_id"],
|
|
||||||
user_id=deps["user_id"],
|
|
||||||
),
|
|
||||||
requires=["db_session", "search_space_id", "user_id"],
|
|
||||||
required_connector="LUMA_CONNECTOR",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Registry Functions
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
def get_tool_by_name(name: str) -> ToolDefinition | None:
|
|
||||||
"""Get a tool definition by its name."""
|
|
||||||
for tool_def in BUILTIN_TOOLS:
|
|
||||||
if tool_def.name == name:
|
|
||||||
return tool_def
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_connector_gated_tools(
|
|
||||||
available_connectors: list[str] | None,
|
|
||||||
) -> list[str]:
|
|
||||||
"""Return tool names to disable"""
|
|
||||||
available = set() if available_connectors is None else set(available_connectors)
|
|
||||||
|
|
||||||
disabled: list[str] = []
|
|
||||||
for tool_def in BUILTIN_TOOLS:
|
|
||||||
if tool_def.required_connector and tool_def.required_connector not in available:
|
|
||||||
disabled.append(tool_def.name)
|
|
||||||
return disabled
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_tool_names() -> list[str]:
|
|
||||||
"""Get names of all registered tools."""
|
|
||||||
return [tool_def.name for tool_def in BUILTIN_TOOLS]
|
|
||||||
|
|
||||||
|
|
||||||
def get_default_enabled_tools() -> list[str]:
|
|
||||||
"""Get names of tools that are enabled by default (excludes hidden tools)."""
|
|
||||||
return [tool_def.name for tool_def in BUILTIN_TOOLS if tool_def.enabled_by_default]
|
|
||||||
|
|
||||||
|
|
||||||
def build_tools(
|
|
||||||
dependencies: dict[str, Any],
|
|
||||||
enabled_tools: list[str] | None = None,
|
|
||||||
disabled_tools: list[str] | None = None,
|
|
||||||
additional_tools: list[BaseTool] | None = None,
|
|
||||||
) -> list[BaseTool]:
|
|
||||||
"""Build the list of tools for the agent.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
dependencies: Dict containing all possible dependencies:
|
|
||||||
- search_space_id: The search space ID
|
|
||||||
- db_session: Database session
|
|
||||||
- connector_service: Connector service instance
|
|
||||||
- firecrawl_api_key: Optional Firecrawl API key
|
|
||||||
enabled_tools: Explicit list of tool names to enable. If None, uses defaults.
|
|
||||||
disabled_tools: List of tool names to disable (applied after enabled_tools).
|
|
||||||
additional_tools: Extra tools to add (e.g., custom tools not in registry).
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of configured tool instances ready for the agent.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
# Use all default tools
|
|
||||||
tools = build_tools(deps)
|
|
||||||
|
|
||||||
# Use only specific tools
|
|
||||||
tools = build_tools(deps, enabled_tools=["generate_report"])
|
|
||||||
|
|
||||||
# Use defaults but disable podcast
|
|
||||||
tools = build_tools(deps, disabled_tools=["generate_podcast"])
|
|
||||||
|
|
||||||
# Add custom tools
|
|
||||||
tools = build_tools(deps, additional_tools=[my_custom_tool])
|
|
||||||
|
|
||||||
"""
|
|
||||||
# Determine which tools to enable
|
|
||||||
if enabled_tools is not None:
|
|
||||||
tool_names_to_use = set(enabled_tools)
|
|
||||||
else:
|
|
||||||
tool_names_to_use = set(get_default_enabled_tools())
|
|
||||||
|
|
||||||
# Apply disabled list
|
|
||||||
if disabled_tools:
|
|
||||||
tool_names_to_use -= set(disabled_tools)
|
|
||||||
|
|
||||||
# Build the tools (skip hidden/WIP tools unconditionally)
|
|
||||||
tools: list[BaseTool] = []
|
|
||||||
for tool_def in BUILTIN_TOOLS:
|
|
||||||
if tool_def.hidden or tool_def.name not in tool_names_to_use:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Check that all required dependencies are provided
|
|
||||||
missing_deps = [dep for dep in tool_def.requires if dep not in dependencies]
|
|
||||||
if missing_deps:
|
|
||||||
msg = f"Tool '{tool_def.name}' requires dependencies: {missing_deps}"
|
|
||||||
raise ValueError(
|
|
||||||
msg,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create the tool
|
|
||||||
tool = tool_def.factory(dependencies)
|
|
||||||
# Propagate the registry-level metadata so middleware (e.g.
|
|
||||||
# ``DedupHITLToolCallsMiddleware``) and the action-log/revert
|
|
||||||
# pipeline can pick the resolvers up via ``tool.metadata`` without
|
|
||||||
# re-importing :data:`BUILTIN_TOOLS`.
|
|
||||||
if tool_def.dedup_key is not None or tool_def.reverse is not None:
|
|
||||||
existing_meta = getattr(tool, "metadata", None) or {}
|
|
||||||
merged_meta = dict(existing_meta)
|
|
||||||
if tool_def.dedup_key is not None:
|
|
||||||
merged_meta.setdefault("dedup_key", tool_def.dedup_key)
|
|
||||||
if tool_def.reverse is not None:
|
|
||||||
merged_meta.setdefault("reverse", tool_def.reverse)
|
|
||||||
try:
|
|
||||||
tool.metadata = merged_meta
|
|
||||||
except Exception:
|
|
||||||
logger.debug(
|
|
||||||
"Tool %s rejected metadata mutation; relying on registry lookup",
|
|
||||||
tool_def.name,
|
|
||||||
)
|
|
||||||
tools.append(tool)
|
|
||||||
|
|
||||||
# Add any additional custom tools
|
|
||||||
if additional_tools:
|
|
||||||
tools.extend(additional_tools)
|
|
||||||
|
|
||||||
return tools
|
|
||||||
|
|
||||||
|
|
||||||
async def build_tools_async(
|
|
||||||
dependencies: dict[str, Any],
|
|
||||||
enabled_tools: list[str] | None = None,
|
|
||||||
disabled_tools: list[str] | None = None,
|
|
||||||
additional_tools: list[BaseTool] | None = None,
|
|
||||||
include_mcp_tools: bool = True,
|
|
||||||
) -> list[BaseTool]:
|
|
||||||
"""Async version of build_tools that also loads MCP tools from database.
|
|
||||||
|
|
||||||
Design Note:
|
|
||||||
This function exists because MCP tools require database queries to load
|
|
||||||
user configs, while built-in tools are created synchronously from static
|
|
||||||
code.
|
|
||||||
|
|
||||||
Alternative: We could make build_tools() itself async and always query
|
|
||||||
the database, but that would force async everywhere even when only using
|
|
||||||
built-in tools. The current design keeps the simple case (static tools
|
|
||||||
only) synchronous while supporting dynamic database-loaded tools through
|
|
||||||
this async wrapper.
|
|
||||||
|
|
||||||
Phase 1.3: built-in tool construction (CPU; runs in a thread pool to
|
|
||||||
avoid event-loop stalls) and MCP tool loading (HTTP/DB I/O; runs on
|
|
||||||
the event loop) are kicked off concurrently. Cold-path savings are
|
|
||||||
bounded by the slower of the two — typically MCP at ~200ms-1.7s —
|
|
||||||
so the parallelization recovers the ~50-200ms previously spent
|
|
||||||
serially on built-in construction.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
dependencies: Dict containing all possible dependencies
|
|
||||||
enabled_tools: Explicit list of tool names to enable. If None, uses defaults.
|
|
||||||
disabled_tools: List of tool names to disable (applied after enabled_tools).
|
|
||||||
additional_tools: Extra tools to add (e.g., custom tools not in registry).
|
|
||||||
include_mcp_tools: Whether to load user's MCP tools from database.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of configured tool instances ready for the agent, including MCP tools.
|
|
||||||
|
|
||||||
"""
|
|
||||||
import asyncio
|
|
||||||
import time
|
|
||||||
|
|
||||||
_perf_log = logging.getLogger("surfsense.perf")
|
|
||||||
_perf_log.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
can_load_mcp = (
|
|
||||||
include_mcp_tools
|
|
||||||
and "db_session" in dependencies
|
|
||||||
and "search_space_id" in dependencies
|
|
||||||
)
|
|
||||||
|
|
||||||
# Built-in tool construction is synchronous + CPU-only. Off-loop it so
|
|
||||||
# MCP's HTTP/DB I/O can fire concurrently. ``build_tools`` is pure
|
|
||||||
# function over its inputs — safe to thread-shift.
|
|
||||||
_t0 = time.perf_counter()
|
|
||||||
builtin_task = asyncio.create_task(
|
|
||||||
asyncio.to_thread(
|
|
||||||
build_tools, dependencies, enabled_tools, disabled_tools, additional_tools
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
mcp_task: asyncio.Task | None = None
|
|
||||||
if can_load_mcp:
|
|
||||||
mcp_task = asyncio.create_task(
|
|
||||||
load_mcp_tools(
|
|
||||||
dependencies["db_session"],
|
|
||||||
dependencies["search_space_id"],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Surface failures from each task independently so a flaky MCP
|
|
||||||
# endpoint never poisons built-in tool registration. ``return_exceptions``
|
|
||||||
# gives us per-task exceptions instead of dropping the second result
|
|
||||||
# when the first raises.
|
|
||||||
if mcp_task is not None:
|
|
||||||
builtin_result, mcp_result = await asyncio.gather(
|
|
||||||
builtin_task, mcp_task, return_exceptions=True
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
builtin_result = await builtin_task
|
|
||||||
mcp_result = None
|
|
||||||
|
|
||||||
if isinstance(builtin_result, BaseException):
|
|
||||||
raise builtin_result # built-in registration failure is non-recoverable
|
|
||||||
tools: list[BaseTool] = builtin_result
|
|
||||||
_perf_log.info(
|
|
||||||
"[build_tools_async] Built-in tools in %.3fs (%d tools, parallel)",
|
|
||||||
time.perf_counter() - _t0,
|
|
||||||
len(tools),
|
|
||||||
)
|
|
||||||
|
|
||||||
if mcp_task is not None:
|
|
||||||
if isinstance(mcp_result, BaseException):
|
|
||||||
# ``return_exceptions=True`` captures the exception out-of-band,
|
|
||||||
# so ``sys.exc_info()`` is empty here. Pass the captured
|
|
||||||
# exception via ``exc_info=`` to get a real traceback.
|
|
||||||
logging.error(
|
|
||||||
"Failed to load MCP tools: %s", mcp_result, exc_info=mcp_result
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
mcp_tools = mcp_result or []
|
|
||||||
_perf_log.info(
|
|
||||||
"[build_tools_async] MCP tools loaded in %.3fs (%d tools, parallel)",
|
|
||||||
time.perf_counter() - _t0,
|
|
||||||
len(mcp_tools),
|
|
||||||
)
|
|
||||||
tools.extend(mcp_tools)
|
|
||||||
logging.info(
|
|
||||||
"Registered %d MCP tools: %s",
|
|
||||||
len(mcp_tools),
|
|
||||||
[t.name for t in mcp_tools],
|
|
||||||
)
|
|
||||||
|
|
||||||
logging.info(
|
|
||||||
"Total tools for agent: %d — %s",
|
|
||||||
len(tools),
|
|
||||||
[t.name for t in tools],
|
|
||||||
)
|
|
||||||
|
|
||||||
return tools
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Wraps every tool call via :meth:`AgentMiddleware.awrap_tool_call` and writes
|
Wraps every tool call via :meth:`AgentMiddleware.awrap_tool_call` and writes
|
||||||
a row to :class:`~app.db.AgentActionLog` after the tool returns. Tools opt
|
a row to :class:`~app.db.AgentActionLog` after the tool returns. Tools opt
|
||||||
into reversibility by declaring a ``reverse`` callable on their
|
into reversibility by declaring a ``reverse`` callable on their
|
||||||
:class:`~app.agents.new_chat.tools.registry.ToolDefinition`; the rendered
|
:class:`~app.agents.shared.tools.registry.ToolDefinition`; the rendered
|
||||||
descriptor is persisted in ``reverse_descriptor`` for use by
|
descriptor is persisted in ``reverse_descriptor`` for use by
|
||||||
``/api/threads/{thread_id}/revert/{action_id}``.
|
``/api/threads/{thread_id}/revert/{action_id}``.
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ if TYPE_CHECKING: # pragma: no cover - type-only
|
||||||
# Type-only import: keeping it lazy avoids a module-load cycle through the
|
# Type-only import: keeping it lazy avoids a module-load cycle through the
|
||||||
# frozen single-agent package (new_chat.__init__ -> chat_deepagent ->
|
# frozen single-agent package (new_chat.__init__ -> chat_deepagent ->
|
||||||
# middleware shim). Resolves to app.agents.shared.tools once tools migrate.
|
# middleware shim). Resolves to app.agents.shared.tools once tools migrate.
|
||||||
from app.agents.new_chat.tools.registry import ToolDefinition
|
from app.agents.shared.tools.registry import ToolDefinition
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ class ToolCallNameRepairMiddleware(
|
||||||
# Local import avoids a module-load cycle through the frozen single-agent
|
# Local import avoids a module-load cycle through the frozen single-agent
|
||||||
# package (new_chat.__init__ -> chat_deepagent -> middleware shim).
|
# package (new_chat.__init__ -> chat_deepagent -> middleware shim).
|
||||||
# Resolves to app.agents.shared.tools once tools migrate.
|
# Resolves to app.agents.shared.tools once tools migrate.
|
||||||
from app.agents.new_chat.tools.invalid_tool import INVALID_TOOL_NAME
|
from app.agents.shared.tools.invalid_tool import INVALID_TOOL_NAME
|
||||||
|
|
||||||
if INVALID_TOOL_NAME in registered:
|
if INVALID_TOOL_NAME in registered:
|
||||||
original_args = call.get("args") or {}
|
original_args = call.get("args") or {}
|
||||||
|
|
|
||||||
55
surfsense_backend/app/agents/shared/tools/__init__.py
Normal file
55
surfsense_backend/app/agents/shared/tools/__init__.py
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
"""
|
||||||
|
Tools module for SurfSense deep agent.
|
||||||
|
|
||||||
|
This module contains all the tools available to the SurfSense agent.
|
||||||
|
To add a new tool, see the documentation in registry.py.
|
||||||
|
|
||||||
|
Available tools:
|
||||||
|
- generate_podcast: Generate audio podcasts from content
|
||||||
|
- generate_video_presentation: Generate video presentations with slides and narration
|
||||||
|
- generate_image: Generate images from text descriptions using AI models
|
||||||
|
- scrape_webpage: Extract content from webpages
|
||||||
|
- update_memory: Update the user's / team's memory document
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Registry exports
|
||||||
|
# Tool factory exports (for direct use)
|
||||||
|
from .generate_image import create_generate_image_tool
|
||||||
|
from .knowledge_base import (
|
||||||
|
CONNECTOR_DESCRIPTIONS,
|
||||||
|
format_documents_for_context,
|
||||||
|
search_knowledge_base_async,
|
||||||
|
)
|
||||||
|
from .podcast import create_generate_podcast_tool
|
||||||
|
from .registry import (
|
||||||
|
BUILTIN_TOOLS,
|
||||||
|
ToolDefinition,
|
||||||
|
build_tools,
|
||||||
|
get_all_tool_names,
|
||||||
|
get_default_enabled_tools,
|
||||||
|
get_tool_by_name,
|
||||||
|
)
|
||||||
|
from .scrape_webpage import create_scrape_webpage_tool
|
||||||
|
from .update_memory import create_update_memory_tool, create_update_team_memory_tool
|
||||||
|
from .video_presentation import create_generate_video_presentation_tool
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
# Registry
|
||||||
|
"BUILTIN_TOOLS",
|
||||||
|
# Knowledge base utilities
|
||||||
|
"CONNECTOR_DESCRIPTIONS",
|
||||||
|
"ToolDefinition",
|
||||||
|
"build_tools",
|
||||||
|
# Tool factories
|
||||||
|
"create_generate_image_tool",
|
||||||
|
"create_generate_podcast_tool",
|
||||||
|
"create_generate_video_presentation_tool",
|
||||||
|
"create_scrape_webpage_tool",
|
||||||
|
"create_update_memory_tool",
|
||||||
|
"create_update_team_memory_tool",
|
||||||
|
"format_documents_for_context",
|
||||||
|
"get_all_tool_names",
|
||||||
|
"get_default_enabled_tools",
|
||||||
|
"get_tool_by_name",
|
||||||
|
"search_knowledge_base_async",
|
||||||
|
]
|
||||||
|
|
@ -5,7 +5,7 @@ from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm.attributes import flag_modified
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.connectors.confluence_history import ConfluenceHistoryConnector
|
from app.connectors.confluence_history import ConfluenceHistoryConnector
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
from app.services.confluence import ConfluenceToolMetadataService
|
from app.services.confluence import ConfluenceToolMetadataService
|
||||||
|
|
@ -5,7 +5,7 @@ from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm.attributes import flag_modified
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.connectors.confluence_history import ConfluenceHistoryConnector
|
from app.connectors.confluence_history import ConfluenceHistoryConnector
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
from app.services.confluence import ConfluenceToolMetadataService
|
from app.services.confluence import ConfluenceToolMetadataService
|
||||||
|
|
@ -5,7 +5,7 @@ from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm.attributes import flag_modified
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.connectors.confluence_history import ConfluenceHistoryConnector
|
from app.connectors.confluence_history import ConfluenceHistoryConnector
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
from app.services.confluence import ConfluenceToolMetadataService
|
from app.services.confluence import ConfluenceToolMetadataService
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
from app.agents.new_chat.tools.discord.list_channels import (
|
from app.agents.shared.tools.discord.list_channels import (
|
||||||
create_list_discord_channels_tool,
|
create_list_discord_channels_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.discord.read_messages import (
|
from app.agents.shared.tools.discord.read_messages import (
|
||||||
create_read_discord_messages_tool,
|
create_read_discord_messages_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.discord.send_message import (
|
from app.agents.shared.tools.discord.send_message import (
|
||||||
create_send_discord_message_tool,
|
create_send_discord_message_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -5,7 +5,7 @@ import httpx
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
|
|
||||||
from ._auth import DISCORD_API, get_bot_token, get_discord_connector
|
from ._auth import DISCORD_API, get_bot_token, get_discord_connector
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from app.agents.new_chat.tools.dropbox.create_file import (
|
from app.agents.shared.tools.dropbox.create_file import (
|
||||||
create_create_dropbox_file_tool,
|
create_create_dropbox_file_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.dropbox.trash_file import (
|
from app.agents.shared.tools.dropbox.trash_file import (
|
||||||
create_delete_dropbox_file_tool,
|
create_delete_dropbox_file_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.future import select
|
from sqlalchemy.future import select
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.connectors.dropbox.client import DropboxClient
|
from app.connectors.dropbox.client import DropboxClient
|
||||||
from app.db import SearchSourceConnector, SearchSourceConnectorType, async_session_maker
|
from app.db import SearchSourceConnector, SearchSourceConnectorType, async_session_maker
|
||||||
|
|
||||||
|
|
@ -6,7 +6,7 @@ from sqlalchemy import String, and_, cast, func
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.future import select
|
from sqlalchemy.future import select
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.connectors.dropbox.client import DropboxClient
|
from app.connectors.dropbox.client import DropboxClient
|
||||||
from app.db import (
|
from app.db import (
|
||||||
Document,
|
Document,
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
from app.agents.new_chat.tools.gmail.create_draft import (
|
from app.agents.shared.tools.gmail.create_draft import (
|
||||||
create_create_gmail_draft_tool,
|
create_create_gmail_draft_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.gmail.read_email import (
|
from app.agents.shared.tools.gmail.read_email import (
|
||||||
create_read_gmail_email_tool,
|
create_read_gmail_email_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.gmail.search_emails import (
|
from app.agents.shared.tools.gmail.search_emails import (
|
||||||
create_search_gmail_tool,
|
create_search_gmail_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.gmail.send_email import (
|
from app.agents.shared.tools.gmail.send_email import (
|
||||||
create_send_gmail_email_tool,
|
create_send_gmail_email_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.gmail.trash_email import (
|
from app.agents.shared.tools.gmail.trash_email import (
|
||||||
create_trash_gmail_email_tool,
|
create_trash_gmail_email_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.gmail.update_draft import (
|
from app.agents.shared.tools.gmail.update_draft import (
|
||||||
create_update_gmail_draft_tool,
|
create_update_gmail_draft_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ from typing import Any
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
from app.services.gmail import GmailToolMetadataService
|
from app.services.gmail import GmailToolMetadataService
|
||||||
|
|
||||||
|
|
@ -241,7 +241,7 @@ def create_create_gmail_draft_tool(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if is_composio_gmail:
|
if is_composio_gmail:
|
||||||
from app.agents.new_chat.tools.gmail.composio_helpers import (
|
from app.agents.shared.tools.gmail.composio_helpers import (
|
||||||
execute_composio_gmail_tool,
|
execute_composio_gmail_tool,
|
||||||
split_recipients,
|
split_recipients,
|
||||||
)
|
)
|
||||||
|
|
@ -79,7 +79,7 @@ def create_read_gmail_email_tool(
|
||||||
"message": "Composio connected account ID not found.",
|
"message": "Composio connected account ID not found.",
|
||||||
}
|
}
|
||||||
|
|
||||||
from app.agents.new_chat.tools.gmail.search_emails import (
|
from app.agents.shared.tools.gmail.search_emails import (
|
||||||
_format_gmail_summary,
|
_format_gmail_summary,
|
||||||
)
|
)
|
||||||
from app.services.composio_service import ComposioService
|
from app.services.composio_service import ComposioService
|
||||||
|
|
@ -116,7 +116,7 @@ def create_read_gmail_email_tool(
|
||||||
"content": content,
|
"content": content,
|
||||||
}
|
}
|
||||||
|
|
||||||
from app.agents.new_chat.tools.gmail.search_emails import (
|
from app.agents.shared.tools.gmail.search_emails import (
|
||||||
_build_credentials,
|
_build_credentials,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ from typing import Any
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
from app.services.gmail import GmailToolMetadataService
|
from app.services.gmail import GmailToolMetadataService
|
||||||
|
|
||||||
|
|
@ -242,7 +242,7 @@ def create_send_gmail_email_tool(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if is_composio_gmail:
|
if is_composio_gmail:
|
||||||
from app.agents.new_chat.tools.gmail.composio_helpers import (
|
from app.agents.shared.tools.gmail.composio_helpers import (
|
||||||
execute_composio_gmail_tool,
|
execute_composio_gmail_tool,
|
||||||
split_recipients,
|
split_recipients,
|
||||||
)
|
)
|
||||||
|
|
@ -6,7 +6,7 @@ from typing import Any
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
from app.services.gmail import GmailToolMetadataService
|
from app.services.gmail import GmailToolMetadataService
|
||||||
|
|
||||||
|
|
@ -233,7 +233,7 @@ def create_trash_gmail_email_tool(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if is_composio_gmail:
|
if is_composio_gmail:
|
||||||
from app.agents.new_chat.tools.gmail.composio_helpers import (
|
from app.agents.shared.tools.gmail.composio_helpers import (
|
||||||
execute_composio_gmail_tool,
|
execute_composio_gmail_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ from typing import Any
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
from app.services.gmail import GmailToolMetadataService
|
from app.services.gmail import GmailToolMetadataService
|
||||||
|
|
||||||
|
|
@ -297,7 +297,7 @@ def create_update_gmail_draft_tool(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if is_composio_gmail:
|
if is_composio_gmail:
|
||||||
from app.agents.new_chat.tools.gmail.composio_helpers import (
|
from app.agents.shared.tools.gmail.composio_helpers import (
|
||||||
execute_composio_gmail_tool,
|
execute_composio_gmail_tool,
|
||||||
split_recipients,
|
split_recipients,
|
||||||
)
|
)
|
||||||
|
|
@ -466,7 +466,7 @@ async def _find_draft_id_by_message(gmail_service: Any, message_id: str) -> str
|
||||||
async def _find_composio_draft_id_by_message(
|
async def _find_composio_draft_id_by_message(
|
||||||
connector: Any, user_id: str, message_id: str
|
connector: Any, user_id: str, message_id: str
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
from app.agents.new_chat.tools.gmail.composio_helpers import (
|
from app.agents.shared.tools.gmail.composio_helpers import (
|
||||||
execute_composio_gmail_tool,
|
execute_composio_gmail_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
from app.agents.new_chat.tools.google_calendar.create_event import (
|
from app.agents.shared.tools.google_calendar.create_event import (
|
||||||
create_create_calendar_event_tool,
|
create_create_calendar_event_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.google_calendar.delete_event import (
|
from app.agents.shared.tools.google_calendar.delete_event import (
|
||||||
create_delete_calendar_event_tool,
|
create_delete_calendar_event_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.google_calendar.search_events import (
|
from app.agents.shared.tools.google_calendar.search_events import (
|
||||||
create_search_calendar_events_tool,
|
create_search_calendar_events_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.google_calendar.update_event import (
|
from app.agents.shared.tools.google_calendar.update_event import (
|
||||||
create_update_calendar_event_tool,
|
create_update_calendar_event_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ from googleapiclient.discovery import build
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
from app.services.google_calendar import GoogleCalendarToolMetadataService
|
from app.services.google_calendar import GoogleCalendarToolMetadataService
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ from googleapiclient.discovery import build
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
from app.services.google_calendar import GoogleCalendarToolMetadataService
|
from app.services.google_calendar import GoogleCalendarToolMetadataService
|
||||||
|
|
||||||
|
|
@ -5,7 +5,7 @@ from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.future import select
|
from sqlalchemy.future import select
|
||||||
|
|
||||||
from app.agents.new_chat.tools.gmail.search_emails import _build_credentials
|
from app.agents.shared.tools.gmail.search_emails import _build_credentials
|
||||||
from app.db import SearchSourceConnector, SearchSourceConnectorType, async_session_maker
|
from app.db import SearchSourceConnector, SearchSourceConnectorType, async_session_maker
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -8,7 +8,7 @@ from googleapiclient.discovery import build
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
from app.services.google_calendar import GoogleCalendarToolMetadataService
|
from app.services.google_calendar import GoogleCalendarToolMetadataService
|
||||||
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from app.agents.new_chat.tools.google_drive.create_file import (
|
from app.agents.shared.tools.google_drive.create_file import (
|
||||||
create_create_google_drive_file_tool,
|
create_create_google_drive_file_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.google_drive.trash_file import (
|
from app.agents.shared.tools.google_drive.trash_file import (
|
||||||
create_delete_google_drive_file_tool,
|
create_delete_google_drive_file_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -5,7 +5,7 @@ from googleapiclient.errors import HttpError
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.connectors.google_drive.client import GoogleDriveClient
|
from app.connectors.google_drive.client import GoogleDriveClient
|
||||||
from app.connectors.google_drive.file_types import GOOGLE_DOC, GOOGLE_SHEET
|
from app.connectors.google_drive.file_types import GOOGLE_DOC, GOOGLE_SHEET
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
|
|
@ -5,7 +5,7 @@ from googleapiclient.errors import HttpError
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.connectors.google_drive.client import GoogleDriveClient
|
from app.connectors.google_drive.client import GoogleDriveClient
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
from app.services.google_drive import GoogleDriveToolMetadataService
|
from app.services.google_drive import GoogleDriveToolMetadataService
|
||||||
|
|
@ -6,7 +6,7 @@ shared by every sensitive tool (native connectors and MCP tools alike).
|
||||||
|
|
||||||
Usage inside a tool::
|
Usage inside a tool::
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
|
|
||||||
result = request_approval(
|
result = request_approval(
|
||||||
action_type="gmail_email_send",
|
action_type="gmail_email_send",
|
||||||
53
surfsense_backend/app/agents/shared/tools/invalid_tool.py
Normal file
53
surfsense_backend/app/agents/shared/tools/invalid_tool.py
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
"""
|
||||||
|
The ``invalid`` fallback tool.
|
||||||
|
|
||||||
|
When the model emits a tool call whose name doesn't match any registered
|
||||||
|
tool, :class:`ToolCallNameRepairMiddleware` rewrites the call to ``invalid``
|
||||||
|
with the original name and a parser/validation error string. This tool's
|
||||||
|
execution then returns that error to the model so it can self-correct.
|
||||||
|
|
||||||
|
Ported from OpenCode's ``packages/opencode/src/tool/invalid.ts`` —
|
||||||
|
LangChain has no equivalent fallback path; the default behavior on an
|
||||||
|
unknown tool name is a hard ``ToolNotFoundError`` which kills the turn.
|
||||||
|
|
||||||
|
Critically, the :class:`ToolDefinition` for this tool is **excluded** from
|
||||||
|
the system-prompt tool list and from ``LLMToolSelectorMiddleware`` selection
|
||||||
|
(see ``ToolDefinition.always_include`` filtering in the registry) — the
|
||||||
|
model never advertises ``invalid`` as a callable. It only ever shows up
|
||||||
|
in the tool registry so LangGraph can dispatch the rewritten call.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from langchain_core.tools import tool
|
||||||
|
|
||||||
|
INVALID_TOOL_NAME = "invalid"
|
||||||
|
INVALID_TOOL_DESCRIPTION = "Do not use"
|
||||||
|
|
||||||
|
|
||||||
|
def _format_invalid_message(tool: str | None, error: str | None) -> str:
|
||||||
|
"""Return the user-visible error string. Mirrors ``invalid.ts``."""
|
||||||
|
name = tool or "<unknown>"
|
||||||
|
detail = error or "(no error message provided)"
|
||||||
|
return (
|
||||||
|
f"The arguments provided to the tool `{name}` are invalid: {detail}\n"
|
||||||
|
f"Read the tool's docstring carefully and try again with valid arguments."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tool(name_or_callable=INVALID_TOOL_NAME, description=INVALID_TOOL_DESCRIPTION)
|
||||||
|
def invalid_tool(tool: str | None = None, error: str | None = None) -> str:
|
||||||
|
"""Return a human-readable explanation of a tool-call validation failure.
|
||||||
|
|
||||||
|
Activated only when :class:`ToolCallNameRepairMiddleware` rewrites a
|
||||||
|
failed tool call to ``invalid`` with the original tool name and the
|
||||||
|
error message produced during validation.
|
||||||
|
"""
|
||||||
|
return _format_invalid_message(tool, error)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"INVALID_TOOL_DESCRIPTION",
|
||||||
|
"INVALID_TOOL_NAME",
|
||||||
|
"invalid_tool",
|
||||||
|
]
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
from app.agents.new_chat.tools.luma.create_event import (
|
from app.agents.shared.tools.luma.create_event import (
|
||||||
create_create_luma_event_tool,
|
create_create_luma_event_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.luma.list_events import (
|
from app.agents.shared.tools.luma.list_events import (
|
||||||
create_list_luma_events_tool,
|
create_list_luma_events_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.luma.read_event import (
|
from app.agents.shared.tools.luma.read_event import (
|
||||||
create_read_luma_event_tool,
|
create_read_luma_event_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -5,7 +5,7 @@ import httpx
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
|
|
||||||
from ._auth import LUMA_API, get_api_key, get_luma_connector, luma_headers
|
from ._auth import LUMA_API, get_api_key, get_luma_connector, luma_headers
|
||||||
|
|
@ -34,9 +34,9 @@ from sqlalchemy.dialects.postgresql import JSONB
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.shared.middleware.dedup_tool_calls import dedup_key_full_args
|
from app.agents.shared.middleware.dedup_tool_calls import dedup_key_full_args
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.agents.new_chat.tools.mcp_client import MCPClient
|
from app.agents.shared.tools.mcp_client import MCPClient
|
||||||
from app.agents.new_chat.tools.mcp_tools_cache import (
|
from app.agents.shared.tools.mcp_tools_cache import (
|
||||||
CachedMCPTools,
|
CachedMCPTools,
|
||||||
read_cached_tools,
|
read_cached_tools,
|
||||||
write_cached_tools,
|
write_cached_tools,
|
||||||
|
|
@ -112,7 +112,7 @@ def refresh_mcp_tools_cache_for_connector(
|
||||||
when an event loop is available. Neither path raises.
|
when an event loop is available. Neither path raises.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from app.agents.new_chat.tools.mcp_tool import invalidate_mcp_tools_cache
|
from app.agents.shared.tools.mcp_tool import invalidate_mcp_tools_cache
|
||||||
|
|
||||||
invalidate_mcp_tools_cache(search_space_id)
|
invalidate_mcp_tools_cache(search_space_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -133,7 +133,7 @@ def refresh_mcp_tools_cache_for_connector(
|
||||||
|
|
||||||
|
|
||||||
async def _run_connector_prefetch(connector_id: int) -> None:
|
async def _run_connector_prefetch(connector_id: int) -> None:
|
||||||
from app.agents.new_chat.tools.mcp_tool import discover_single_mcp_connector
|
from app.agents.shared.tools.mcp_tool import discover_single_mcp_connector
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await discover_single_mcp_connector(connector_id)
|
await discover_single_mcp_connector(connector_id)
|
||||||
|
|
@ -4,7 +4,7 @@ from typing import Any
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.connectors.notion_history import NotionAPIError, NotionHistoryConnector
|
from app.connectors.notion_history import NotionAPIError, NotionHistoryConnector
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
from app.services.notion import NotionToolMetadataService
|
from app.services.notion import NotionToolMetadataService
|
||||||
|
|
@ -4,7 +4,7 @@ from typing import Any
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.connectors.notion_history import NotionAPIError, NotionHistoryConnector
|
from app.connectors.notion_history import NotionAPIError, NotionHistoryConnector
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
from app.services.notion.tool_metadata_service import NotionToolMetadataService
|
from app.services.notion.tool_metadata_service import NotionToolMetadataService
|
||||||
|
|
@ -4,7 +4,7 @@ from typing import Any
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.connectors.notion_history import NotionAPIError, NotionHistoryConnector
|
from app.connectors.notion_history import NotionAPIError, NotionHistoryConnector
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
from app.services.notion import NotionToolMetadataService
|
from app.services.notion import NotionToolMetadataService
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from app.agents.new_chat.tools.onedrive.create_file import (
|
from app.agents.shared.tools.onedrive.create_file import (
|
||||||
create_create_onedrive_file_tool,
|
create_create_onedrive_file_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.onedrive.trash_file import (
|
from app.agents.shared.tools.onedrive.trash_file import (
|
||||||
create_delete_onedrive_file_tool,
|
create_delete_onedrive_file_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.future import select
|
from sqlalchemy.future import select
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.connectors.onedrive.client import OneDriveClient
|
from app.connectors.onedrive.client import OneDriveClient
|
||||||
from app.db import SearchSourceConnector, SearchSourceConnectorType, async_session_maker
|
from app.db import SearchSourceConnector, SearchSourceConnectorType, async_session_maker
|
||||||
|
|
||||||
|
|
@ -6,7 +6,7 @@ from sqlalchemy import String, and_, cast, func
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.future import select
|
from sqlalchemy.future import select
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.connectors.onedrive.client import OneDriveClient
|
from app.connectors.onedrive.client import OneDriveClient
|
||||||
from app.db import (
|
from app.db import (
|
||||||
Document,
|
Document,
|
||||||
962
surfsense_backend/app/agents/shared/tools/registry.py
Normal file
962
surfsense_backend/app/agents/shared/tools/registry.py
Normal file
|
|
@ -0,0 +1,962 @@
|
||||||
|
"""Tools registry for SurfSense deep agent.
|
||||||
|
|
||||||
|
This module provides a registry pattern for managing tools in the SurfSense agent.
|
||||||
|
It makes it easy for OSS contributors to add new tools by:
|
||||||
|
1. Creating a tool factory function in a new file in this directory
|
||||||
|
2. Registering the tool in the BUILTIN_TOOLS list below
|
||||||
|
|
||||||
|
Example of adding a new tool:
|
||||||
|
------------------------------
|
||||||
|
1. Create your tool file (e.g., `tools/my_tool.py`):
|
||||||
|
|
||||||
|
from langchain_core.tools import tool
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
def create_my_tool(search_space_id: int, db_session: AsyncSession):
|
||||||
|
@tool
|
||||||
|
async def my_tool(param: str) -> dict:
|
||||||
|
'''My tool description.'''
|
||||||
|
# Your implementation
|
||||||
|
return {"result": "success"}
|
||||||
|
return my_tool
|
||||||
|
|
||||||
|
2. Import and register in this file:
|
||||||
|
|
||||||
|
from .my_tool import create_my_tool
|
||||||
|
|
||||||
|
# Add to BUILTIN_TOOLS list:
|
||||||
|
ToolDefinition(
|
||||||
|
name="my_tool",
|
||||||
|
description="Description of what your tool does",
|
||||||
|
factory=lambda deps: create_my_tool(
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
),
|
||||||
|
requires=["search_space_id", "db_session"],
|
||||||
|
),
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from langchain_core.tools import BaseTool
|
||||||
|
|
||||||
|
from app.agents.shared.middleware.dedup_tool_calls import (
|
||||||
|
wrap_dedup_key_by_arg_name,
|
||||||
|
)
|
||||||
|
from app.db import ChatVisibility
|
||||||
|
|
||||||
|
from .confluence import (
|
||||||
|
create_create_confluence_page_tool,
|
||||||
|
create_delete_confluence_page_tool,
|
||||||
|
create_update_confluence_page_tool,
|
||||||
|
)
|
||||||
|
from .connected_accounts import create_get_connected_accounts_tool
|
||||||
|
from .discord import (
|
||||||
|
create_list_discord_channels_tool,
|
||||||
|
create_read_discord_messages_tool,
|
||||||
|
create_send_discord_message_tool,
|
||||||
|
)
|
||||||
|
from .dropbox import (
|
||||||
|
create_create_dropbox_file_tool,
|
||||||
|
create_delete_dropbox_file_tool,
|
||||||
|
)
|
||||||
|
from .generate_image import create_generate_image_tool
|
||||||
|
from .gmail import (
|
||||||
|
create_create_gmail_draft_tool,
|
||||||
|
create_read_gmail_email_tool,
|
||||||
|
create_search_gmail_tool,
|
||||||
|
create_send_gmail_email_tool,
|
||||||
|
create_trash_gmail_email_tool,
|
||||||
|
create_update_gmail_draft_tool,
|
||||||
|
)
|
||||||
|
from .google_calendar import (
|
||||||
|
create_create_calendar_event_tool,
|
||||||
|
create_delete_calendar_event_tool,
|
||||||
|
create_search_calendar_events_tool,
|
||||||
|
create_update_calendar_event_tool,
|
||||||
|
)
|
||||||
|
from .google_drive import (
|
||||||
|
create_create_google_drive_file_tool,
|
||||||
|
create_delete_google_drive_file_tool,
|
||||||
|
)
|
||||||
|
from .luma import (
|
||||||
|
create_create_luma_event_tool,
|
||||||
|
create_list_luma_events_tool,
|
||||||
|
create_read_luma_event_tool,
|
||||||
|
)
|
||||||
|
from .mcp_tool import load_mcp_tools
|
||||||
|
from .notion import (
|
||||||
|
create_create_notion_page_tool,
|
||||||
|
create_delete_notion_page_tool,
|
||||||
|
create_update_notion_page_tool,
|
||||||
|
)
|
||||||
|
from .onedrive import (
|
||||||
|
create_create_onedrive_file_tool,
|
||||||
|
create_delete_onedrive_file_tool,
|
||||||
|
)
|
||||||
|
from .podcast import create_generate_podcast_tool
|
||||||
|
from .report import create_generate_report_tool
|
||||||
|
from .resume import create_generate_resume_tool
|
||||||
|
from .scrape_webpage import create_scrape_webpage_tool
|
||||||
|
from .teams import (
|
||||||
|
create_list_teams_channels_tool,
|
||||||
|
create_read_teams_messages_tool,
|
||||||
|
create_send_teams_message_tool,
|
||||||
|
)
|
||||||
|
from .update_memory import create_update_memory_tool, create_update_team_memory_tool
|
||||||
|
from .video_presentation import create_generate_video_presentation_tool
|
||||||
|
from .web_search import create_web_search_tool
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Tool Definition
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ToolDefinition:
|
||||||
|
"""Definition of a tool that can be added to the agent.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
name: Unique identifier for the tool
|
||||||
|
description: Human-readable description of what the tool does
|
||||||
|
factory: Callable that creates the tool. Receives a dict of dependencies.
|
||||||
|
requires: List of dependency names this tool needs (e.g., "search_space_id", "db_session")
|
||||||
|
enabled_by_default: Whether the tool is enabled when no explicit config is provided
|
||||||
|
required_connector: Searchable type string (e.g. ``"LINEAR_CONNECTOR"``)
|
||||||
|
that must be in ``available_connectors`` for the tool to be enabled.
|
||||||
|
dedup_key: Optional callable that maps a tool's ``args`` dict to a
|
||||||
|
string signature used by :class:`DedupHITLToolCallsMiddleware`
|
||||||
|
to drop duplicate calls within a single LLM response.
|
||||||
|
reverse: Optional callable that, given the tool's ``(args, result)``,
|
||||||
|
returns a ``ReverseDescriptor`` describing the inverse tool
|
||||||
|
invocation. Consumed by the snapshot/revert pipeline.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
factory: Callable[[dict[str, Any]], BaseTool]
|
||||||
|
requires: list[str] = field(default_factory=list)
|
||||||
|
enabled_by_default: bool = True
|
||||||
|
hidden: bool = False
|
||||||
|
required_connector: str | None = None
|
||||||
|
dedup_key: Callable[[dict[str, Any]], str] | None = None
|
||||||
|
reverse: Callable[[dict[str, Any], Any], dict[str, Any]] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Deferred-import factories
|
||||||
|
# =============================================================================
|
||||||
|
# Used for tools whose impls live under ``multi_agent_chat``. Importing those
|
||||||
|
# at module-load time would cycle (``multi_agent_chat`` middleware imports
|
||||||
|
# this registry). The import inside the factory runs only when
|
||||||
|
# ``build_tools`` is called, by which point ``multi_agent_chat`` is fully
|
||||||
|
# initialised.
|
||||||
|
|
||||||
|
|
||||||
|
def _build_create_automation_tool(deps: dict[str, Any]) -> BaseTool:
|
||||||
|
from app.agents.multi_agent_chat.main_agent.tools.automation import (
|
||||||
|
create_create_automation_tool,
|
||||||
|
)
|
||||||
|
|
||||||
|
return create_create_automation_tool(
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
llm=deps["llm"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Built-in Tools Registry
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Registry of all built-in tools
|
||||||
|
# Contributors: Add your new tools here!
|
||||||
|
BUILTIN_TOOLS: list[ToolDefinition] = [
|
||||||
|
# Podcast generation tool
|
||||||
|
ToolDefinition(
|
||||||
|
name="generate_podcast",
|
||||||
|
description="Generate an audio podcast from provided content",
|
||||||
|
factory=lambda deps: create_generate_podcast_tool(
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
thread_id=deps["thread_id"],
|
||||||
|
),
|
||||||
|
requires=["search_space_id", "db_session", "thread_id"],
|
||||||
|
),
|
||||||
|
# Video presentation generation tool
|
||||||
|
ToolDefinition(
|
||||||
|
name="generate_video_presentation",
|
||||||
|
description="Generate a video presentation with slides and narration from provided content",
|
||||||
|
factory=lambda deps: create_generate_video_presentation_tool(
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
thread_id=deps["thread_id"],
|
||||||
|
),
|
||||||
|
requires=["search_space_id", "db_session", "thread_id"],
|
||||||
|
),
|
||||||
|
# Report generation tool (inline, short-lived sessions for DB ops)
|
||||||
|
# Supports internal KB search via source_strategy so the agent does not
|
||||||
|
# need a separate search step before generating.
|
||||||
|
ToolDefinition(
|
||||||
|
name="generate_report",
|
||||||
|
description="Generate a structured report from provided content and export it",
|
||||||
|
factory=lambda deps: create_generate_report_tool(
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
thread_id=deps["thread_id"],
|
||||||
|
connector_service=deps.get("connector_service"),
|
||||||
|
available_connectors=deps.get("available_connectors"),
|
||||||
|
available_document_types=deps.get("available_document_types"),
|
||||||
|
),
|
||||||
|
requires=["search_space_id", "thread_id"],
|
||||||
|
# connector_service, available_connectors, and available_document_types
|
||||||
|
# are optional — when missing, source_strategy="kb_search" degrades
|
||||||
|
# gracefully to "provided"
|
||||||
|
),
|
||||||
|
# Resume generation tool (Typst-based, uses rendercv package)
|
||||||
|
ToolDefinition(
|
||||||
|
name="generate_resume",
|
||||||
|
description="Generate a professional resume as a Typst document",
|
||||||
|
factory=lambda deps: create_generate_resume_tool(
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
thread_id=deps["thread_id"],
|
||||||
|
),
|
||||||
|
requires=["search_space_id", "thread_id"],
|
||||||
|
),
|
||||||
|
# Generate image tool - creates images using AI models (DALL-E, GPT Image, etc.)
|
||||||
|
ToolDefinition(
|
||||||
|
name="generate_image",
|
||||||
|
description="Generate images from text descriptions using AI image models",
|
||||||
|
factory=lambda deps: create_generate_image_tool(
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
),
|
||||||
|
requires=["search_space_id", "db_session"],
|
||||||
|
),
|
||||||
|
# Web scraping tool - extracts content from webpages
|
||||||
|
ToolDefinition(
|
||||||
|
name="scrape_webpage",
|
||||||
|
description="Scrape and extract the main content from a webpage",
|
||||||
|
factory=lambda deps: create_scrape_webpage_tool(
|
||||||
|
firecrawl_api_key=deps.get("firecrawl_api_key"),
|
||||||
|
),
|
||||||
|
requires=[], # firecrawl_api_key is optional
|
||||||
|
),
|
||||||
|
# Web search tool — real-time web search via SearXNG + user-configured engines
|
||||||
|
ToolDefinition(
|
||||||
|
name="web_search",
|
||||||
|
description="Search the web for real-time information using configured search engines",
|
||||||
|
factory=lambda deps: create_web_search_tool(
|
||||||
|
search_space_id=deps.get("search_space_id"),
|
||||||
|
available_connectors=deps.get("available_connectors"),
|
||||||
|
),
|
||||||
|
requires=[],
|
||||||
|
),
|
||||||
|
# =========================================================================
|
||||||
|
# SERVICE ACCOUNT DISCOVERY
|
||||||
|
# Generic tool for the LLM to discover connected accounts and resolve
|
||||||
|
# service-specific identifiers (e.g. Jira cloudId, Slack team, etc.)
|
||||||
|
# =========================================================================
|
||||||
|
ToolDefinition(
|
||||||
|
name="get_connected_accounts",
|
||||||
|
description="Discover connected accounts for a service and their metadata",
|
||||||
|
factory=lambda deps: create_get_connected_accounts_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
),
|
||||||
|
# =========================================================================
|
||||||
|
# AUTOMATION AUTHORING - single HITL tool. The tool takes an NL ``intent``
|
||||||
|
# from the main agent, drafts the full AutomationCreate JSON via a focused
|
||||||
|
# sub-LLM, surfaces it on an approval card, and persists on approval. The
|
||||||
|
# factory defers its import because the impl lives under ``multi_agent_chat``
|
||||||
|
# and that package transitively pulls this registry via middleware;
|
||||||
|
# deferring to ``build_tools`` call-time breaks the cycle without a
|
||||||
|
# parallel registry.
|
||||||
|
# =========================================================================
|
||||||
|
ToolDefinition(
|
||||||
|
name="create_automation",
|
||||||
|
description="Draft an automation from an NL intent; user approves the card; tool saves",
|
||||||
|
factory=_build_create_automation_tool,
|
||||||
|
requires=["search_space_id", "user_id", "llm"],
|
||||||
|
),
|
||||||
|
# =========================================================================
|
||||||
|
# MEMORY TOOL - single update_memory, private or team by thread_visibility
|
||||||
|
# =========================================================================
|
||||||
|
ToolDefinition(
|
||||||
|
name="update_memory",
|
||||||
|
description="Save important long-term facts, preferences, and instructions to the (personal or team) memory",
|
||||||
|
factory=lambda deps: (
|
||||||
|
create_update_team_memory_tool(
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
llm=deps.get("llm"),
|
||||||
|
)
|
||||||
|
if deps["thread_visibility"] == ChatVisibility.SEARCH_SPACE
|
||||||
|
else create_update_memory_tool(
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
llm=deps.get("llm"),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
requires=[
|
||||||
|
"user_id",
|
||||||
|
"search_space_id",
|
||||||
|
"db_session",
|
||||||
|
"thread_visibility",
|
||||||
|
"llm",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
# =========================================================================
|
||||||
|
# NOTION TOOLS - create, update, delete pages
|
||||||
|
# Auto-disabled when no Notion connector is configured (see chat_deepagent.py)
|
||||||
|
# =========================================================================
|
||||||
|
ToolDefinition(
|
||||||
|
name="create_notion_page",
|
||||||
|
description="Create a new page in the user's Notion workspace",
|
||||||
|
factory=lambda deps: create_create_notion_page_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="NOTION_CONNECTOR",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("title"),
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="update_notion_page",
|
||||||
|
description="Append new content to an existing Notion page",
|
||||||
|
factory=lambda deps: create_update_notion_page_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="NOTION_CONNECTOR",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("page_title"),
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="delete_notion_page",
|
||||||
|
description="Delete an existing Notion page",
|
||||||
|
factory=lambda deps: create_delete_notion_page_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="NOTION_CONNECTOR",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("page_title"),
|
||||||
|
),
|
||||||
|
# =========================================================================
|
||||||
|
# GOOGLE DRIVE TOOLS - create files, delete files
|
||||||
|
# Auto-disabled when no Google Drive connector is configured (see chat_deepagent.py)
|
||||||
|
# =========================================================================
|
||||||
|
ToolDefinition(
|
||||||
|
name="create_google_drive_file",
|
||||||
|
description="Create a new Google Doc or Google Sheet in Google Drive",
|
||||||
|
factory=lambda deps: create_create_google_drive_file_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="GOOGLE_DRIVE_FILE",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("file_name"),
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="delete_google_drive_file",
|
||||||
|
description="Move an indexed Google Drive file to trash",
|
||||||
|
factory=lambda deps: create_delete_google_drive_file_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="GOOGLE_DRIVE_FILE",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("file_name"),
|
||||||
|
),
|
||||||
|
# =========================================================================
|
||||||
|
# DROPBOX TOOLS - create and trash files
|
||||||
|
# Auto-disabled when no Dropbox connector is configured (see chat_deepagent.py)
|
||||||
|
# =========================================================================
|
||||||
|
ToolDefinition(
|
||||||
|
name="create_dropbox_file",
|
||||||
|
description="Create a new file in Dropbox",
|
||||||
|
factory=lambda deps: create_create_dropbox_file_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="DROPBOX_FILE",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("file_name"),
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="delete_dropbox_file",
|
||||||
|
description="Delete a file from Dropbox",
|
||||||
|
factory=lambda deps: create_delete_dropbox_file_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="DROPBOX_FILE",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("file_name"),
|
||||||
|
),
|
||||||
|
# =========================================================================
|
||||||
|
# ONEDRIVE TOOLS - create and trash files
|
||||||
|
# Auto-disabled when no OneDrive connector is configured (see chat_deepagent.py)
|
||||||
|
# =========================================================================
|
||||||
|
ToolDefinition(
|
||||||
|
name="create_onedrive_file",
|
||||||
|
description="Create a new file in Microsoft OneDrive",
|
||||||
|
factory=lambda deps: create_create_onedrive_file_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="ONEDRIVE_FILE",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("file_name"),
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="delete_onedrive_file",
|
||||||
|
description="Move a OneDrive file to the recycle bin",
|
||||||
|
factory=lambda deps: create_delete_onedrive_file_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="ONEDRIVE_FILE",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("file_name"),
|
||||||
|
),
|
||||||
|
# =========================================================================
|
||||||
|
# GOOGLE CALENDAR TOOLS - search, create, update, delete events
|
||||||
|
# Auto-disabled when no Google Calendar connector is configured
|
||||||
|
# =========================================================================
|
||||||
|
ToolDefinition(
|
||||||
|
name="search_calendar_events",
|
||||||
|
description="Search Google Calendar events within a date range",
|
||||||
|
factory=lambda deps: create_search_calendar_events_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="GOOGLE_CALENDAR_CONNECTOR",
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="create_calendar_event",
|
||||||
|
description="Create a new event on Google Calendar",
|
||||||
|
factory=lambda deps: create_create_calendar_event_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="GOOGLE_CALENDAR_CONNECTOR",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("title"),
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="update_calendar_event",
|
||||||
|
description="Update an existing indexed Google Calendar event",
|
||||||
|
factory=lambda deps: create_update_calendar_event_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="GOOGLE_CALENDAR_CONNECTOR",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("event_title_or_id"),
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="delete_calendar_event",
|
||||||
|
description="Delete an existing indexed Google Calendar event",
|
||||||
|
factory=lambda deps: create_delete_calendar_event_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="GOOGLE_CALENDAR_CONNECTOR",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("event_title_or_id"),
|
||||||
|
),
|
||||||
|
# =========================================================================
|
||||||
|
# GMAIL TOOLS - search, read, create drafts, update drafts, send, trash
|
||||||
|
# Auto-disabled when no Gmail connector is configured
|
||||||
|
# =========================================================================
|
||||||
|
ToolDefinition(
|
||||||
|
name="search_gmail",
|
||||||
|
description="Search emails in Gmail using Gmail search syntax",
|
||||||
|
factory=lambda deps: create_search_gmail_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="GOOGLE_GMAIL_CONNECTOR",
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="read_gmail_email",
|
||||||
|
description="Read the full content of a specific Gmail email",
|
||||||
|
factory=lambda deps: create_read_gmail_email_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="GOOGLE_GMAIL_CONNECTOR",
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="create_gmail_draft",
|
||||||
|
description="Create a draft email in Gmail",
|
||||||
|
factory=lambda deps: create_create_gmail_draft_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="GOOGLE_GMAIL_CONNECTOR",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("subject"),
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="send_gmail_email",
|
||||||
|
description="Send an email via Gmail",
|
||||||
|
factory=lambda deps: create_send_gmail_email_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="GOOGLE_GMAIL_CONNECTOR",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("subject"),
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="trash_gmail_email",
|
||||||
|
description="Move an indexed email to trash in Gmail",
|
||||||
|
factory=lambda deps: create_trash_gmail_email_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="GOOGLE_GMAIL_CONNECTOR",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("email_subject_or_id"),
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="update_gmail_draft",
|
||||||
|
description="Update an existing Gmail draft",
|
||||||
|
factory=lambda deps: create_update_gmail_draft_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="GOOGLE_GMAIL_CONNECTOR",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("draft_subject_or_id"),
|
||||||
|
),
|
||||||
|
# =========================================================================
|
||||||
|
# CONFLUENCE TOOLS - create, update, delete pages
|
||||||
|
# Auto-disabled when no Confluence connector is configured (see chat_deepagent.py)
|
||||||
|
# =========================================================================
|
||||||
|
ToolDefinition(
|
||||||
|
name="create_confluence_page",
|
||||||
|
description="Create a new page in the user's Confluence space",
|
||||||
|
factory=lambda deps: create_create_confluence_page_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="CONFLUENCE_CONNECTOR",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("title"),
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="update_confluence_page",
|
||||||
|
description="Update an existing indexed Confluence page",
|
||||||
|
factory=lambda deps: create_update_confluence_page_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="CONFLUENCE_CONNECTOR",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("page_title_or_id"),
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="delete_confluence_page",
|
||||||
|
description="Delete an existing indexed Confluence page",
|
||||||
|
factory=lambda deps: create_delete_confluence_page_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="CONFLUENCE_CONNECTOR",
|
||||||
|
dedup_key=wrap_dedup_key_by_arg_name("page_title_or_id"),
|
||||||
|
),
|
||||||
|
# =========================================================================
|
||||||
|
# DISCORD TOOLS - list channels, read messages, send messages
|
||||||
|
# Auto-disabled when no Discord connector is configured
|
||||||
|
# =========================================================================
|
||||||
|
ToolDefinition(
|
||||||
|
name="list_discord_channels",
|
||||||
|
description="List text channels in the connected Discord server",
|
||||||
|
factory=lambda deps: create_list_discord_channels_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="DISCORD_CONNECTOR",
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="read_discord_messages",
|
||||||
|
description="Read recent messages from a Discord text channel",
|
||||||
|
factory=lambda deps: create_read_discord_messages_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="DISCORD_CONNECTOR",
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="send_discord_message",
|
||||||
|
description="Send a message to a Discord text channel",
|
||||||
|
factory=lambda deps: create_send_discord_message_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="DISCORD_CONNECTOR",
|
||||||
|
),
|
||||||
|
# =========================================================================
|
||||||
|
# TEAMS TOOLS - list channels, read messages, send messages
|
||||||
|
# Auto-disabled when no Teams connector is configured
|
||||||
|
# =========================================================================
|
||||||
|
ToolDefinition(
|
||||||
|
name="list_teams_channels",
|
||||||
|
description="List Microsoft Teams and their channels",
|
||||||
|
factory=lambda deps: create_list_teams_channels_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="TEAMS_CONNECTOR",
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="read_teams_messages",
|
||||||
|
description="Read recent messages from a Microsoft Teams channel",
|
||||||
|
factory=lambda deps: create_read_teams_messages_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="TEAMS_CONNECTOR",
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="send_teams_message",
|
||||||
|
description="Send a message to a Microsoft Teams channel",
|
||||||
|
factory=lambda deps: create_send_teams_message_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="TEAMS_CONNECTOR",
|
||||||
|
),
|
||||||
|
# =========================================================================
|
||||||
|
# LUMA TOOLS - list events, read event details, create events
|
||||||
|
# Auto-disabled when no Luma connector is configured
|
||||||
|
# =========================================================================
|
||||||
|
ToolDefinition(
|
||||||
|
name="list_luma_events",
|
||||||
|
description="List upcoming and recent Luma events",
|
||||||
|
factory=lambda deps: create_list_luma_events_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="LUMA_CONNECTOR",
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="read_luma_event",
|
||||||
|
description="Read detailed information about a specific Luma event",
|
||||||
|
factory=lambda deps: create_read_luma_event_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="LUMA_CONNECTOR",
|
||||||
|
),
|
||||||
|
ToolDefinition(
|
||||||
|
name="create_luma_event",
|
||||||
|
description="Create a new event on Luma",
|
||||||
|
factory=lambda deps: create_create_luma_event_tool(
|
||||||
|
db_session=deps["db_session"],
|
||||||
|
search_space_id=deps["search_space_id"],
|
||||||
|
user_id=deps["user_id"],
|
||||||
|
),
|
||||||
|
requires=["db_session", "search_space_id", "user_id"],
|
||||||
|
required_connector="LUMA_CONNECTOR",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Registry Functions
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
def get_tool_by_name(name: str) -> ToolDefinition | None:
|
||||||
|
"""Get a tool definition by its name."""
|
||||||
|
for tool_def in BUILTIN_TOOLS:
|
||||||
|
if tool_def.name == name:
|
||||||
|
return tool_def
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_connector_gated_tools(
|
||||||
|
available_connectors: list[str] | None,
|
||||||
|
) -> list[str]:
|
||||||
|
"""Return tool names to disable"""
|
||||||
|
available = set() if available_connectors is None else set(available_connectors)
|
||||||
|
|
||||||
|
disabled: list[str] = []
|
||||||
|
for tool_def in BUILTIN_TOOLS:
|
||||||
|
if tool_def.required_connector and tool_def.required_connector not in available:
|
||||||
|
disabled.append(tool_def.name)
|
||||||
|
return disabled
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_tool_names() -> list[str]:
|
||||||
|
"""Get names of all registered tools."""
|
||||||
|
return [tool_def.name for tool_def in BUILTIN_TOOLS]
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_enabled_tools() -> list[str]:
|
||||||
|
"""Get names of tools that are enabled by default (excludes hidden tools)."""
|
||||||
|
return [tool_def.name for tool_def in BUILTIN_TOOLS if tool_def.enabled_by_default]
|
||||||
|
|
||||||
|
|
||||||
|
def build_tools(
|
||||||
|
dependencies: dict[str, Any],
|
||||||
|
enabled_tools: list[str] | None = None,
|
||||||
|
disabled_tools: list[str] | None = None,
|
||||||
|
additional_tools: list[BaseTool] | None = None,
|
||||||
|
) -> list[BaseTool]:
|
||||||
|
"""Build the list of tools for the agent.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dependencies: Dict containing all possible dependencies:
|
||||||
|
- search_space_id: The search space ID
|
||||||
|
- db_session: Database session
|
||||||
|
- connector_service: Connector service instance
|
||||||
|
- firecrawl_api_key: Optional Firecrawl API key
|
||||||
|
enabled_tools: Explicit list of tool names to enable. If None, uses defaults.
|
||||||
|
disabled_tools: List of tool names to disable (applied after enabled_tools).
|
||||||
|
additional_tools: Extra tools to add (e.g., custom tools not in registry).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of configured tool instances ready for the agent.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
# Use all default tools
|
||||||
|
tools = build_tools(deps)
|
||||||
|
|
||||||
|
# Use only specific tools
|
||||||
|
tools = build_tools(deps, enabled_tools=["generate_report"])
|
||||||
|
|
||||||
|
# Use defaults but disable podcast
|
||||||
|
tools = build_tools(deps, disabled_tools=["generate_podcast"])
|
||||||
|
|
||||||
|
# Add custom tools
|
||||||
|
tools = build_tools(deps, additional_tools=[my_custom_tool])
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Determine which tools to enable
|
||||||
|
if enabled_tools is not None:
|
||||||
|
tool_names_to_use = set(enabled_tools)
|
||||||
|
else:
|
||||||
|
tool_names_to_use = set(get_default_enabled_tools())
|
||||||
|
|
||||||
|
# Apply disabled list
|
||||||
|
if disabled_tools:
|
||||||
|
tool_names_to_use -= set(disabled_tools)
|
||||||
|
|
||||||
|
# Build the tools (skip hidden/WIP tools unconditionally)
|
||||||
|
tools: list[BaseTool] = []
|
||||||
|
for tool_def in BUILTIN_TOOLS:
|
||||||
|
if tool_def.hidden or tool_def.name not in tool_names_to_use:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check that all required dependencies are provided
|
||||||
|
missing_deps = [dep for dep in tool_def.requires if dep not in dependencies]
|
||||||
|
if missing_deps:
|
||||||
|
msg = f"Tool '{tool_def.name}' requires dependencies: {missing_deps}"
|
||||||
|
raise ValueError(
|
||||||
|
msg,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create the tool
|
||||||
|
tool = tool_def.factory(dependencies)
|
||||||
|
# Propagate the registry-level metadata so middleware (e.g.
|
||||||
|
# ``DedupHITLToolCallsMiddleware``) and the action-log/revert
|
||||||
|
# pipeline can pick the resolvers up via ``tool.metadata`` without
|
||||||
|
# re-importing :data:`BUILTIN_TOOLS`.
|
||||||
|
if tool_def.dedup_key is not None or tool_def.reverse is not None:
|
||||||
|
existing_meta = getattr(tool, "metadata", None) or {}
|
||||||
|
merged_meta = dict(existing_meta)
|
||||||
|
if tool_def.dedup_key is not None:
|
||||||
|
merged_meta.setdefault("dedup_key", tool_def.dedup_key)
|
||||||
|
if tool_def.reverse is not None:
|
||||||
|
merged_meta.setdefault("reverse", tool_def.reverse)
|
||||||
|
try:
|
||||||
|
tool.metadata = merged_meta
|
||||||
|
except Exception:
|
||||||
|
logger.debug(
|
||||||
|
"Tool %s rejected metadata mutation; relying on registry lookup",
|
||||||
|
tool_def.name,
|
||||||
|
)
|
||||||
|
tools.append(tool)
|
||||||
|
|
||||||
|
# Add any additional custom tools
|
||||||
|
if additional_tools:
|
||||||
|
tools.extend(additional_tools)
|
||||||
|
|
||||||
|
return tools
|
||||||
|
|
||||||
|
|
||||||
|
async def build_tools_async(
|
||||||
|
dependencies: dict[str, Any],
|
||||||
|
enabled_tools: list[str] | None = None,
|
||||||
|
disabled_tools: list[str] | None = None,
|
||||||
|
additional_tools: list[BaseTool] | None = None,
|
||||||
|
include_mcp_tools: bool = True,
|
||||||
|
) -> list[BaseTool]:
|
||||||
|
"""Async version of build_tools that also loads MCP tools from database.
|
||||||
|
|
||||||
|
Design Note:
|
||||||
|
This function exists because MCP tools require database queries to load
|
||||||
|
user configs, while built-in tools are created synchronously from static
|
||||||
|
code.
|
||||||
|
|
||||||
|
Alternative: We could make build_tools() itself async and always query
|
||||||
|
the database, but that would force async everywhere even when only using
|
||||||
|
built-in tools. The current design keeps the simple case (static tools
|
||||||
|
only) synchronous while supporting dynamic database-loaded tools through
|
||||||
|
this async wrapper.
|
||||||
|
|
||||||
|
Phase 1.3: built-in tool construction (CPU; runs in a thread pool to
|
||||||
|
avoid event-loop stalls) and MCP tool loading (HTTP/DB I/O; runs on
|
||||||
|
the event loop) are kicked off concurrently. Cold-path savings are
|
||||||
|
bounded by the slower of the two — typically MCP at ~200ms-1.7s —
|
||||||
|
so the parallelization recovers the ~50-200ms previously spent
|
||||||
|
serially on built-in construction.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dependencies: Dict containing all possible dependencies
|
||||||
|
enabled_tools: Explicit list of tool names to enable. If None, uses defaults.
|
||||||
|
disabled_tools: List of tool names to disable (applied after enabled_tools).
|
||||||
|
additional_tools: Extra tools to add (e.g., custom tools not in registry).
|
||||||
|
include_mcp_tools: Whether to load user's MCP tools from database.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of configured tool instances ready for the agent, including MCP tools.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
import time
|
||||||
|
|
||||||
|
_perf_log = logging.getLogger("surfsense.perf")
|
||||||
|
_perf_log.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
can_load_mcp = (
|
||||||
|
include_mcp_tools
|
||||||
|
and "db_session" in dependencies
|
||||||
|
and "search_space_id" in dependencies
|
||||||
|
)
|
||||||
|
|
||||||
|
# Built-in tool construction is synchronous + CPU-only. Off-loop it so
|
||||||
|
# MCP's HTTP/DB I/O can fire concurrently. ``build_tools`` is pure
|
||||||
|
# function over its inputs — safe to thread-shift.
|
||||||
|
_t0 = time.perf_counter()
|
||||||
|
builtin_task = asyncio.create_task(
|
||||||
|
asyncio.to_thread(
|
||||||
|
build_tools, dependencies, enabled_tools, disabled_tools, additional_tools
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
mcp_task: asyncio.Task | None = None
|
||||||
|
if can_load_mcp:
|
||||||
|
mcp_task = asyncio.create_task(
|
||||||
|
load_mcp_tools(
|
||||||
|
dependencies["db_session"],
|
||||||
|
dependencies["search_space_id"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Surface failures from each task independently so a flaky MCP
|
||||||
|
# endpoint never poisons built-in tool registration. ``return_exceptions``
|
||||||
|
# gives us per-task exceptions instead of dropping the second result
|
||||||
|
# when the first raises.
|
||||||
|
if mcp_task is not None:
|
||||||
|
builtin_result, mcp_result = await asyncio.gather(
|
||||||
|
builtin_task, mcp_task, return_exceptions=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
builtin_result = await builtin_task
|
||||||
|
mcp_result = None
|
||||||
|
|
||||||
|
if isinstance(builtin_result, BaseException):
|
||||||
|
raise builtin_result # built-in registration failure is non-recoverable
|
||||||
|
tools: list[BaseTool] = builtin_result
|
||||||
|
_perf_log.info(
|
||||||
|
"[build_tools_async] Built-in tools in %.3fs (%d tools, parallel)",
|
||||||
|
time.perf_counter() - _t0,
|
||||||
|
len(tools),
|
||||||
|
)
|
||||||
|
|
||||||
|
if mcp_task is not None:
|
||||||
|
if isinstance(mcp_result, BaseException):
|
||||||
|
# ``return_exceptions=True`` captures the exception out-of-band,
|
||||||
|
# so ``sys.exc_info()`` is empty here. Pass the captured
|
||||||
|
# exception via ``exc_info=`` to get a real traceback.
|
||||||
|
logging.error(
|
||||||
|
"Failed to load MCP tools: %s", mcp_result, exc_info=mcp_result
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
mcp_tools = mcp_result or []
|
||||||
|
_perf_log.info(
|
||||||
|
"[build_tools_async] MCP tools loaded in %.3fs (%d tools, parallel)",
|
||||||
|
time.perf_counter() - _t0,
|
||||||
|
len(mcp_tools),
|
||||||
|
)
|
||||||
|
tools.extend(mcp_tools)
|
||||||
|
logging.info(
|
||||||
|
"Registered %d MCP tools: %s",
|
||||||
|
len(mcp_tools),
|
||||||
|
[t.name for t in mcp_tools],
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.info(
|
||||||
|
"Total tools for agent: %d — %s",
|
||||||
|
len(tools),
|
||||||
|
[t.name for t in tools],
|
||||||
|
)
|
||||||
|
|
||||||
|
return tools
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
from app.agents.new_chat.tools.teams.list_channels import (
|
from app.agents.shared.tools.teams.list_channels import (
|
||||||
create_list_teams_channels_tool,
|
create_list_teams_channels_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.teams.read_messages import (
|
from app.agents.shared.tools.teams.read_messages import (
|
||||||
create_read_teams_messages_tool,
|
create_read_teams_messages_tool,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.teams.send_message import (
|
from app.agents.shared.tools.teams.send_message import (
|
||||||
create_send_teams_message_tool,
|
create_send_teams_message_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -5,7 +5,7 @@ import httpx
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import request_approval
|
from app.agents.shared.tools.hitl import request_approval
|
||||||
from app.db import async_session_maker
|
from app.db import async_session_maker
|
||||||
|
|
||||||
from ._auth import GRAPH_API, get_access_token, get_teams_connector
|
from ._auth import GRAPH_API, get_access_token, get_teams_connector
|
||||||
|
|
@ -665,7 +665,7 @@ def _refresh_mcp_cache(connector_id: int, space_id: int) -> None:
|
||||||
isolated from the OAuth response flow.
|
isolated from the OAuth response flow.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from app.agents.new_chat.tools.mcp_tools_cache import (
|
from app.agents.shared.tools.mcp_tools_cache import (
|
||||||
refresh_mcp_tools_cache_for_connector,
|
refresh_mcp_tools_cache_for_connector,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1668,7 +1668,7 @@ async def list_agent_tools(
|
||||||
|
|
||||||
Hidden (WIP) tools are excluded from the response.
|
Hidden (WIP) tools are excluded from the response.
|
||||||
"""
|
"""
|
||||||
from app.agents.new_chat.tools.registry import BUILTIN_TOOLS
|
from app.agents.shared.tools.registry import BUILTIN_TOOLS
|
||||||
|
|
||||||
return [
|
return [
|
||||||
AgentToolInfo(
|
AgentToolInfo(
|
||||||
|
|
|
||||||
|
|
@ -675,7 +675,7 @@ async def delete_search_source_connector(
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
if is_mcp:
|
if is_mcp:
|
||||||
from app.agents.new_chat.tools.mcp_tool import invalidate_mcp_tools_cache
|
from app.agents.shared.tools.mcp_tool import invalidate_mcp_tools_cache
|
||||||
|
|
||||||
invalidate_mcp_tools_cache(search_space_id)
|
invalidate_mcp_tools_cache(search_space_id)
|
||||||
|
|
||||||
|
|
@ -2687,7 +2687,7 @@ async def create_mcp_connector(
|
||||||
f"for user {user.id} in search space {search_space_id}"
|
f"for user {user.id} in search space {search_space_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
from app.agents.new_chat.tools.mcp_tools_cache import (
|
from app.agents.shared.tools.mcp_tools_cache import (
|
||||||
refresh_mcp_tools_cache_for_connector,
|
refresh_mcp_tools_cache_for_connector,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -2867,7 +2867,7 @@ async def update_mcp_connector(
|
||||||
|
|
||||||
logger.info(f"Updated MCP connector {connector_id}")
|
logger.info(f"Updated MCP connector {connector_id}")
|
||||||
|
|
||||||
from app.agents.new_chat.tools.mcp_tools_cache import (
|
from app.agents.shared.tools.mcp_tools_cache import (
|
||||||
refresh_mcp_tools_cache_for_connector,
|
refresh_mcp_tools_cache_for_connector,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -2927,7 +2927,7 @@ async def delete_mcp_connector(
|
||||||
await session.delete(connector)
|
await session.delete(connector)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
from app.agents.new_chat.tools.mcp_tool import invalidate_mcp_tools_cache
|
from app.agents.shared.tools.mcp_tool import invalidate_mcp_tools_cache
|
||||||
|
|
||||||
invalidate_mcp_tools_cache(search_space_id)
|
invalidate_mcp_tools_cache(search_space_id)
|
||||||
|
|
||||||
|
|
@ -2966,7 +2966,7 @@ async def test_mcp_server_connection(
|
||||||
Connection status and list of available tools
|
Connection status and list of available tools
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from app.agents.new_chat.tools.mcp_client import (
|
from app.agents.shared.tools.mcp_client import (
|
||||||
test_mcp_connection,
|
test_mcp_connection,
|
||||||
test_mcp_http_connection,
|
test_mcp_http_connection,
|
||||||
)
|
)
|
||||||
|
|
@ -3157,7 +3157,7 @@ async def trust_mcp_tool(
|
||||||
connectors (``LINEAR_CONNECTOR``, ``JIRA_CONNECTOR``, ...) — the
|
connectors (``LINEAR_CONNECTOR``, ``JIRA_CONNECTOR``, ...) — the
|
||||||
storage primitive is the same JSON list under ``config.trusted_tools``.
|
storage primitive is the same JSON list under ``config.trusted_tools``.
|
||||||
"""
|
"""
|
||||||
from app.agents.new_chat.tools.mcp_tool import invalidate_mcp_tools_cache
|
from app.agents.shared.tools.mcp_tool import invalidate_mcp_tools_cache
|
||||||
from app.services.user_tool_allowlist import add_user_trust
|
from app.services.user_tool_allowlist import add_user_trust
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -3197,7 +3197,7 @@ async def untrust_mcp_tool(
|
||||||
|
|
||||||
The tool will require HITL approval again on subsequent calls.
|
The tool will require HITL approval again on subsequent calls.
|
||||||
"""
|
"""
|
||||||
from app.agents.new_chat.tools.mcp_tool import invalidate_mcp_tools_cache
|
from app.agents.shared.tools.mcp_tool import invalidate_mcp_tools_cache
|
||||||
from app.services.user_tool_allowlist import remove_user_trust
|
from app.services.user_tool_allowlist import remove_user_trust
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ logger = logging.getLogger(__name__)
|
||||||
# class-body init time. ``app.agents.shared.llm_config`` re-exports
|
# class-body init time. ``app.agents.shared.llm_config`` re-exports
|
||||||
# this constant under the historical ``PROVIDER_MAP`` name; placing the
|
# this constant under the historical ``PROVIDER_MAP`` name; placing the
|
||||||
# map there directly would re-introduce the
|
# map there directly would re-introduce the
|
||||||
# ``app.config -> ... -> app.agents.new_chat.tools.generate_image ->
|
# ``app.config -> ... -> app.agents.shared.tools.generate_image ->
|
||||||
# app.config`` cycle that prompted the move.
|
# app.config`` cycle that prompted the move.
|
||||||
_PROVIDER_PREFIX_MAP: dict[str, str] = {
|
_PROVIDER_PREFIX_MAP: dict[str, str] = {
|
||||||
"OPENAI": "openai",
|
"OPENAI": "openai",
|
||||||
|
|
|
||||||
|
|
@ -137,10 +137,10 @@ def install(active_patches: list[Any]) -> None:
|
||||||
"""Patch production MCP streamable-HTTP boundaries exactly once."""
|
"""Patch production MCP streamable-HTTP boundaries exactly once."""
|
||||||
targets = [
|
targets = [
|
||||||
(
|
(
|
||||||
"app.agents.new_chat.tools.mcp_tool.streamablehttp_client",
|
"app.agents.shared.tools.mcp_tool.streamablehttp_client",
|
||||||
_fake_streamablehttp_client,
|
_fake_streamablehttp_client,
|
||||||
),
|
),
|
||||||
("app.agents.new_chat.tools.mcp_tool.ClientSession", _FakeClientSession),
|
("app.agents.shared.tools.mcp_tool.ClientSession", _FakeClientSession),
|
||||||
]
|
]
|
||||||
for target, replacement in targets:
|
for target, replacement in targets:
|
||||||
p = patch(target, replacement)
|
p = patch(target, replacement)
|
||||||
|
|
|
||||||
|
|
@ -429,9 +429,9 @@ def install(active_patches: list[Any]) -> None:
|
||||||
("app.connectors.google_drive.client.build", _fake_build),
|
("app.connectors.google_drive.client.build", _fake_build),
|
||||||
("app.connectors.google_gmail_connector.build", _fake_build),
|
("app.connectors.google_gmail_connector.build", _fake_build),
|
||||||
("app.connectors.google_calendar_connector.build", _fake_build),
|
("app.connectors.google_calendar_connector.build", _fake_build),
|
||||||
("app.agents.new_chat.tools.google_calendar.create_event.build", _fake_build),
|
("app.agents.shared.tools.google_calendar.create_event.build", _fake_build),
|
||||||
("app.agents.new_chat.tools.google_calendar.update_event.build", _fake_build),
|
("app.agents.shared.tools.google_calendar.update_event.build", _fake_build),
|
||||||
("app.agents.new_chat.tools.google_calendar.delete_event.build", _fake_build),
|
("app.agents.shared.tools.google_calendar.delete_event.build", _fake_build),
|
||||||
("googleapiclient.http.MediaIoBaseDownload", _FakeMediaIoBaseDownload),
|
("googleapiclient.http.MediaIoBaseDownload", _FakeMediaIoBaseDownload),
|
||||||
(
|
(
|
||||||
"app.connectors.google_drive.client._build_thread_http",
|
"app.connectors.google_drive.client._build_thread_http",
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,7 @@ def patched_shielded_session(async_engine, monkeypatch):
|
||||||
yield session
|
yield session
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"app.agents.new_chat.tools.knowledge_base.shielded_async_session",
|
"app.agents.shared.tools.knowledge_base.shielded_async_session",
|
||||||
_test_shielded,
|
_test_shielded,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ async def test_browse_recent_documents_with_list_type_returns_both(
|
||||||
committed_google_data, patched_shielded_session
|
committed_google_data, patched_shielded_session
|
||||||
):
|
):
|
||||||
"""_browse_recent_documents returns docs of all types when given a list."""
|
"""_browse_recent_documents returns docs of all types when given a list."""
|
||||||
from app.agents.new_chat.tools.knowledge_base import _browse_recent_documents
|
from app.agents.shared.tools.knowledge_base import _browse_recent_documents
|
||||||
|
|
||||||
space_id = committed_google_data["search_space_id"]
|
space_id = committed_google_data["search_space_id"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ from langchain_core.tools import tool
|
||||||
|
|
||||||
from app.agents.shared.feature_flags import AgentFeatureFlags
|
from app.agents.shared.feature_flags import AgentFeatureFlags
|
||||||
from app.agents.shared.middleware.action_log import ActionLogMiddleware
|
from app.agents.shared.middleware.action_log import ActionLogMiddleware
|
||||||
from app.agents.new_chat.tools.registry import ToolDefinition
|
from app.agents.shared.tools.registry import ToolDefinition
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ def test_no_agent_tools_means_no_dedup() -> None:
|
||||||
|
|
||||||
Coverage for the previously hardcoded native HITL tools now lives on
|
Coverage for the previously hardcoded native HITL tools now lives on
|
||||||
each :class:`ToolDefinition.dedup_key` in
|
each :class:`ToolDefinition.dedup_key` in
|
||||||
:mod:`app.agents.new_chat.tools.registry`, which is wired through to
|
:mod:`app.agents.shared.tools.registry`, which is wired through to
|
||||||
``tool.metadata`` by :func:`build_tools`.
|
``tool.metadata`` by :func:`build_tools`.
|
||||||
"""
|
"""
|
||||||
mw = DedupHITLToolCallsMiddleware(agent_tools=None)
|
mw = DedupHITLToolCallsMiddleware(agent_tools=None)
|
||||||
|
|
@ -116,7 +116,7 @@ def test_registry_propagates_dedup_key_to_tool_metadata() -> None:
|
||||||
the constructed tool's ``metadata`` so :class:`DedupHITLToolCallsMiddleware`
|
the constructed tool's ``metadata`` so :class:`DedupHITLToolCallsMiddleware`
|
||||||
can pick it up at agent build time.
|
can pick it up at agent build time.
|
||||||
"""
|
"""
|
||||||
from app.agents.new_chat.tools.registry import (
|
from app.agents.shared.tools.registry import (
|
||||||
BUILTIN_TOOLS,
|
BUILTIN_TOOLS,
|
||||||
wrap_dedup_key_by_arg_name,
|
wrap_dedup_key_by_arg_name,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ caused two production-painful behaviors:
|
||||||
read-only tool calls, raising ``RejectedError("ls")``.
|
read-only tool calls, raising ``RejectedError("ls")``.
|
||||||
* Mutating connector tools got *double* prompted — once via the
|
* Mutating connector tools got *double* prompted — once via the
|
||||||
middleware ``ask`` and again via the per-tool ``interrupt()`` in
|
middleware ``ask`` and again via the per-tool ``interrupt()`` in
|
||||||
``app.agents.new_chat.tools.hitl``.
|
``app.agents.shared.tools.hitl``.
|
||||||
|
|
||||||
These tests pin the layering so a refactor that drops the default
|
These tests pin the layering so a refactor that drops the default
|
||||||
ruleset fails loud.
|
ruleset fails loud.
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from app.agents.new_chat.tools.hitl import (
|
from app.agents.shared.tools.hitl import (
|
||||||
DEFAULT_AUTO_APPROVED_TOOLS,
|
DEFAULT_AUTO_APPROVED_TOOLS,
|
||||||
HITLResult,
|
HITLResult,
|
||||||
request_approval,
|
request_approval,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from langchain_core.messages import AIMessage
|
||||||
from app.agents.shared.middleware.tool_call_repair import (
|
from app.agents.shared.middleware.tool_call_repair import (
|
||||||
ToolCallNameRepairMiddleware,
|
ToolCallNameRepairMiddleware,
|
||||||
)
|
)
|
||||||
from app.agents.new_chat.tools.invalid_tool import INVALID_TOOL_NAME
|
from app.agents.shared.tools.invalid_tool import INVALID_TOOL_NAME
|
||||||
|
|
||||||
pytestmark = pytest.mark.unit
|
pytestmark = pytest.mark.unit
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from types import SimpleNamespace
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from app.agents.new_chat.tools.mcp_tools_cache import (
|
from app.agents.shared.tools.mcp_tools_cache import (
|
||||||
CachedMCPToolDef,
|
CachedMCPToolDef,
|
||||||
CachedMCPTools,
|
CachedMCPTools,
|
||||||
read_cached_tools,
|
read_cached_tools,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from unittest.mock import AsyncMock
|
||||||
import pypdf
|
import pypdf
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from app.agents.new_chat.tools import resume as resume_tool
|
from app.agents.shared.tools import resume as resume_tool
|
||||||
|
|
||||||
pytestmark = pytest.mark.unit
|
pytestmark = pytest.mark.unit
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ async def test_global_openrouter_image_gen_sets_api_base_when_config_empty():
|
||||||
async def test_generate_image_tool_global_sets_api_base_when_config_empty():
|
async def test_generate_image_tool_global_sets_api_base_when_config_empty():
|
||||||
"""Same defense at the agent tool entry point — both surfaces share
|
"""Same defense at the agent tool entry point — both surfaces share
|
||||||
the same OpenRouter config payloads."""
|
the same OpenRouter config payloads."""
|
||||||
from app.agents.new_chat.tools import generate_image as gi_module
|
from app.agents.shared.tools import generate_image as gi_module
|
||||||
|
|
||||||
cfg = {
|
cfg = {
|
||||||
"id": -20_001,
|
"id": -20_001,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue