zjcode 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deepagents_code/__init__.py +42 -0
- deepagents_code/__main__.py +6 -0
- deepagents_code/_ask_user_types.py +90 -0
- deepagents_code/_cli_context.py +96 -0
- deepagents_code/_constants.py +41 -0
- deepagents_code/_debug.py +148 -0
- deepagents_code/_debug_buffer.py +204 -0
- deepagents_code/_env_vars.py +411 -0
- deepagents_code/_fake_models.py +66 -0
- deepagents_code/_git.py +521 -0
- deepagents_code/_paths.py +69 -0
- deepagents_code/_server_config.py +576 -0
- deepagents_code/_session_stats.py +235 -0
- deepagents_code/_startup_error.py +45 -0
- deepagents_code/_testing_models.py +305 -0
- deepagents_code/_textual_patches.py +420 -0
- deepagents_code/_tool_stream.py +694 -0
- deepagents_code/_version.py +46 -0
- deepagents_code/agent.py +1976 -0
- deepagents_code/app.py +19239 -0
- deepagents_code/app.tcss +438 -0
- deepagents_code/approval_mode.py +131 -0
- deepagents_code/ask_user.py +306 -0
- deepagents_code/auth_display.py +185 -0
- deepagents_code/auth_store.py +545 -0
- deepagents_code/built_in_skills/__init__.py +5 -0
- deepagents_code/built_in_skills/remember/SKILL.md +118 -0
- deepagents_code/built_in_skills/skill-creator/SKILL.md +383 -0
- deepagents_code/built_in_skills/skill-creator/scripts/init_skill.py +366 -0
- deepagents_code/built_in_skills/skill-creator/scripts/quick_validate.py +158 -0
- deepagents_code/client/__init__.py +1 -0
- deepagents_code/client/commands/__init__.py +1 -0
- deepagents_code/client/commands/auth.py +541 -0
- deepagents_code/client/commands/config.py +714 -0
- deepagents_code/client/commands/mcp.py +250 -0
- deepagents_code/client/commands/tools.py +416 -0
- deepagents_code/client/launch/__init__.py +1 -0
- deepagents_code/client/launch/server.py +978 -0
- deepagents_code/client/launch/server_manager.py +540 -0
- deepagents_code/client/non_interactive.py +1758 -0
- deepagents_code/client/remote_client.py +794 -0
- deepagents_code/clipboard.py +217 -0
- deepagents_code/command_registry.py +450 -0
- deepagents_code/config.py +4829 -0
- deepagents_code/config_manifest.py +1451 -0
- deepagents_code/configurable_model.py +577 -0
- deepagents_code/default_agent_prompt.md +12 -0
- deepagents_code/doctor.py +559 -0
- deepagents_code/editor.py +142 -0
- deepagents_code/event_bus.py +411 -0
- deepagents_code/extras_info.py +661 -0
- deepagents_code/file_ops.py +576 -0
- deepagents_code/formatting.py +135 -0
- deepagents_code/goal_rubric.py +497 -0
- deepagents_code/goal_tools.py +459 -0
- deepagents_code/hooks.py +348 -0
- deepagents_code/input.py +1041 -0
- deepagents_code/integrations/__init__.py +1 -0
- deepagents_code/integrations/openai_codex.py +551 -0
- deepagents_code/integrations/sandbox_config.py +198 -0
- deepagents_code/integrations/sandbox_factory.py +1124 -0
- deepagents_code/integrations/sandbox_provider.py +137 -0
- deepagents_code/integrations/sandbox_registry.py +350 -0
- deepagents_code/iterm_cursor_guide.py +176 -0
- deepagents_code/local_context.py +926 -0
- deepagents_code/main.py +3985 -0
- deepagents_code/managed_tools.py +642 -0
- deepagents_code/mcp_auth.py +1950 -0
- deepagents_code/mcp_config.py +176 -0
- deepagents_code/mcp_disabled.py +212 -0
- deepagents_code/mcp_login_service.py +281 -0
- deepagents_code/mcp_oauth_ui.py +199 -0
- deepagents_code/mcp_providers/__init__.py +23 -0
- deepagents_code/mcp_providers/_registry.py +39 -0
- deepagents_code/mcp_providers/base.py +133 -0
- deepagents_code/mcp_providers/github.py +102 -0
- deepagents_code/mcp_providers/slack.py +175 -0
- deepagents_code/mcp_tools.py +2427 -0
- deepagents_code/mcp_trust.py +207 -0
- deepagents_code/media_utils.py +826 -0
- deepagents_code/memory_guard.py +474 -0
- deepagents_code/model_config.py +4156 -0
- deepagents_code/notifications.py +247 -0
- deepagents_code/offload.py +402 -0
- deepagents_code/onboarding.py +223 -0
- deepagents_code/output.py +69 -0
- deepagents_code/paste_collapse.py +103 -0
- deepagents_code/project_utils.py +231 -0
- deepagents_code/py.typed +0 -0
- deepagents_code/reasoning_effort.py +641 -0
- deepagents_code/reliable_rubric.py +97 -0
- deepagents_code/resume_state.py +237 -0
- deepagents_code/server_graph.py +310 -0
- deepagents_code/session_end_summary.py +329 -0
- deepagents_code/sessions.py +1716 -0
- deepagents_code/skills/__init__.py +18 -0
- deepagents_code/skills/commands.py +1252 -0
- deepagents_code/skills/invocation.py +112 -0
- deepagents_code/skills/load.py +196 -0
- deepagents_code/skills/trust.py +547 -0
- deepagents_code/state_migration.py +136 -0
- deepagents_code/subagents.py +278 -0
- deepagents_code/system_prompt.md +204 -0
- deepagents_code/terminal_capabilities.py +115 -0
- deepagents_code/terminal_escape.py +287 -0
- deepagents_code/theme.py +891 -0
- deepagents_code/todo_list_prompt.md +12 -0
- deepagents_code/tool_catalog.py +509 -0
- deepagents_code/tool_display.py +367 -0
- deepagents_code/tools.py +516 -0
- deepagents_code/tui/__init__.py +1 -0
- deepagents_code/tui/textual_adapter.py +2553 -0
- deepagents_code/tui/widgets/__init__.py +9 -0
- deepagents_code/tui/widgets/_js_eval_display.py +139 -0
- deepagents_code/tui/widgets/_links.py +261 -0
- deepagents_code/tui/widgets/agent_selector.py +420 -0
- deepagents_code/tui/widgets/approval.py +602 -0
- deepagents_code/tui/widgets/ask_user.py +515 -0
- deepagents_code/tui/widgets/auth.py +1997 -0
- deepagents_code/tui/widgets/autocomplete.py +890 -0
- deepagents_code/tui/widgets/chat_input.py +3181 -0
- deepagents_code/tui/widgets/codex_auth.py +452 -0
- deepagents_code/tui/widgets/cwd_switch.py +242 -0
- deepagents_code/tui/widgets/debug_console.py +868 -0
- deepagents_code/tui/widgets/diff.py +252 -0
- deepagents_code/tui/widgets/effort_selector.py +189 -0
- deepagents_code/tui/widgets/goal_review.py +390 -0
- deepagents_code/tui/widgets/goal_status.py +50 -0
- deepagents_code/tui/widgets/history.py +194 -0
- deepagents_code/tui/widgets/install_confirm.py +248 -0
- deepagents_code/tui/widgets/launch_init.py +482 -0
- deepagents_code/tui/widgets/loading.py +227 -0
- deepagents_code/tui/widgets/mcp_login.py +539 -0
- deepagents_code/tui/widgets/mcp_reconnect.py +212 -0
- deepagents_code/tui/widgets/mcp_viewer.py +1634 -0
- deepagents_code/tui/widgets/message_store.py +999 -0
- deepagents_code/tui/widgets/messages.py +3846 -0
- deepagents_code/tui/widgets/model_selector.py +2343 -0
- deepagents_code/tui/widgets/notification_center.py +456 -0
- deepagents_code/tui/widgets/notification_detail.py +257 -0
- deepagents_code/tui/widgets/notification_settings.py +170 -0
- deepagents_code/tui/widgets/restart_prompt.py +158 -0
- deepagents_code/tui/widgets/skill_trust.py +131 -0
- deepagents_code/tui/widgets/startup_tip.py +91 -0
- deepagents_code/tui/widgets/status.py +781 -0
- deepagents_code/tui/widgets/subagent_panel.py +969 -0
- deepagents_code/tui/widgets/theme_selector.py +401 -0
- deepagents_code/tui/widgets/thread_selector.py +2564 -0
- deepagents_code/tui/widgets/tool_renderers.py +190 -0
- deepagents_code/tui/widgets/tool_widgets.py +304 -0
- deepagents_code/tui/widgets/update_available.py +311 -0
- deepagents_code/tui/widgets/update_confirm.py +184 -0
- deepagents_code/tui/widgets/update_progress.py +327 -0
- deepagents_code/tui/widgets/welcome.py +508 -0
- deepagents_code/turn_end_summary.py +522 -0
- deepagents_code/ui.py +858 -0
- deepagents_code/unicode_security.py +563 -0
- deepagents_code/update_check.py +3124 -0
- zjcode-0.0.1.data/data/deepagents_code/default_agent_prompt.md +12 -0
- zjcode-0.0.1.dist-info/METADATA +220 -0
- zjcode-0.0.1.dist-info/RECORD +163 -0
- zjcode-0.0.1.dist-info/WHEEL +4 -0
- zjcode-0.0.1.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
"""Inspect optional-dependency install status for the running distribution.
|
|
2
|
+
|
|
3
|
+
Reads `Requires-Dist` metadata to report which packages declared under
|
|
4
|
+
`[project.optional-dependencies]` are installed, and renders that status
|
|
5
|
+
in either plain text (for stdout) or markdown (for rich UI contexts).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import ast
|
|
11
|
+
import importlib.util
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
import re
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from importlib.metadata import (
|
|
17
|
+
PackageNotFoundError,
|
|
18
|
+
distribution,
|
|
19
|
+
version as pkg_version,
|
|
20
|
+
)
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Literal
|
|
23
|
+
from urllib.parse import urlparse
|
|
24
|
+
from urllib.request import url2pathname
|
|
25
|
+
|
|
26
|
+
from packaging.requirements import InvalidRequirement, Requirement
|
|
27
|
+
from packaging.utils import canonicalize_name
|
|
28
|
+
|
|
29
|
+
from deepagents_code._version import DISTRIBUTION_NAME
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
SdkVersionStatus = Literal["resolved", "not_installed", "error"]
|
|
34
|
+
"""Outcome of an SDK version lookup.
|
|
35
|
+
|
|
36
|
+
`"not_installed"` means the package metadata is genuinely absent;
|
|
37
|
+
`"error"` means an unexpected failure occurred while reading it. Callers
|
|
38
|
+
that don't care which kind of failure happened can treat both the same.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _editable_sdk_source_root() -> Path | None:
|
|
43
|
+
"""Return the editable `deepagents` source root from package metadata."""
|
|
44
|
+
try:
|
|
45
|
+
raw = distribution("deepagents").read_text("direct_url.json")
|
|
46
|
+
if not raw:
|
|
47
|
+
return None
|
|
48
|
+
data = json.loads(raw)
|
|
49
|
+
if not isinstance(data, dict):
|
|
50
|
+
logger.debug("Ignoring malformed deepagents direct_url.json metadata")
|
|
51
|
+
return None
|
|
52
|
+
dir_info = data.get("dir_info")
|
|
53
|
+
if not isinstance(dir_info, dict):
|
|
54
|
+
logger.debug("Ignoring malformed deepagents direct_url.json dir_info")
|
|
55
|
+
return None
|
|
56
|
+
if not dir_info.get("editable", False):
|
|
57
|
+
return None
|
|
58
|
+
url = data.get("url")
|
|
59
|
+
if not isinstance(url, str):
|
|
60
|
+
logger.debug("Ignoring editable deepagents metadata without a source URL")
|
|
61
|
+
return None
|
|
62
|
+
parsed = urlparse(url)
|
|
63
|
+
if parsed.scheme != "file":
|
|
64
|
+
logger.debug("Ignoring editable deepagents metadata with non-file URL")
|
|
65
|
+
return None
|
|
66
|
+
path = url2pathname(parsed.path)
|
|
67
|
+
if parsed.netloc and parsed.netloc != "localhost":
|
|
68
|
+
path = f"//{parsed.netloc}{path}"
|
|
69
|
+
return Path(path)
|
|
70
|
+
except (PackageNotFoundError, OSError, ValueError, TypeError):
|
|
71
|
+
# `OSError` covers `FileNotFoundError`/`PermissionError`/etc. while
|
|
72
|
+
# reading the metadata file; `ValueError` covers malformed JSON
|
|
73
|
+
# (`json.JSONDecodeError`), bad encodings (`UnicodeDecodeError`), and an
|
|
74
|
+
# invalid IPv6 host from `urlparse`; `TypeError` covers a non-text
|
|
75
|
+
# `read_text` payload. `url2pathname` is intentionally lenient and adds
|
|
76
|
+
# no new failure modes. This probe must never propagate, since callers
|
|
77
|
+
# treat it as a best-effort refinement over the metadata version.
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _sdk_version_from_source(root: Path) -> str | None:
|
|
82
|
+
"""Read `deepagents.__version__` from a source tree rooted at `root`.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The source SDK version, or `None` when it cannot be read.
|
|
86
|
+
"""
|
|
87
|
+
version_file = root / "deepagents" / "_version.py"
|
|
88
|
+
try:
|
|
89
|
+
source = version_file.read_text(encoding="utf-8")
|
|
90
|
+
module = ast.parse(source, filename=str(version_file))
|
|
91
|
+
except (OSError, SyntaxError, ValueError):
|
|
92
|
+
# Reached only for editable installs, where the package is known to be
|
|
93
|
+
# present — so an unreadable or malformed version file is a broken local
|
|
94
|
+
# checkout, not an absent dependency. Warn (not debug): the source
|
|
95
|
+
# version is masked and the caller falls back to potentially stale
|
|
96
|
+
# metadata.
|
|
97
|
+
logger.warning("Failed to read deepagents SDK version file", exc_info=True)
|
|
98
|
+
return None
|
|
99
|
+
for node in module.body:
|
|
100
|
+
# Match only a plain `__version__ = "..."` assignment. release-please
|
|
101
|
+
# writes the SDK's `_version.py` that way, so annotated (`ast.AnnAssign`)
|
|
102
|
+
# or tuple-target forms are intentionally ignored.
|
|
103
|
+
if not isinstance(node, ast.Assign):
|
|
104
|
+
continue
|
|
105
|
+
if not any(
|
|
106
|
+
isinstance(target, ast.Name) and target.id == "__version__"
|
|
107
|
+
for target in node.targets
|
|
108
|
+
):
|
|
109
|
+
continue
|
|
110
|
+
try:
|
|
111
|
+
value = ast.literal_eval(node.value)
|
|
112
|
+
except (ValueError, TypeError):
|
|
113
|
+
# A non-literal `__version__` RHS masks the source version just like
|
|
114
|
+
# an unreadable file, so warn for parity with the read/parse failure
|
|
115
|
+
# above rather than falling back to stale metadata silently.
|
|
116
|
+
logger.warning(
|
|
117
|
+
"Failed to evaluate deepagents SDK __version__ literal",
|
|
118
|
+
exc_info=True,
|
|
119
|
+
)
|
|
120
|
+
return None
|
|
121
|
+
return value if isinstance(value, str) and value else None
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def resolve_sdk_version() -> tuple[str | None, SdkVersionStatus]:
|
|
126
|
+
"""Resolve the installed `deepagents` SDK version.
|
|
127
|
+
|
|
128
|
+
Single source of truth for the lookup that `--version`, `/version`, and
|
|
129
|
+
`doctor` each used to reimplement. Editable installs can have stale package
|
|
130
|
+
metadata after local version files change, so they prefer the source tree's
|
|
131
|
+
`_version.py` and fall back to metadata when the source version is
|
|
132
|
+
unavailable. Distinguishes a genuinely missing package from an unexpected
|
|
133
|
+
metadata error so diagnostic callers can report the two differently, while
|
|
134
|
+
collapse-friendly callers can ignore the split.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
`(version, status)`. `version` is the resolved version string when
|
|
138
|
+
`status` is `"resolved"`, otherwise `None`.
|
|
139
|
+
"""
|
|
140
|
+
try:
|
|
141
|
+
metadata_version = pkg_version("deepagents")
|
|
142
|
+
except PackageNotFoundError:
|
|
143
|
+
logger.debug("deepagents SDK package not found in environment")
|
|
144
|
+
return None, "not_installed"
|
|
145
|
+
except Exception: # Best-effort lookup; never propagate to the caller
|
|
146
|
+
logger.warning(
|
|
147
|
+
"Unexpected error looking up deepagents SDK version", exc_info=True
|
|
148
|
+
)
|
|
149
|
+
return None, "error"
|
|
150
|
+
|
|
151
|
+
source_root = _editable_sdk_source_root()
|
|
152
|
+
if source_root:
|
|
153
|
+
source_version = _sdk_version_from_source(source_root)
|
|
154
|
+
if source_version:
|
|
155
|
+
return source_version, "resolved"
|
|
156
|
+
|
|
157
|
+
return metadata_version, "resolved"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
_EXTRA_MARKER_RE = re.compile(r"""extra\s*==\s*["']([^"']+)["']""")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class ExtrasIntrospectionError(RuntimeError):
|
|
164
|
+
"""Raised when installed extras cannot be determined safely."""
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
_COMPOSITE_EXTRAS: frozenset[str] = frozenset({"all-providers", "all-sandboxes"})
|
|
168
|
+
"""Extras whose package set is already covered by other, more specific extras.
|
|
169
|
+
|
|
170
|
+
Build backends flatten these meta-extras into their component packages
|
|
171
|
+
rather than preserving the `deepagents-code[a,b,...]` self-reference, so
|
|
172
|
+
name-based filtering is the only reliable way to drop them.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
MODEL_PROVIDER_EXTRAS: frozenset[str] = frozenset(
|
|
176
|
+
{
|
|
177
|
+
"anthropic",
|
|
178
|
+
"baseten",
|
|
179
|
+
"bedrock",
|
|
180
|
+
"cohere",
|
|
181
|
+
"deepseek",
|
|
182
|
+
"fireworks",
|
|
183
|
+
"google-genai",
|
|
184
|
+
"groq",
|
|
185
|
+
"huggingface",
|
|
186
|
+
"ibm",
|
|
187
|
+
"litellm",
|
|
188
|
+
"meta",
|
|
189
|
+
"mistralai",
|
|
190
|
+
"nvidia",
|
|
191
|
+
"ollama",
|
|
192
|
+
"openai",
|
|
193
|
+
"openrouter",
|
|
194
|
+
"perplexity",
|
|
195
|
+
"together",
|
|
196
|
+
"vertex",
|
|
197
|
+
"xai",
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
"""Optional extras that add model-provider integrations.
|
|
201
|
+
|
|
202
|
+
Keep in sync with `[project.optional-dependencies]` in `pyproject.toml`.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
SANDBOX_EXTRAS: frozenset[str] = frozenset(
|
|
206
|
+
{"agentcore", "daytona", "modal", "runloop", "vercel"}
|
|
207
|
+
)
|
|
208
|
+
"""Optional extras that add sandbox integrations."""
|
|
209
|
+
|
|
210
|
+
STANDALONE_EXTRAS: frozenset[str] = frozenset({"media", "quickjs"})
|
|
211
|
+
"""Optional extras that don't fit the provider/sandbox taxonomy.
|
|
212
|
+
|
|
213
|
+
`quickjs` is a core dependency as of 0.1.24, but the empty extra remains
|
|
214
|
+
installable so older `deepagents-code[quickjs]` and `/install quickjs` workflows
|
|
215
|
+
stay harmless.
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
KNOWN_EXTRAS: frozenset[str] = (
|
|
219
|
+
MODEL_PROVIDER_EXTRAS | SANDBOX_EXTRAS | STANDALONE_EXTRAS
|
|
220
|
+
)
|
|
221
|
+
"""Union of all individually-installable extras.
|
|
222
|
+
|
|
223
|
+
Excludes the composite meta-extras (`all-providers`, `all-sandboxes`) since
|
|
224
|
+
those expand to other extras and don't add anything on their own.
|
|
225
|
+
Drift-protected by `test_model_config.TestProviderApiKeyEnv` and the
|
|
226
|
+
model-provider-drift checks; new extras must be added to the corresponding
|
|
227
|
+
category frozenset above.
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def format_known_extras() -> str:
|
|
232
|
+
"""Render the installable extras grouped by category as plain text.
|
|
233
|
+
|
|
234
|
+
Drives the no-argument `/install` slash-command help so users can
|
|
235
|
+
discover valid extras without consulting `pyproject.toml`. Sourced from
|
|
236
|
+
the category frozensets above, so it stays in sync with `KNOWN_EXTRAS`
|
|
237
|
+
automatically.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Multi-line string with one labeled line per category, each listing
|
|
241
|
+
its extras alphabetically.
|
|
242
|
+
"""
|
|
243
|
+
groups: tuple[tuple[str, frozenset[str]], ...] = (
|
|
244
|
+
("Model providers", MODEL_PROVIDER_EXTRAS),
|
|
245
|
+
("Sandboxes", SANDBOX_EXTRAS),
|
|
246
|
+
("Other", STANDALONE_EXTRAS),
|
|
247
|
+
)
|
|
248
|
+
lines = ["Available extras:"]
|
|
249
|
+
lines.extend(
|
|
250
|
+
f" {label}: {', '.join(sorted(extras))}" for label, extras in groups if extras
|
|
251
|
+
)
|
|
252
|
+
return "\n".join(lines)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
ExtrasStatus = dict[str, list[tuple[str, str]]]
|
|
256
|
+
"""Mapping from extra name to `(package, installed_version)` tuples.
|
|
257
|
+
|
|
258
|
+
Only packages that are actually installed are included. Extras whose
|
|
259
|
+
declared packages are all missing are omitted entirely.
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@dataclass(frozen=True)
|
|
264
|
+
class ExtraDependencyStatus:
|
|
265
|
+
"""Install status for one optional dependency extra."""
|
|
266
|
+
|
|
267
|
+
name: str
|
|
268
|
+
"""Extra name, such as `anthropic` or `daytona`."""
|
|
269
|
+
|
|
270
|
+
installed: tuple[tuple[str, str], ...]
|
|
271
|
+
"""Installed `(package, version)` pairs declared by this extra."""
|
|
272
|
+
|
|
273
|
+
missing: tuple[str, ...]
|
|
274
|
+
"""Declared package names for this extra that are not installed."""
|
|
275
|
+
|
|
276
|
+
@property
|
|
277
|
+
def ready(self) -> bool:
|
|
278
|
+
"""Whether all declared packages for this extra are installed."""
|
|
279
|
+
return bool(self.installed) and not self.missing
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _extract_extra_name(marker_str: str) -> str | None:
|
|
283
|
+
"""Pull the extra name out of a marker like `extra == "anthropic"`.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
marker_str: String form of a `packaging.markers.Marker`.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
The quoted extra name, or `None` when the marker does not carry an
|
|
290
|
+
`extra == "..."` clause.
|
|
291
|
+
"""
|
|
292
|
+
match = _EXTRA_MARKER_RE.search(marker_str)
|
|
293
|
+
return match.group(1) if match else None
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def get_extras_status(
|
|
297
|
+
distribution_name: str = DISTRIBUTION_NAME,
|
|
298
|
+
) -> ExtrasStatus:
|
|
299
|
+
"""Return installed optional dependencies grouped by extra.
|
|
300
|
+
|
|
301
|
+
Reads `Requires-Dist` metadata from the named distribution, groups the
|
|
302
|
+
entries gated by `extra == "..."` markers under their extra name, and
|
|
303
|
+
resolves each package's installed version via `importlib.metadata`.
|
|
304
|
+
Packages that are not installed are omitted; extras whose entire
|
|
305
|
+
package list is absent are dropped.
|
|
306
|
+
|
|
307
|
+
Composite meta-extras that only bundle other extras (see
|
|
308
|
+
`_COMPOSITE_EXTRAS`) and self-references to the distribution itself
|
|
309
|
+
are skipped — their components already appear under their own extras.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
distribution_name: Name of the installed distribution to inspect.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Mapping from extra name to a sorted list of `(package, version)`
|
|
316
|
+
tuples for packages that are currently installed. An empty
|
|
317
|
+
mapping is returned when the distribution itself is not found.
|
|
318
|
+
"""
|
|
319
|
+
result: ExtrasStatus = {}
|
|
320
|
+
for extra in get_optional_dependency_status(distribution_name):
|
|
321
|
+
if extra.installed:
|
|
322
|
+
result[extra.name] = list(extra.installed)
|
|
323
|
+
return result
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def installed_extra_names(
|
|
327
|
+
distribution_name: str = DISTRIBUTION_NAME,
|
|
328
|
+
*,
|
|
329
|
+
strict: bool = False,
|
|
330
|
+
) -> set[str]:
|
|
331
|
+
"""Return extras with at least one installed dependency.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
distribution_name: Name of the installed distribution to inspect.
|
|
335
|
+
strict: Raise when the distribution metadata cannot be read or parsed
|
|
336
|
+
reliably.
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Set of extra names whose optional dependency metadata has at least one
|
|
340
|
+
installed package. Composite extras are excluded.
|
|
341
|
+
"""
|
|
342
|
+
statuses = get_optional_dependency_status(distribution_name, strict=strict)
|
|
343
|
+
return {extra.name for extra in statuses if extra.installed}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def get_optional_dependency_status(
|
|
347
|
+
distribution_name: str = DISTRIBUTION_NAME,
|
|
348
|
+
*,
|
|
349
|
+
strict: bool = False,
|
|
350
|
+
) -> tuple[ExtraDependencyStatus, ...]:
|
|
351
|
+
"""Return installed and missing optional dependencies grouped by extra.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
distribution_name: Name of the installed distribution to inspect.
|
|
355
|
+
strict: Raise when the distribution metadata cannot be read or parsed
|
|
356
|
+
reliably.
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Sorted tuple of optional extra statuses. An empty tuple is returned
|
|
360
|
+
when the distribution itself is not found.
|
|
361
|
+
|
|
362
|
+
Raises:
|
|
363
|
+
ExtrasIntrospectionError: If `strict` is `True` and metadata
|
|
364
|
+
introspection fails.
|
|
365
|
+
"""
|
|
366
|
+
try:
|
|
367
|
+
dist = distribution(distribution_name)
|
|
368
|
+
except PackageNotFoundError:
|
|
369
|
+
if strict:
|
|
370
|
+
msg = (
|
|
371
|
+
f"Distribution {distribution_name!r} not found; cannot preserve "
|
|
372
|
+
"already-installed extras safely"
|
|
373
|
+
)
|
|
374
|
+
raise ExtrasIntrospectionError(msg) from None
|
|
375
|
+
# Editable installs renamed by the user, dev checkouts without metadata,
|
|
376
|
+
# or vendored copies all hit this path. The dependency screen otherwise
|
|
377
|
+
# silently renders "none detected" twice; warn so the cause is visible.
|
|
378
|
+
logger.warning(
|
|
379
|
+
"Distribution %s not found; optional-dependency status will be empty",
|
|
380
|
+
distribution_name,
|
|
381
|
+
)
|
|
382
|
+
return ()
|
|
383
|
+
|
|
384
|
+
own_name = distribution_name.lower()
|
|
385
|
+
installed: dict[str, list[tuple[str, str]]] = {}
|
|
386
|
+
missing: dict[str, list[str]] = {}
|
|
387
|
+
for raw in dist.requires or []:
|
|
388
|
+
try:
|
|
389
|
+
req = Requirement(raw)
|
|
390
|
+
except InvalidRequirement:
|
|
391
|
+
if strict:
|
|
392
|
+
msg = (
|
|
393
|
+
"Could not parse optional-dependency metadata; cannot "
|
|
394
|
+
f"preserve already-installed extras safely: {raw}"
|
|
395
|
+
)
|
|
396
|
+
raise ExtrasIntrospectionError(msg) from None
|
|
397
|
+
logger.warning("Could not parse Requires-Dist entry: %s", raw)
|
|
398
|
+
continue
|
|
399
|
+
if not req.marker:
|
|
400
|
+
continue
|
|
401
|
+
extra = _extract_extra_name(str(req.marker))
|
|
402
|
+
if not extra:
|
|
403
|
+
continue
|
|
404
|
+
if extra in _COMPOSITE_EXTRAS:
|
|
405
|
+
continue
|
|
406
|
+
if req.name.lower() == own_name:
|
|
407
|
+
continue
|
|
408
|
+
try:
|
|
409
|
+
version = pkg_version(req.name)
|
|
410
|
+
except PackageNotFoundError:
|
|
411
|
+
missing.setdefault(extra, []).append(req.name)
|
|
412
|
+
else:
|
|
413
|
+
installed.setdefault(extra, []).append((req.name, version))
|
|
414
|
+
|
|
415
|
+
names = sorted(set(installed) | set(missing))
|
|
416
|
+
return tuple(
|
|
417
|
+
ExtraDependencyStatus(
|
|
418
|
+
name=name,
|
|
419
|
+
installed=tuple(sorted(installed.get(name, []))),
|
|
420
|
+
missing=tuple(sorted(missing.get(name, []))),
|
|
421
|
+
)
|
|
422
|
+
for name in names
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def extra_for_package(
|
|
427
|
+
package: str,
|
|
428
|
+
distribution_name: str = DISTRIBUTION_NAME,
|
|
429
|
+
) -> str | None:
|
|
430
|
+
"""Return the installable extra that declares a package.
|
|
431
|
+
|
|
432
|
+
Resolves recovery hints from the package that is actually missing
|
|
433
|
+
instead of guessing from a provider identifier. For example,
|
|
434
|
+
`langchain-google-vertexai` maps to the `vertex` extra even though the
|
|
435
|
+
provider id is `google_vertexai`.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
package: Distribution package name to find in optional dependencies.
|
|
439
|
+
distribution_name: Name of the installed distribution to inspect.
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
The known extra name that declares `package`, or `None` when the
|
|
443
|
+
package is not declared by an individually-installable extra,
|
|
444
|
+
or when the distribution's metadata could not be read (logged
|
|
445
|
+
at `warning` level — callers should treat both cases the same
|
|
446
|
+
since the right fallback in either is `install_package_command`).
|
|
447
|
+
"""
|
|
448
|
+
try:
|
|
449
|
+
dist = distribution(distribution_name)
|
|
450
|
+
except PackageNotFoundError:
|
|
451
|
+
logger.warning(
|
|
452
|
+
"Distribution %s not found; cannot resolve extra for package %s",
|
|
453
|
+
distribution_name,
|
|
454
|
+
package,
|
|
455
|
+
)
|
|
456
|
+
return None
|
|
457
|
+
|
|
458
|
+
own_name = canonicalize_name(distribution_name)
|
|
459
|
+
target = canonicalize_name(package)
|
|
460
|
+
for raw in dist.requires or []:
|
|
461
|
+
try:
|
|
462
|
+
req = Requirement(raw)
|
|
463
|
+
except InvalidRequirement:
|
|
464
|
+
logger.warning("Could not parse Requires-Dist entry: %s", raw)
|
|
465
|
+
continue
|
|
466
|
+
if canonicalize_name(req.name) != target:
|
|
467
|
+
continue
|
|
468
|
+
if canonicalize_name(req.name) == own_name:
|
|
469
|
+
continue
|
|
470
|
+
if not req.marker:
|
|
471
|
+
continue
|
|
472
|
+
extra = _extract_extra_name(str(req.marker))
|
|
473
|
+
if extra in KNOWN_EXTRAS:
|
|
474
|
+
return extra
|
|
475
|
+
return None
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def verify_interpreter_deps() -> None:
|
|
479
|
+
"""Check that `langchain-quickjs` is installed for the interpreter.
|
|
480
|
+
|
|
481
|
+
Uses `importlib.util.find_spec` for a lightweight check with no actual
|
|
482
|
+
imports. Call this in the app process *before* spawning the server
|
|
483
|
+
subprocess so users get a clear, actionable error instead of an opaque
|
|
484
|
+
server crash when the core dependency is missing or broken.
|
|
485
|
+
|
|
486
|
+
Returns silently when the package is importable.
|
|
487
|
+
|
|
488
|
+
Raises:
|
|
489
|
+
ImportError: If `langchain_quickjs` is not importable.
|
|
490
|
+
"""
|
|
491
|
+
try:
|
|
492
|
+
found = importlib.util.find_spec("langchain_quickjs") is not None
|
|
493
|
+
except (ImportError, ValueError):
|
|
494
|
+
# A broken-but-installed `langchain_quickjs` (e.g., parent package
|
|
495
|
+
# raises during import) would otherwise masquerade as "not installed";
|
|
496
|
+
# capture the underlying cause for debug logs.
|
|
497
|
+
logger.debug("find_spec failed for langchain_quickjs", exc_info=True)
|
|
498
|
+
found = False
|
|
499
|
+
|
|
500
|
+
if not found:
|
|
501
|
+
from deepagents_code.config import _is_editable_install
|
|
502
|
+
|
|
503
|
+
if _is_editable_install():
|
|
504
|
+
msg = (
|
|
505
|
+
"Missing core dependency for the interpreter. Editable install "
|
|
506
|
+
"detected — refresh the local environment with uv sync, or "
|
|
507
|
+
"relaunch with --no-interpreter to skip it."
|
|
508
|
+
)
|
|
509
|
+
else:
|
|
510
|
+
msg = (
|
|
511
|
+
"Missing core dependency for the interpreter. "
|
|
512
|
+
"Reinstall dcode to restore langchain-quickjs, or relaunch with "
|
|
513
|
+
"--no-interpreter to skip it."
|
|
514
|
+
)
|
|
515
|
+
raise ImportError(msg)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def format_extras_status_plain(status: ExtrasStatus) -> str:
|
|
519
|
+
"""Render an `ExtrasStatus` mapping as column-aligned plain text.
|
|
520
|
+
|
|
521
|
+
Suitable for stdout in non-interactive contexts (e.g. the `--version`
|
|
522
|
+
CLI flag) where a markdown renderer is unavailable.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
status: Mapping returned by `get_extras_status`.
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
Multi-line string with a heading and one `extra package version`
|
|
529
|
+
row per installed package.
|
|
530
|
+
|
|
531
|
+
Returns an empty string when `status` is empty.
|
|
532
|
+
"""
|
|
533
|
+
if not status:
|
|
534
|
+
return ""
|
|
535
|
+
rows: list[tuple[str, str, str]] = [
|
|
536
|
+
(extra_name, pkg_name, version)
|
|
537
|
+
for extra_name, pkgs in status.items()
|
|
538
|
+
for pkg_name, version in pkgs
|
|
539
|
+
]
|
|
540
|
+
extra_width = max(len(row[0]) for row in rows)
|
|
541
|
+
package_width = max(len(row[1]) for row in rows)
|
|
542
|
+
lines = ["Installed optional dependencies:"]
|
|
543
|
+
lines.extend(
|
|
544
|
+
f" {extra.ljust(extra_width)} {pkg.ljust(package_width)} {version}"
|
|
545
|
+
for extra, pkg, version in rows
|
|
546
|
+
)
|
|
547
|
+
return "\n".join(lines)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
CORE_DEPENDENCIES: tuple[str, ...] = (
|
|
551
|
+
"langchain",
|
|
552
|
+
"langchain-core",
|
|
553
|
+
"langgraph",
|
|
554
|
+
"langgraph-checkpoint",
|
|
555
|
+
"langgraph-prebuilt",
|
|
556
|
+
"langgraph-sdk",
|
|
557
|
+
"langsmith",
|
|
558
|
+
)
|
|
559
|
+
"""Core LangChain-ecosystem packages surfaced for editable installs.
|
|
560
|
+
|
|
561
|
+
The deepagents SDK is reported separately by `/version`, so it is omitted
|
|
562
|
+
here. These are the packages a local checkout is most likely to pin or
|
|
563
|
+
override, so their resolved versions help diagnose editable environments.
|
|
564
|
+
"""
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def get_core_dependency_versions() -> list[tuple[str, str | None]]:
|
|
568
|
+
"""Return `(package, version)` pairs for the core ecosystem dependencies.
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
One entry per package in `CORE_DEPENDENCIES`, in declaration order.
|
|
572
|
+
The version is `None` when the package is not installed.
|
|
573
|
+
"""
|
|
574
|
+
versions: list[tuple[str, str | None]] = []
|
|
575
|
+
for name in CORE_DEPENDENCIES:
|
|
576
|
+
try:
|
|
577
|
+
versions.append((name, pkg_version(name)))
|
|
578
|
+
except PackageNotFoundError:
|
|
579
|
+
versions.append((name, None))
|
|
580
|
+
return versions
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def format_core_dependencies_plain() -> str:
|
|
584
|
+
"""Render core ecosystem dependency versions as column-aligned plain text.
|
|
585
|
+
|
|
586
|
+
Suitable for stdout in non-interactive contexts (e.g. the `--version`
|
|
587
|
+
CLI flag) where a markdown renderer is unavailable.
|
|
588
|
+
|
|
589
|
+
Returns:
|
|
590
|
+
Multi-line string with a heading and one `package version` row per
|
|
591
|
+
core dependency. Missing packages are reported as `not installed`.
|
|
592
|
+
"""
|
|
593
|
+
rows = [
|
|
594
|
+
(name, version or "not installed")
|
|
595
|
+
for name, version in get_core_dependency_versions()
|
|
596
|
+
]
|
|
597
|
+
package_width = max(len(name) for name, _ in rows)
|
|
598
|
+
lines = ["Core dependencies:"]
|
|
599
|
+
lines.extend(f" {name.ljust(package_width)} {version}" for name, version in rows)
|
|
600
|
+
return "\n".join(lines)
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def format_core_dependencies() -> str:
|
|
604
|
+
"""Render core ecosystem dependency versions as a markdown fragment.
|
|
605
|
+
|
|
606
|
+
Returns:
|
|
607
|
+
Multi-line markdown string with a heading and a pipe table listing
|
|
608
|
+
each core package and its resolved version (or `not installed`).
|
|
609
|
+
"""
|
|
610
|
+
rows = [
|
|
611
|
+
(name, version or "not installed")
|
|
612
|
+
for name, version in get_core_dependency_versions()
|
|
613
|
+
]
|
|
614
|
+
headers = ("Package", "Version")
|
|
615
|
+
|
|
616
|
+
def _row(cells: tuple[str, str]) -> str:
|
|
617
|
+
return "| " + " | ".join(cells) + " |"
|
|
618
|
+
|
|
619
|
+
lines = [
|
|
620
|
+
"### Core dependencies",
|
|
621
|
+
"",
|
|
622
|
+
_row(headers),
|
|
623
|
+
"| " + " | ".join("---" for _ in headers) + " |",
|
|
624
|
+
*(_row(row) for row in rows),
|
|
625
|
+
]
|
|
626
|
+
return "\n".join(lines)
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def format_extras_status(status: ExtrasStatus) -> str:
|
|
630
|
+
"""Render an `ExtrasStatus` mapping as a markdown fragment.
|
|
631
|
+
|
|
632
|
+
Args:
|
|
633
|
+
status: Mapping returned by `get_extras_status`.
|
|
634
|
+
|
|
635
|
+
Returns:
|
|
636
|
+
Multi-line markdown string containing a heading and a pipe table
|
|
637
|
+
with `Extra`, `Package`, and `Version` columns, suitable for
|
|
638
|
+
rendering via a markdown widget.
|
|
639
|
+
|
|
640
|
+
Returns an empty string when `status` is empty.
|
|
641
|
+
"""
|
|
642
|
+
if not status:
|
|
643
|
+
return ""
|
|
644
|
+
rows: list[tuple[str, str, str]] = [
|
|
645
|
+
(extra_name, pkg_name, version)
|
|
646
|
+
for extra_name, pkgs in status.items()
|
|
647
|
+
for pkg_name, version in pkgs
|
|
648
|
+
]
|
|
649
|
+
headers = ("Extra", "Package", "Version")
|
|
650
|
+
|
|
651
|
+
def _row(cells: tuple[str, str, str]) -> str:
|
|
652
|
+
return "| " + " | ".join(cells) + " |"
|
|
653
|
+
|
|
654
|
+
lines = [
|
|
655
|
+
"### Installed optional dependencies",
|
|
656
|
+
"",
|
|
657
|
+
_row(headers),
|
|
658
|
+
"| " + " | ".join("---" for _ in headers) + " |",
|
|
659
|
+
*(_row(row) for row in rows),
|
|
660
|
+
]
|
|
661
|
+
return "\n".join(lines)
|