mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-08-27 20:50:04 -04:00
fix(code): editable-install guidance for adding extras (#3610)
Correct misleading recovery hints for users on an editable `uv tool install -e …` setup who try to add an optional extra. The old text told them to `uv sync --extra <name>` from the source directory, which only touches the source `.venv` — never the tool environment that `dcode` actually runs from. Also fixes a Rich markup bug where the literal `[quickjs]` in the hint was parsed as a style tag and stripped from output.
This commit is contained in:
@@ -3180,6 +3180,7 @@ class DeepAgentsApp(App):
|
||||
)
|
||||
from deepagents_code.update_check import (
|
||||
create_update_log_path,
|
||||
editable_extra_hint,
|
||||
install_extra_command,
|
||||
is_valid_extra_name,
|
||||
perform_install_extra,
|
||||
@@ -3203,9 +3204,8 @@ class DeepAgentsApp(App):
|
||||
if await asyncio.to_thread(_is_editable_install):
|
||||
await self._mount_message(
|
||||
AppMessage(
|
||||
"Cannot install extras on editable installs. "
|
||||
f"Run `uv sync --extra {extra}` from the "
|
||||
"deepagents-code source directory instead.",
|
||||
"Editable install detected — cannot install extras.\n"
|
||||
+ editable_extra_hint(extra),
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
@@ -290,10 +290,20 @@ def verify_interpreter_deps() -> None:
|
||||
found = False
|
||||
|
||||
if not found:
|
||||
msg = (
|
||||
"Missing dependencies for --interpreter. "
|
||||
"Install with: dcode --install quickjs"
|
||||
)
|
||||
from deepagents_code.config import _is_editable_install
|
||||
|
||||
if _is_editable_install():
|
||||
from deepagents_code.update_check import editable_extra_hint
|
||||
|
||||
msg = (
|
||||
"Missing dependencies for --interpreter. Editable install "
|
||||
f"detected — {editable_extra_hint('quickjs')}"
|
||||
)
|
||||
else:
|
||||
msg = (
|
||||
"Missing dependencies for --interpreter. "
|
||||
"Install with: dcode --install quickjs"
|
||||
)
|
||||
raise ImportError(msg)
|
||||
|
||||
|
||||
|
||||
@@ -1988,6 +1988,7 @@ def cli_main() -> None:
|
||||
from deepagents_code.extras_info import KNOWN_EXTRAS
|
||||
from deepagents_code.update_check import (
|
||||
create_update_log_path,
|
||||
editable_extra_hint,
|
||||
install_extra_command,
|
||||
is_valid_extra_name,
|
||||
perform_install_extra,
|
||||
@@ -2011,9 +2012,9 @@ def cli_main() -> None:
|
||||
if _is_editable_install():
|
||||
console.print(
|
||||
"[bold yellow]Warning:[/bold yellow] "
|
||||
"--install is not supported on editable installs. "
|
||||
f"Run [cyan]uv sync --extra {extra}[/cyan] from the "
|
||||
"deepagents-code source directory instead."
|
||||
"--install is not supported on editable installs.\n"
|
||||
+ escape(editable_extra_hint(extra)),
|
||||
highlight=False,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@@ -972,6 +972,21 @@ def install_extra_command(extra: str) -> str:
|
||||
return f"uv tool install -U 'deepagents-code[{extra}]'"
|
||||
|
||||
|
||||
def editable_extra_hint(extra: str) -> str:
|
||||
"""Return the canonical action hint for editable installs missing an extra.
|
||||
|
||||
Shared by every site that detects an editable install and points the user
|
||||
at the correct `uv tool install --editable` invocation, so wording stays
|
||||
consistent and the literal `[<extra>]` bracket fragment is centrally
|
||||
defined (callers that print through Rich markup must still escape it).
|
||||
"""
|
||||
return (
|
||||
"Rerun your `uv tool install --editable` command with "
|
||||
f"`--with 'deepagents-code[{extra}]'` added so the extra is "
|
||||
"resolved against the editable source."
|
||||
)
|
||||
|
||||
|
||||
async def perform_install_extra(
|
||||
extra: str,
|
||||
*,
|
||||
@@ -981,9 +996,9 @@ async def perform_install_extra(
|
||||
"""Add `extra` to the installed dcode tool environment.
|
||||
|
||||
Runs `uv tool install -U 'deepagents-code[<extra>]'`. Editable installs
|
||||
are refused — the caller should instead `uv sync --extra <extra>` from
|
||||
the package directory, which we cannot do without knowing the source
|
||||
location.
|
||||
are refused — the caller should rerun their `uv tool install --editable`
|
||||
command with `--with 'deepagents-code[<extra>]'` added so the extra is
|
||||
resolved against the editable source.
|
||||
|
||||
Args:
|
||||
extra: The extra name to install. Must satisfy `is_valid_extra_name`;
|
||||
@@ -1004,9 +1019,8 @@ async def perform_install_extra(
|
||||
method = detect_install_method()
|
||||
if method == "unknown":
|
||||
return False, (
|
||||
"Editable install detected — cannot add extras automatically. "
|
||||
f"Run `uv sync --extra {extra}` from the deepagents-code source "
|
||||
"directory instead."
|
||||
"Editable install detected — cannot add extras automatically.\n"
|
||||
+ editable_extra_hint(extra)
|
||||
)
|
||||
if method == "brew":
|
||||
# Homebrew formula doesn't expose extras; uv tool install is the
|
||||
|
||||
@@ -269,16 +269,35 @@ def test_extras_categories_are_disjoint() -> None:
|
||||
assert not overlap, f"Extras classified twice in {label}: {sorted(overlap)}"
|
||||
|
||||
|
||||
def test_verify_interpreter_deps_raises_when_module_missing() -> None:
|
||||
# `verify_interpreter_deps` does a lazy `from deepagents_code.config import
|
||||
# _is_editable_install` each call, so the symbol is resolved against
|
||||
# `deepagents_code.config` at call time. Patch the source module — patching
|
||||
# `deepagents_code.extras_info._is_editable_install` would not work (it isn't
|
||||
# bound there as a module-level attribute).
|
||||
def test_verify_interpreter_deps_raises_with_dcode_hint_for_tool_install() -> None:
|
||||
with (
|
||||
patch(
|
||||
"deepagents_code.extras_info.importlib.util.find_spec", return_value=None
|
||||
),
|
||||
patch("deepagents_code.config._is_editable_install", return_value=False),
|
||||
pytest.raises(ImportError, match="dcode --install quickjs"),
|
||||
):
|
||||
verify_interpreter_deps()
|
||||
|
||||
|
||||
def test_verify_interpreter_deps_raises_with_uv_hint_for_editable_install() -> None:
|
||||
with (
|
||||
patch(
|
||||
"deepagents_code.extras_info.importlib.util.find_spec", return_value=None
|
||||
),
|
||||
patch("deepagents_code.config._is_editable_install", return_value=True),
|
||||
pytest.raises(
|
||||
ImportError, match=r"uv tool install --editable.*deepagents-code\[quickjs\]"
|
||||
),
|
||||
):
|
||||
verify_interpreter_deps()
|
||||
|
||||
|
||||
def test_verify_interpreter_deps_passes_when_module_present() -> None:
|
||||
fake_spec = MagicMock()
|
||||
with patch(
|
||||
|
||||
@@ -236,7 +236,4 @@ async def test_install_slash_editable_install_refuses() -> None:
|
||||
await pilot.pause()
|
||||
perform_mock.assert_not_awaited()
|
||||
app_msgs = [m for m in app.query(AppMessage) if not m._is_markdown]
|
||||
assert any(
|
||||
"Cannot install extras on editable installs" in str(m._content)
|
||||
for m in app_msgs
|
||||
)
|
||||
assert any("Editable install detected" in str(m._content) for m in app_msgs)
|
||||
|
||||
@@ -21,6 +21,7 @@ from deepagents_code.update_check import (
|
||||
clear_update_notified,
|
||||
create_update_log_path,
|
||||
detect_install_method,
|
||||
editable_extra_hint,
|
||||
format_age_suffix,
|
||||
format_installed_age_suffix,
|
||||
format_release_age,
|
||||
@@ -906,6 +907,21 @@ class TestInstallExtraCommand:
|
||||
install_extra_command("quickjs']; touch /tmp/pwned; '")
|
||||
|
||||
|
||||
class TestEditableExtraHint:
|
||||
"""`editable_extra_hint` is the shared editable-install action hint."""
|
||||
|
||||
def test_contains_uv_command_and_bracketed_extra(self) -> None:
|
||||
hint = editable_extra_hint("quickjs")
|
||||
assert "uv tool install --editable" in hint
|
||||
assert "--with 'deepagents-code[quickjs]'" in hint
|
||||
|
||||
def test_extra_is_interpolated_into_brackets(self) -> None:
|
||||
# The bracket fragment is load-bearing — Rich-markup call sites
|
||||
# must `escape()` this output, so the bracketed extra must always
|
||||
# be present in the hint (callers rely on this contract).
|
||||
assert "[fireworks]" in editable_extra_hint("fireworks")
|
||||
|
||||
|
||||
class TestInstallPackageCommand:
|
||||
"""`install_package_command` builds a uv tool package install string."""
|
||||
|
||||
@@ -938,7 +954,8 @@ class TestPerformInstallExtra:
|
||||
success, output = await perform_install_extra("quickjs")
|
||||
assert success is False
|
||||
assert "Editable install" in output
|
||||
assert "uv sync --extra quickjs" in output
|
||||
assert "uv tool install --editable" in output
|
||||
assert "--with 'deepagents-code[quickjs]'" in output
|
||||
|
||||
async def test_brew_install_refuses(self) -> None:
|
||||
"""Homebrew formula doesn't expose extras."""
|
||||
|
||||
Reference in New Issue
Block a user