mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-22 01:35:28 -04:00
feat(cli): show sandbox provider in header sub_title (#3295)
Closes #2304 --- Auto-populate `sub_title` with the active sandbox provider name (e.g. `"Sandbox: Daytona"`) so the optional header bar indicates which sandbox is in use. Display names use proper casing via a lookup dict, with a `.title()` fallback for unrecognised providers. An explicitly passed `sub_title` takes precedence over the auto-detected value. Co-authored-by: Mason Daugherty <mason@langchain.dev>
This commit is contained in:
@@ -888,6 +888,15 @@ _COMMAND_URLS: dict[str, str] = {
|
||||
}
|
||||
"""Slash-command to URL mapping for commands that just open a browser."""
|
||||
|
||||
_SANDBOX_DISPLAY_NAMES: dict[str, str] = {
|
||||
"agentcore": "AgentCore",
|
||||
"daytona": "Daytona",
|
||||
"langsmith": "LangSmith",
|
||||
"modal": "Modal",
|
||||
"runloop": "Runloop",
|
||||
}
|
||||
"""Human-readable display names for sandbox providers."""
|
||||
|
||||
|
||||
_toast_internals_warned: list[bool] = [False]
|
||||
"""Single-slot flag; once `_Toast._notification` is missing, log warning once.
|
||||
@@ -1315,6 +1324,12 @@ class DeepAgentsApp(App):
|
||||
self._sandbox_type: str | None = raw if raw and raw != "none" else None
|
||||
"""Normalized sandbox type (or `None`), attached to trace metadata."""
|
||||
|
||||
if sub_title is None and self._sandbox_type is not None:
|
||||
display = _SANDBOX_DISPLAY_NAMES.get(
|
||||
self._sandbox_type, self._sandbox_type.title()
|
||||
)
|
||||
self.sub_title = f"Sandbox: {display}"
|
||||
|
||||
# Per-turn model overrides
|
||||
self._model_override: str | None = None
|
||||
"""Per-turn model override set via `/model`; `None` uses session default."""
|
||||
|
||||
@@ -7717,6 +7717,43 @@ class TestHeaderAndTitle:
|
||||
assert not app.query(Header)
|
||||
|
||||
|
||||
class TestSandboxSubTitle:
|
||||
"""sub_title reflects the active sandbox provider."""
|
||||
|
||||
async def test_sandbox_sets_sub_title(self) -> None:
|
||||
"""When a sandbox is active, sub_title shows the provider name."""
|
||||
app = DeepAgentsApp(server_kwargs={"sandbox_type": "daytona"})
|
||||
assert app.sub_title == "Sandbox: Daytona"
|
||||
|
||||
async def test_sandbox_sub_title_proper_casing(self) -> None:
|
||||
"""Provider display names use proper casing."""
|
||||
app = DeepAgentsApp(server_kwargs={"sandbox_type": "langsmith"})
|
||||
assert app.sub_title == "Sandbox: LangSmith"
|
||||
|
||||
app2 = DeepAgentsApp(server_kwargs={"sandbox_type": "agentcore"})
|
||||
assert app2.sub_title == "Sandbox: AgentCore"
|
||||
|
||||
async def test_explicit_sub_title_overrides_sandbox(self) -> None:
|
||||
"""An explicitly passed sub_title is not overwritten by sandbox info."""
|
||||
app = DeepAgentsApp(sub_title="custom", server_kwargs={"sandbox_type": "modal"})
|
||||
assert app.sub_title == "custom"
|
||||
|
||||
async def test_no_sandbox_leaves_sub_title_default(self) -> None:
|
||||
"""Without a sandbox, sub_title remains at its Textual default."""
|
||||
app = DeepAgentsApp()
|
||||
assert app.sub_title == ""
|
||||
|
||||
async def test_sandbox_none_does_not_set_sub_title(self) -> None:
|
||||
"""The argparse default `'none'` is treated as no sandbox."""
|
||||
app = DeepAgentsApp(server_kwargs={"sandbox_type": "none"})
|
||||
assert app.sub_title == ""
|
||||
|
||||
async def test_unknown_provider_uses_title_case_fallback(self) -> None:
|
||||
"""An unrecognized provider falls back to `.title()` casing."""
|
||||
app = DeepAgentsApp(server_kwargs={"sandbox_type": "kubernetes"})
|
||||
assert app.sub_title == "Sandbox: Kubernetes"
|
||||
|
||||
|
||||
class TestHandleExternalSignal:
|
||||
"""Verify routing of `kind=signal` external events."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user