feat(deps): upgrade deepagents to 0.7.0 with todos restore and delete gating- #395

- Introduced TodoListMiddleware to the middleware stack for better task management.
- Updated HITL interrupt configuration to include 'delete' operations requiring approval.
- Implemented error handling for delete operations in read-only and memory backends.
- Enhanced approval prompt formatting to display file paths for delete actions.
- Added tests to ensure delete operations are correctly blocked or prompted for approval.
- Updated dependencies to use deepagents 0.7.0 and langchain 1.5.3 for improved functionality.
This commit is contained in:
Xi Zhang
2026-07-30 10:41:12 +01:00
committed by GitHub
parent 4ddf7ebe52
commit f81a8b086e
19 changed files with 346 additions and 62 deletions
+20 -19
View File
@@ -22,7 +22,11 @@ import os
from pathlib import Path
from typing import TYPE_CHECKING
from langchain.agents.middleware import AgentMiddleware, HumanInTheLoopMiddleware
from langchain.agents.middleware import (
AgentMiddleware,
HumanInTheLoopMiddleware,
TodoListMiddleware,
)
from . import paths as _paths_mod
from .config import (
@@ -52,6 +56,15 @@ SUBAGENTS_CONFIG = Path(__file__).parent / "subagents"
SKILLS_DIR = str(Path(__file__).parent / "skills")
DEFAULT_SKILL_SOURCES = ("/skills/",)
# Tools requiring human approval on attended agents (deepagents 0.7.0 ships a
# recursive `delete` FS tool that would otherwise bypass the execute blocklist).
HITL_INTERRUPT_ON: dict[str, bool] = {
"execute": True,
"run_in_background": True,
"schedule_task": True,
"delete": True,
}
# =============================================================================
# Lazy state — initialized on first use, not at import time
# =============================================================================
@@ -359,6 +372,7 @@ def _inject_subagent_middleware(
create_context_editing_middleware(chat_model),
create_runtime_context_middleware(),
ToolErrorHandlerMiddleware(),
TodoListMiddleware(),
ContextOverflowMapperMiddleware(),
]
if memory_controls.memory_enabled:
@@ -790,6 +804,9 @@ def _get_default_middleware(
ModelFallbackMiddleware(events=events),
ContextOverflowMapperMiddleware(),
ToolErrorHandlerMiddleware(),
# deepagents 0.7.0 dropped TodoListMiddleware from its defaults;
# EXPERIMENT_WORKFLOW planning and the todo UI pipeline require it.
TodoListMiddleware(),
*create_tool_selector_middleware(
model=tool_selector_model,
events=events,
@@ -876,15 +893,7 @@ def _get_default_agent():
# breaks parallel execute calls (multi-pending-interrupt LangGraph
# error). See PR #202.
if not cfg.auto_approve:
mw.append(
HumanInTheLoopMiddleware(
interrupt_on={
"execute": True,
"run_in_background": True,
"schedule_task": True,
}
)
)
mw.append(HumanInTheLoopMiddleware(interrupt_on=dict(HITL_INTERRUPT_ON)))
if os.environ.get("EVOSCIENTIST_DEPLOY_MODE", "").lower() == "stripped":
kwargs = _build_base_kwargs(
@@ -1042,15 +1051,7 @@ def create_cli_agent(
# would propagate it to every subagent, breaking parallel execute calls
# (multi-pending-interrupt LangGraph error).
if not cfg.auto_approve:
mw.append(
HumanInTheLoopMiddleware(
interrupt_on={
"execute": True,
"run_in_background": True,
"schedule_task": True,
}
)
)
mw.append(HumanInTheLoopMiddleware(interrupt_on=dict(HITL_INTERRUPT_ON)))
# Re-load MCP tools from current config (picks up /mcp add changes)
kwargs = load_mcp_and_build_kwargs(
+19
View File
@@ -15,6 +15,7 @@ from pathlib import Path
from deepagents.backends import FilesystemBackend, LocalShellBackend
from deepagents.backends.protocol import (
BackendProtocol,
DeleteResult,
EditResult,
ExecuteResponse,
FileDownloadResponse,
@@ -904,6 +905,11 @@ class ReadOnlyFilesystemBackend(FilesystemBackend):
for file_path, _ in files
]
def delete(self, file_path: str) -> DeleteResult:
return DeleteResult(
error="This directory is read-only. Delete operations are not permitted here."
)
class MemoryFilesystemBackend(FilesystemBackend):
"""Filesystem backend for memory files with structured-write enforcement.
@@ -920,6 +926,10 @@ class MemoryFilesystemBackend(FilesystemBackend):
"Raw edits under /memories are limited to existing "
"/memories/profile/... files. Use memory tools for observations."
)
_RAW_DELETE_ERROR = (
"Deletes under /memories are blocked. Manage memory files through "
"memory tools instead."
)
@staticmethod
def _is_profile_path(file_path: str) -> bool:
@@ -946,6 +956,9 @@ class MemoryFilesystemBackend(FilesystemBackend):
for file_path, _ in files
]
def delete(self, file_path: str) -> DeleteResult:
return DeleteResult(error=self._RAW_DELETE_ERROR)
def build_memory_agent_backend(
*,
@@ -1552,6 +1565,12 @@ class AutoskillProposalSandboxBackend(CustomSandboxBackend):
for file_path, _ in files
]
def delete(self, file_path: str) -> DeleteResult:
return DeleteResult(
error="Deletes are blocked for AutoSkills. Manage proposal files "
"under /autoskill-proposals/ instead."
)
def execute(self, command: str, *, timeout: int | None = None) -> ExecuteResponse:
return super().execute(
self._rewrite_autoskill_mount(command),
+11 -2
View File
@@ -102,7 +102,10 @@ def format_approval_prompt(
name = req.get("name", "")
args = req.get("args", {})
if isinstance(args, dict):
command = args.get("command", args.get("path", ""))
# deepagents 0.7.0's `delete` tool uses `file_path`, not
# `command`/`path` — without this fallback the prompt shows
# only "delete" with no target.
command = args.get("command", args.get("path", args.get("file_path", "")))
else:
command = ""
if command:
@@ -217,7 +220,11 @@ def config_auto_approve(action_requests: list[dict]) -> bool:
return True
try:
from ..config.settings import HITL_SHELL_TOOLS, load_config
from ..config.settings import (
HITL_ALWAYS_PROMPT_TOOLS,
HITL_SHELL_TOOLS,
load_config,
)
cfg = load_config()
except Exception:
@@ -234,6 +241,8 @@ def config_auto_approve(action_requests: list[dict]) -> bool:
for req in action_requests:
name = req.get("name", "")
if name in HITL_ALWAYS_PROMPT_TOOLS:
return False
if name not in HITL_SHELL_TOOLS:
continue
args = req.get("args", {})
+10 -4
View File
@@ -101,6 +101,15 @@ class ApprovalWidget(Widget):
self._selected = 0
self._option_widgets: list[Static] = []
@staticmethod
def _extract_command(args: dict) -> str:
"""Pull the display-worthy target out of a tool's args dict.
Checks `command`/`path` first, then deepagents 0.7.0's `delete`
tool key `file_path` — without it, `delete` shows no target.
"""
return args.get("command", args.get("path", args.get("file_path", "")))
def compose(self) -> ComposeResult:
self._option_widgets = []
count = len(self._action_requests)
@@ -115,10 +124,7 @@ class ApprovalWidget(Widget):
for req in self._action_requests:
name = req.get("name", "")
args = req.get("args", {})
if isinstance(args, dict):
command = args.get("command", args.get("path", ""))
else:
command = ""
command = self._extract_command(args) if isinstance(args, dict) else ""
if command:
cmd_str = str(command)
if len(cmd_str) > _COMMAND_TRUNCATE_LENGTH:
+5 -1
View File
@@ -21,10 +21,14 @@ from dotenv import dotenv_values, find_dotenv
# Tools that run shell commands and need manual HITL approval (subject to
# shell_allow_list). Single source of truth for every interrupt consumer
# (stream/display.py, channels/consumer.py) — keep aligned with the agent's
# (stream/display.py, channels/interaction.py) — keep aligned with the agent's
# `interrupt_on` set in EvoScientist.py.
HITL_SHELL_TOOLS = ("execute", "run_in_background")
# Armed non-shell destructive tools must always prompt — no allow-list carve-outs
# (their args carry paths, not commands). Keep aligned with HITL_INTERRUPT_ON.
HITL_ALWAYS_PROMPT_TOOLS = ("delete",)
class MemoryObservationTarget(StrEnum):
"""Runtime locations that can receive `record_observation`."""
+1
View File
@@ -18,6 +18,7 @@ from ... import paths as _paths
MEMORY_AGENT_RECURSION_LIMIT = 100
MEMORY_MAINTENANCE_EXCLUDED_TOOLS = frozenset(
{
"delete",
"edit_file",
"execute",
"task",
+1 -1
View File
@@ -21,7 +21,7 @@ from ._factory import (
resolve_memory_agent_paths,
)
_AUTOSKILLS_EXCLUDED_TOOLS = frozenset({"task", "write_todos"})
_AUTOSKILLS_EXCLUDED_TOOLS = frozenset({"delete", "task", "write_todos"})
def _autoskills_system_prompt() -> str:
+1 -1
View File
@@ -34,7 +34,7 @@ from ._factory import (
logger = logging.getLogger(__name__)
_MEMORY_WORKER_EXCLUDED_TOOLS = frozenset(
{"execute", "task", "write_file", "write_todos"}
{"delete", "execute", "task", "write_file", "write_todos"}
)
+9 -1
View File
@@ -1185,7 +1185,11 @@ def _resolve_hitl_approval(
return [{"type": "approve"} for _ in action_requests]
# Config-level auto-approve
from ..config.settings import HITL_SHELL_TOOLS, load_config
from ..config.settings import (
HITL_ALWAYS_PROMPT_TOOLS,
HITL_SHELL_TOOLS,
load_config,
)
cfg = load_config()
if cfg.auto_approve:
@@ -1203,6 +1207,10 @@ def _resolve_hitl_approval(
name = req.get("name", "")
args = req.get("args", {})
if name in HITL_ALWAYS_PROMPT_TOOLS:
needs_prompt = True
break
if name not in HITL_SHELL_TOOLS:
continue # Only shell-running tools need manual approval
+3 -3
View File
@@ -16,13 +16,13 @@ classifiers = [
"Programming Language :: Python :: 3",
]
dependencies = [
"deepagents[quickjs]~=0.6.12",
"deepagents[quickjs]~=0.7.0",
"langchain>=1.3",
"langchain-anthropic>=1.4",
"langchain-anthropic>=1.5",
"langchain-openai>=1.2",
"langchain-deepseek>=1.1",
"langchain-nvidia-ai-endpoints>=1.2",
"langchain-google-genai>=4.2",
"langchain-google-genai>=4.3",
"langchain-ollama>=1.1",
"langchain-openrouter>=0.2.5",
# 0.11.0+ SSE regressions: teardown noise + mid-turn ReadTimeout
+90
View File
@@ -11,6 +11,7 @@ import pytest
from EvoScientist import backends, paths
from EvoScientist.backends import (
AutoskillProposalSandboxBackend,
CustomSandboxBackend,
MemoryFilesystemBackend,
MergedSkillsBackend,
@@ -959,6 +960,52 @@ class TestMemoryFilesystemBackend:
assert not (workspace / "created.txt").exists()
# === delete blocking (deepagents 0.7.0 recursive delete tool) ===
class TestDeleteBlocked:
"""deepagents 0.7.0 adds a recursive delete tool; guarded backends must refuse it."""
def test_readonly_backend_blocks_delete(self, tmp_path):
(tmp_path / "f.txt").write_text("x")
backend = ReadOnlyFilesystemBackend(root_dir=str(tmp_path), virtual_mode=True)
result = backend.delete("/f.txt")
assert result.error is not None
assert (tmp_path / "f.txt").exists()
async def test_readonly_backend_blocks_adelete(self, tmp_path):
(tmp_path / "f.txt").write_text("x")
backend = ReadOnlyFilesystemBackend(root_dir=str(tmp_path), virtual_mode=True)
result = await backend.adelete("/f.txt")
assert result.error is not None
assert (tmp_path / "f.txt").exists()
def test_memory_backend_blocks_delete_everywhere(self, tmp_path):
profile = tmp_path / "profile"
profile.mkdir()
(profile / "USER_PROFILE.md").write_text("x")
backend = MemoryFilesystemBackend(root_dir=str(tmp_path), virtual_mode=True)
result = backend.delete("/profile/USER_PROFILE.md")
assert result.error is not None
assert (profile / "USER_PROFILE.md").exists()
def test_autoskill_backend_blocks_delete(self, tmp_path):
(tmp_path / "f.txt").write_text("x")
backend = AutoskillProposalSandboxBackend(
root_dir=str(tmp_path), virtual_mode=True
)
result = backend.delete("/f.txt")
assert result.error is not None
assert (tmp_path / "f.txt").exists()
def test_sandbox_backend_delete_enabled_by_default(self, tmp_path):
(tmp_path / "f.txt").write_text("x")
backend = CustomSandboxBackend(root_dir=str(tmp_path), virtual_mode=True)
result = backend.delete("/f.txt")
assert result.error is None
assert not (tmp_path / "f.txt").exists()
# === CustomSandboxBackend._resolve_path ===
@@ -1918,3 +1965,46 @@ class TestPlatformQuote:
backends._platform_quote(r"C:\path\%TEMP%\file.py")
== r"C:\path\%TEMP%\file.py"
)
def test_memory_maintenance_excludes_delete_tool():
from EvoScientist.memory.agents._factory import MEMORY_MAINTENANCE_EXCLUDED_TOOLS
assert "delete" in MEMORY_MAINTENANCE_EXCLUDED_TOOLS
def test_autoskill_composite_route_blocks_or_excludes_delete():
"""Codex finding: /autoskill-proposals/ routes to a plain FilesystemBackend whose
delete works; the agent-level tool exclusion is the guard that must cover it."""
from EvoScientist.memory.agents.autoskills import _AUTOSKILLS_EXCLUDED_TOOLS
assert "delete" in _AUTOSKILLS_EXCLUDED_TOOLS
def test_autoskill_proposals_route_delete_is_not_backend_blocked(tmp_path):
"""Documents WHY the tool exclusion above is the enforcement layer: the raw
composite backend's /autoskill-proposals/ route has no backend-level delete
guard (unlike /memories/ and the default proposal-root sandbox), so a bare
`delete("/autoskill-proposals/...")` call still succeeds at the backend level."""
from EvoScientist.backends import build_autoskill_agent_backend
memory_dir = tmp_path / "memories"
proposals_dir = tmp_path / "proposals"
memory_dir.mkdir()
proposals_dir.mkdir()
(proposals_dir / "some-skill").mkdir()
(proposals_dir / "some-skill" / "SKILL.md").write_text("x", encoding="utf-8")
backend = build_autoskill_agent_backend(
memory_dir=memory_dir, proposals_dir=proposals_dir
)
result = backend.delete("/autoskill-proposals/some-skill")
assert result.error is None
assert not (proposals_dir / "some-skill").exists()
def test_memory_worker_excludes_delete_tool():
from EvoScientist.memory.agents.memory_worker import _MEMORY_WORKER_EXCLUDED_TOOLS
assert "delete" in _MEMORY_WORKER_EXCLUDED_TOOLS
+47
View File
@@ -329,6 +329,37 @@ class TestResolveHitlApproval:
finally:
disp._session_auto_approve = original
def test_delete_only_request_prompts_not_auto_approved(self):
"""delete is armed in HITL_INTERRUPT_ON but has no `command` arg, so the
shell_allow_list carve-out must never silently clear it (C1)."""
import EvoScientist.stream.display as disp
from EvoScientist.stream.display import _resolve_hitl_approval
original = disp._session_auto_approve
try:
disp._session_auto_approve = False
mock_cfg = MagicMock()
mock_cfg.auto_approve = False
mock_cfg.shell_allow_list = ""
with patch(
"EvoScientist.config.settings.load_config", return_value=mock_cfg
):
with patch(
"EvoScientist.stream.display._prompt_hitl_approval"
) as mock_prompt:
mock_prompt.return_value = [{"type": "approve"}]
result = _resolve_hitl_approval(
{
"action_requests": [
{"name": "delete", "args": {"file_path": "/f.txt"}}
],
}
)
assert result == [{"type": "approve"}]
mock_prompt.assert_called_once() # must be prompted, not auto-approved
finally:
disp._session_auto_approve = original
# =============================================================================
# Config fields
@@ -556,6 +587,22 @@ class TestConsumerHitlHelpers:
)
assert result is True
def test_should_auto_approve_delete_not_cleared(self):
"""delete has no `command` arg, so shell_allow_list must never clear it
the way it clears execute (C1)."""
from EvoScientist.channels.interaction import config_auto_approve
mock_cfg = MagicMock()
mock_cfg.auto_approve = False
mock_cfg.shell_allow_list = "ls,python"
with patch("EvoScientist.config.settings.load_config", return_value=mock_cfg):
result = config_auto_approve(
[
{"name": "delete", "args": {"file_path": "/f.txt"}},
]
)
assert result is False
# =============================================================================
# Channel reply-interception mechanism (channel.py PendingReplyRegistry)
+12
View File
@@ -0,0 +1,12 @@
"""HITL interrupt policy: which tools require approval on attended agents."""
def test_hitl_interrupt_on_arms_expected_tools():
from EvoScientist.EvoScientist import HITL_INTERRUPT_ON
assert HITL_INTERRUPT_ON == {
"execute": True,
"run_in_background": True,
"schedule_task": True,
"delete": True,
}
+9
View File
@@ -252,6 +252,15 @@ class TestApprovalPromptFormat:
got = I.format_approval_prompt([{"name": "ask_user", "args": {}}])
assert "ask_user" in got
def test_delete_shows_file_path(self):
# deepagents 0.7.0's `delete` tool uses `file_path`, not `command`/
# `path` — the prompt must still show the target, not just the name.
got = I.format_approval_prompt(
[{"name": "delete", "args": {"file_path": "/results/run-3"}}]
)
assert "delete" in got
assert "/results/run-3" in got
def test_metadata_no_buttons(self):
assert I.approval_prompt_metadata({"k": "v"}, with_buttons=False) == {"k": "v"}
+26
View File
@@ -0,0 +1,26 @@
"""deepagents 0.7.0 merges caller middleware into its default stack by `.name`:
a name match silently REPLACES the built-in. None of EvoScientist's middleware
may collide unintentionally. TodoListMiddleware is deliberately absent from the
forbidden set: we pass it on purpose and replacing a profile-added instance
(e.g. the Codex harness profile's) with our identical one is desired dedup.
"""
DEEPAGENTS_BASE_STACK_NAMES = {
"SkillsMiddleware",
"FilesystemMiddleware",
"SubAgentMiddleware",
"SummarizationMiddleware",
"PatchToolCallsMiddleware",
"AsyncSubAgentMiddleware",
"AnthropicPromptCachingMiddleware",
}
def test_no_name_collision_with_deepagents_base_stack():
from EvoScientist.EvoScientist import _get_default_middleware
ours = {m.name for m in _get_default_middleware()}
assert not ours & DEEPAGENTS_BASE_STACK_NAMES
ours_async = {m.name for m in _get_default_middleware(for_async_subagent=True)}
assert not ours_async & DEEPAGENTS_BASE_STACK_NAMES
+2 -9
View File
@@ -683,12 +683,5 @@ class TestDelegationPromptSummarize:
"""The upstream TASK_TOOL_DESCRIPTION already instructs the LLM to summarize."""
from deepagents.middleware.subagents import TASK_TOOL_DESCRIPTION
assert "not visible to the user" in TASK_TOOL_DESCRIPTION
assert "summary of the result" in TASK_TOOL_DESCRIPTION
def test_framework_task_system_prompt_contains_reconcile_step(self):
"""The upstream TASK_SYSTEM_PROMPT includes a reconcile/synthesize step."""
from deepagents.middleware.subagents import TASK_SYSTEM_PROMPT
assert "Reconcile" in TASK_SYSTEM_PROMPT
assert "synthesize" in TASK_SYSTEM_PROMPT.lower()
assert "not shown to the user" in TASK_TOOL_DESCRIPTION
assert "relay a summary yourself" in TASK_TOOL_DESCRIPTION
+27
View File
@@ -0,0 +1,27 @@
"""deepagents 0.7.0 made TodoListMiddleware opt-in; EvoScientist opts back in everywhere."""
def _names(middleware_list):
return {m.name for m in middleware_list}
def test_default_middleware_includes_todos():
from EvoScientist.EvoScientist import _get_default_middleware
assert "TodoListMiddleware" in _names(_get_default_middleware())
def test_async_subagent_middleware_includes_todos():
from EvoScientist.EvoScientist import _get_default_middleware
assert "TodoListMiddleware" in _names(
_get_default_middleware(for_async_subagent=True)
)
def test_injected_subagent_middleware_includes_todos(tmp_path):
from EvoScientist.EvoScientist import _inject_subagent_middleware
subs = [{"name": "research-agent"}]
_inject_subagent_middleware(subs, workspace_dir=str(tmp_path))
assert "TodoListMiddleware" in _names(subs[0]["middleware"])
+32
View File
@@ -285,6 +285,38 @@ class TestToolCallWidget(unittest.TestCase):
assert w._should_collapse() is True
@unittest.skipUnless(_has_textual, "textual not installed")
class TestApprovalWidgetCommandExtraction(unittest.TestCase):
"""ApprovalWidget's compact-line command extraction.
``compose()`` itself needs a mounted App (it opens a Container context
manager), so — per the widget's existing style of unit-testing its
private helpers directly (see TestToolCallWidget) — these exercise the
extraction helper `compose()` calls rather than the full render.
"""
def test_extract_command_prefers_command(self):
from EvoScientist.cli.widgets.approval_widget import ApprovalWidget
assert ApprovalWidget._extract_command({"command": "ls", "path": "/x"}) == "ls"
def test_extract_command_falls_back_to_path(self):
from EvoScientist.cli.widgets.approval_widget import ApprovalWidget
assert ApprovalWidget._extract_command({"path": "/out.txt"}) == "/out.txt"
def test_extract_command_falls_back_to_file_path(self):
# deepagents 0.7.0's `delete` tool uses `file_path`, not `command`/
# `path` — without this fallback the approval prompt shows only the
# tool name and hides the deletion target.
from EvoScientist.cli.widgets.approval_widget import ApprovalWidget
assert (
ApprovalWidget._extract_command({"file_path": "/results/run-3"})
== "/results/run-3"
)
@unittest.skipUnless(_has_textual, "textual not installed")
class TestSubAgentWidget(unittest.TestCase):
"""SubAgentWidget construction and name display."""
Generated
+21 -21
View File
@@ -186,7 +186,7 @@ wheels = [
[[package]]
name = "anthropic"
version = "0.117.0"
version = "0.120.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -198,9 +198,9 @@ dependencies = [
{ name = "sniffio" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/41/0d/8f71d535edb0d438f023bd825fb65f67c14fa88a2bd6b75f292a58a63de4/anthropic-0.117.0.tar.gz", hash = "sha256:98107f2b76439641e0ae2a1754087534b8f178dbab99d6eb1bc4b7bc8c744496", size = 989933, upload-time = "2026-07-16T19:36:13.07Z" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/10/4ca013cb166f226bd89e0aeb0fcaff94f45ddf716d4925ce89475d3c587b/anthropic-0.120.2.tar.gz", hash = "sha256:9722efc10c27a30a69f5338ddacdb35bc6a64297a4e4ba729bf83af873d5fb3a", size = 1008421, upload-time = "2026-07-28T17:38:26.986Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/4c/917d21d6619a4475cdafc6d13a69fdb3b901ddac57e76caca5a25c117b6d/anthropic-0.117.0-py3-none-any.whl", hash = "sha256:451a0a6905f11dff7663d13e4ee5dbf909eb8942b1d049803c7b937a13ac47ec", size = 998327, upload-time = "2026-07-16T19:36:11.225Z" },
{ url = "https://files.pythonhosted.org/packages/63/af/0f5db57b9397a0f3b7fc204cbef143401a7cadaf982330f97f1ce3d39f34/anthropic-0.120.2-py3-none-any.whl", hash = "sha256:0f0bc2b381dc0eb41c8d886b815d79c2041cd2374f83aed36f574b6dc9c579c1", size = 1022851, upload-time = "2026-07-28T17:38:25.466Z" },
]
[[package]]
@@ -859,7 +859,7 @@ wheels = [
[[package]]
name = "deepagents"
version = "0.6.12"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langchain" },
@@ -869,9 +869,9 @@ dependencies = [
{ name = "langsmith" },
{ name = "wcmatch" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e0/db/a6acdc72a9e90c3f07ed10de35c951734a02d4facb693bb59684ad368801/deepagents-0.6.12.tar.gz", hash = "sha256:1f281c0bc5a63132f62e2ee345c1dc593b23188da6e23016401f6879fbe54b5f", size = 211364, upload-time = "2026-06-25T17:26:52.775Z" }
sdist = { url = "https://files.pythonhosted.org/packages/82/6b/f61d30d45e25e8dc720b4928da029162329e8f22173bbbe190083e19a75e/deepagents-0.7.0.tar.gz", hash = "sha256:36c49bced5de9ef0deb1e9c05a449adf931706e25ba5f99449d7436eccbc694b", size = 263583, upload-time = "2026-07-29T16:27:29.241Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/98/49/af7219b3c13520fee047bb807cfaefba17f8e4584c551d946773589a4f08/deepagents-0.6.12-py3-none-any.whl", hash = "sha256:28b8fa0119ca0a689e3e18e288c4634e4046062acfc87a1cb34289d3af3a1c88", size = 236120, upload-time = "2026-06-25T17:26:51.736Z" },
{ url = "https://files.pythonhosted.org/packages/57/0a/ee3a0518a90bae0ee3f48e23061fe185804d0d3476cb75f21ab834f4698c/deepagents-0.7.0-py3-none-any.whl", hash = "sha256:c0ceb3c12f44638cdb81ea95652d6d6fa8ec76a2372ae8016aa2dd9a493762b1", size = 289723, upload-time = "2026-07-29T16:27:28.178Z" },
]
[package.optional-dependencies]
@@ -1060,16 +1060,16 @@ requires-dist = [
{ name = "certifi", marker = "extra == 'wechat'", specifier = ">=2024.0" },
{ name = "cryptography", marker = "extra == 'all-channels'", specifier = ">=41.0" },
{ name = "cryptography", marker = "extra == 'qq'", specifier = ">=41.0" },
{ name = "deepagents", extras = ["quickjs"], specifier = "~=0.6.12" },
{ name = "deepagents", extras = ["quickjs"], specifier = "~=0.7.0" },
{ name = "discord-py", marker = "extra == 'all-channels'", specifier = ">=2.3" },
{ name = "discord-py", marker = "extra == 'discord'", specifier = ">=2.3" },
{ name = "faster-whisper", marker = "extra == 'stt'", specifier = ">=1.0" },
{ name = "filelock", specifier = ">=3.16" },
{ name = "httpx", specifier = ">=0.28" },
{ name = "langchain", specifier = ">=1.3" },
{ name = "langchain-anthropic", specifier = ">=1.4" },
{ name = "langchain-anthropic", specifier = ">=1.5" },
{ name = "langchain-deepseek", specifier = ">=1.1" },
{ name = "langchain-google-genai", specifier = ">=4.2" },
{ name = "langchain-google-genai", specifier = ">=4.3" },
{ name = "langchain-mcp-adapters", specifier = ">=0.2" },
{ name = "langchain-nvidia-ai-endpoints", specifier = ">=1.2" },
{ name = "langchain-ollama", specifier = ">=1.1" },
@@ -2038,21 +2038,21 @@ wheels = [
[[package]]
name = "langchain-anthropic"
version = "1.4.8"
version = "1.5.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anthropic" },
{ name = "langchain-core" },
{ name = "pydantic" },
]
sdist = { url = "https://files.pythonhosted.org/packages/98/22/40ab129b08329ca295b391aa1d48267692b42594757084c6918e22b655ac/langchain_anthropic-1.4.8.tar.gz", hash = "sha256:c76891b2044d56105ff13c106ed12650637b53bd598a4bdf15b4796eefa2a4ec", size = 708524, upload-time = "2026-06-26T21:28:46.916Z" }
sdist = { url = "https://files.pythonhosted.org/packages/70/5b/25ffc9cc66a8260db9e2da2836ee33b260dfbb874d1ea2f99d70023f2b1d/langchain_anthropic-1.5.3.tar.gz", hash = "sha256:48a7e4fc3856c42f9dcb4396ba112afcdd8dee21b70a402bc43283c970de3941", size = 713962, upload-time = "2026-07-28T17:02:55.903Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/14/746235c4da89d9bc6a608c5f489f628e03feb8f697195c146e452c8f23c8/langchain_anthropic-1.4.8-py3-none-any.whl", hash = "sha256:778e9301b6fd517824f76ec1776975ce8add97a1f6a36c50ae3c2f4b03a66f7f", size = 52366, upload-time = "2026-06-26T21:28:45.535Z" },
{ url = "https://files.pythonhosted.org/packages/2a/19/584ab8517718f48bb411d051be713ebd8b58351741f8a4ca23d6a305cb1b/langchain_anthropic-1.5.3-py3-none-any.whl", hash = "sha256:b1b72b7c9e4bf5c044660629ebbcb79cef18c140fdc80174750eb86de13ec8bc", size = 54034, upload-time = "2026-07-28T17:02:54.594Z" },
]
[[package]]
name = "langchain-core"
version = "1.4.9"
version = "1.5.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jsonpatch" },
@@ -2065,9 +2065,9 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "uuid-utils" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2a/b9/e937d0a90b26540bff07e7a7c64349f3b29c2dcc36257cd1cd3fdce17f2a/langchain_core-1.4.9.tar.gz", hash = "sha256:f8078901145bed0466755277500a5a22822a7b628808c4c0a28d4fc88895fcf2", size = 967294, upload-time = "2026-07-08T20:06:54.191Z" }
sdist = { url = "https://files.pythonhosted.org/packages/6e/58/3ad53096eee1e07728e8219ded30ae308b0f2b8b7b26b8ebb6371917c0f5/langchain_core-1.5.2.tar.gz", hash = "sha256:2d13ab35b42eec63d4669a483776b8cdd778ee764107149369fb369d84c08c41", size = 972322, upload-time = "2026-07-28T16:38:37.977Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/84/70/ade2fada52772798ef815b6352b59e71b116aa0c32c3aef5be3dc2cbed12/langchain_core-1.4.9-py3-none-any.whl", hash = "sha256:28e3909e2a10cc81504952d795ac0a9e014c0018121ef89d48dd396fa09ec624", size = 558293, upload-time = "2026-07-08T20:06:52.382Z" },
{ url = "https://files.pythonhosted.org/packages/ca/e4/024e402a65f5ee2eb6ae9667b5ffc5f08776cb4e712f5a11ee1997d56083/langchain_core-1.5.2-py3-none-any.whl", hash = "sha256:a687dd7c3b22c6c1294e1c1eeb61fb6f3a308e6015a1d75b576b3836ad5b5aed", size = 561643, upload-time = "2026-07-28T16:38:36.38Z" },
]
[[package]]
@@ -2085,7 +2085,7 @@ wheels = [
[[package]]
name = "langchain-google-genai"
version = "4.2.7"
version = "4.3.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "filetype" },
@@ -2093,9 +2093,9 @@ dependencies = [
{ name = "langchain-core" },
{ name = "pydantic" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1d/0c/bc60dabc362ca7c6ffe8c4bcc2f724c7e566b43eb230cee51419f88f784c/langchain_google_genai-4.2.7.tar.gz", hash = "sha256:03b1463ffe4d42435f43c7870467f2215f684bb46400d2543435d10157c80ac7", size = 281605, upload-time = "2026-07-06T13:51:58.724Z" }
sdist = { url = "https://files.pythonhosted.org/packages/79/2f/e03b63ad3a61fd1aa479bbc0f3df5d27abb8f9159d111cba96629df844ef/langchain_google_genai-4.3.2.tar.gz", hash = "sha256:6471769a4463fedb10d2d19a9b56c31de1cde505edf7fffd8cdbf98af8c1d7da", size = 286018, upload-time = "2026-07-27T16:27:39.214Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b2/f9/d73d1e712591723aaddb7a7b1e94978cd2320c29acfe0d26b6169a2f26f0/langchain_google_genai-4.2.7-py3-none-any.whl", hash = "sha256:0d9c388d0e6c629718fca6abb19c6fdca728a9a7873d0324c1ec821288b5b571", size = 70702, upload-time = "2026-07-06T13:51:57.499Z" },
{ url = "https://files.pythonhosted.org/packages/01/cb/4a2eb187b108a240d57cf8dcf67e818ca75365f769444bf5716e2823cd98/langchain_google_genai-4.3.2-py3-none-any.whl", hash = "sha256:f3b1c09b264612fd1735a9590987bfa0cccca0bc0111691543decb5a03b8667d", size = 72770, upload-time = "2026-07-27T16:27:38.052Z" },
]
[[package]]
@@ -2355,7 +2355,7 @@ wheels = [
[[package]]
name = "langsmith"
version = "0.10.6"
version = "0.10.12"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -2373,9 +2373,9 @@ dependencies = [
{ name = "xxhash" },
{ name = "zstandard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/93/7e/05db6baaed1383386afd34e5d8fbf0b6cff1e87ed6b5c98d444511af6490/langsmith-0.10.6.tar.gz", hash = "sha256:6ec5b26bb57ee41363c07eb2884f24d69910b8d6bc26cae1f9e367af15259f1b", size = 4729044, upload-time = "2026-07-17T17:14:02.897Z" }
sdist = { url = "https://files.pythonhosted.org/packages/a3/c3/7203f18c6ca0ff4c65087fe8694511fadc8a0037c37010e43d25b9ecacd8/langsmith-0.10.12.tar.gz", hash = "sha256:e27cace529a5c54546ecdc2d3fa7af6d8a84a5b9333e99b497f47cafffa16ef5", size = 4749918, upload-time = "2026-07-29T14:22:52.335Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/d5/2d8d84f5330aa773bae18644aaf7e3288b2f082e31640080784a7d908a24/langsmith-0.10.6-py3-none-any.whl", hash = "sha256:094285b755224f49fd396c3672b0f747294c7b8d9e8278a81d76b487b5cfdb56", size = 661257, upload-time = "2026-07-17T17:14:01.118Z" },
{ url = "https://files.pythonhosted.org/packages/1e/f8/de3b66a9b9212ed41541814d8e062909c009837e650a4c2e43425ee1b43c/langsmith-0.10.12-py3-none-any.whl", hash = "sha256:d280fff7a03ecf612c3fdb172bf716467cfb9c0b9d90661b3523c2d8ee1b9a6e", size = 681040, upload-time = "2026-07-29T14:22:50.372Z" },
]
[package.optional-dependencies]