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.
Files changed (163) hide show
  1. deepagents_code/__init__.py +42 -0
  2. deepagents_code/__main__.py +6 -0
  3. deepagents_code/_ask_user_types.py +90 -0
  4. deepagents_code/_cli_context.py +96 -0
  5. deepagents_code/_constants.py +41 -0
  6. deepagents_code/_debug.py +148 -0
  7. deepagents_code/_debug_buffer.py +204 -0
  8. deepagents_code/_env_vars.py +411 -0
  9. deepagents_code/_fake_models.py +66 -0
  10. deepagents_code/_git.py +521 -0
  11. deepagents_code/_paths.py +69 -0
  12. deepagents_code/_server_config.py +576 -0
  13. deepagents_code/_session_stats.py +235 -0
  14. deepagents_code/_startup_error.py +45 -0
  15. deepagents_code/_testing_models.py +305 -0
  16. deepagents_code/_textual_patches.py +420 -0
  17. deepagents_code/_tool_stream.py +694 -0
  18. deepagents_code/_version.py +46 -0
  19. deepagents_code/agent.py +1976 -0
  20. deepagents_code/app.py +19239 -0
  21. deepagents_code/app.tcss +438 -0
  22. deepagents_code/approval_mode.py +131 -0
  23. deepagents_code/ask_user.py +306 -0
  24. deepagents_code/auth_display.py +185 -0
  25. deepagents_code/auth_store.py +545 -0
  26. deepagents_code/built_in_skills/__init__.py +5 -0
  27. deepagents_code/built_in_skills/remember/SKILL.md +118 -0
  28. deepagents_code/built_in_skills/skill-creator/SKILL.md +383 -0
  29. deepagents_code/built_in_skills/skill-creator/scripts/init_skill.py +366 -0
  30. deepagents_code/built_in_skills/skill-creator/scripts/quick_validate.py +158 -0
  31. deepagents_code/client/__init__.py +1 -0
  32. deepagents_code/client/commands/__init__.py +1 -0
  33. deepagents_code/client/commands/auth.py +541 -0
  34. deepagents_code/client/commands/config.py +714 -0
  35. deepagents_code/client/commands/mcp.py +250 -0
  36. deepagents_code/client/commands/tools.py +416 -0
  37. deepagents_code/client/launch/__init__.py +1 -0
  38. deepagents_code/client/launch/server.py +978 -0
  39. deepagents_code/client/launch/server_manager.py +540 -0
  40. deepagents_code/client/non_interactive.py +1758 -0
  41. deepagents_code/client/remote_client.py +794 -0
  42. deepagents_code/clipboard.py +217 -0
  43. deepagents_code/command_registry.py +450 -0
  44. deepagents_code/config.py +4829 -0
  45. deepagents_code/config_manifest.py +1451 -0
  46. deepagents_code/configurable_model.py +577 -0
  47. deepagents_code/default_agent_prompt.md +12 -0
  48. deepagents_code/doctor.py +559 -0
  49. deepagents_code/editor.py +142 -0
  50. deepagents_code/event_bus.py +411 -0
  51. deepagents_code/extras_info.py +661 -0
  52. deepagents_code/file_ops.py +576 -0
  53. deepagents_code/formatting.py +135 -0
  54. deepagents_code/goal_rubric.py +497 -0
  55. deepagents_code/goal_tools.py +459 -0
  56. deepagents_code/hooks.py +348 -0
  57. deepagents_code/input.py +1041 -0
  58. deepagents_code/integrations/__init__.py +1 -0
  59. deepagents_code/integrations/openai_codex.py +551 -0
  60. deepagents_code/integrations/sandbox_config.py +198 -0
  61. deepagents_code/integrations/sandbox_factory.py +1124 -0
  62. deepagents_code/integrations/sandbox_provider.py +137 -0
  63. deepagents_code/integrations/sandbox_registry.py +350 -0
  64. deepagents_code/iterm_cursor_guide.py +176 -0
  65. deepagents_code/local_context.py +926 -0
  66. deepagents_code/main.py +3985 -0
  67. deepagents_code/managed_tools.py +642 -0
  68. deepagents_code/mcp_auth.py +1950 -0
  69. deepagents_code/mcp_config.py +176 -0
  70. deepagents_code/mcp_disabled.py +212 -0
  71. deepagents_code/mcp_login_service.py +281 -0
  72. deepagents_code/mcp_oauth_ui.py +199 -0
  73. deepagents_code/mcp_providers/__init__.py +23 -0
  74. deepagents_code/mcp_providers/_registry.py +39 -0
  75. deepagents_code/mcp_providers/base.py +133 -0
  76. deepagents_code/mcp_providers/github.py +102 -0
  77. deepagents_code/mcp_providers/slack.py +175 -0
  78. deepagents_code/mcp_tools.py +2427 -0
  79. deepagents_code/mcp_trust.py +207 -0
  80. deepagents_code/media_utils.py +826 -0
  81. deepagents_code/memory_guard.py +474 -0
  82. deepagents_code/model_config.py +4156 -0
  83. deepagents_code/notifications.py +247 -0
  84. deepagents_code/offload.py +402 -0
  85. deepagents_code/onboarding.py +223 -0
  86. deepagents_code/output.py +69 -0
  87. deepagents_code/paste_collapse.py +103 -0
  88. deepagents_code/project_utils.py +231 -0
  89. deepagents_code/py.typed +0 -0
  90. deepagents_code/reasoning_effort.py +641 -0
  91. deepagents_code/reliable_rubric.py +97 -0
  92. deepagents_code/resume_state.py +237 -0
  93. deepagents_code/server_graph.py +310 -0
  94. deepagents_code/session_end_summary.py +329 -0
  95. deepagents_code/sessions.py +1716 -0
  96. deepagents_code/skills/__init__.py +18 -0
  97. deepagents_code/skills/commands.py +1252 -0
  98. deepagents_code/skills/invocation.py +112 -0
  99. deepagents_code/skills/load.py +196 -0
  100. deepagents_code/skills/trust.py +547 -0
  101. deepagents_code/state_migration.py +136 -0
  102. deepagents_code/subagents.py +278 -0
  103. deepagents_code/system_prompt.md +204 -0
  104. deepagents_code/terminal_capabilities.py +115 -0
  105. deepagents_code/terminal_escape.py +287 -0
  106. deepagents_code/theme.py +891 -0
  107. deepagents_code/todo_list_prompt.md +12 -0
  108. deepagents_code/tool_catalog.py +509 -0
  109. deepagents_code/tool_display.py +367 -0
  110. deepagents_code/tools.py +516 -0
  111. deepagents_code/tui/__init__.py +1 -0
  112. deepagents_code/tui/textual_adapter.py +2553 -0
  113. deepagents_code/tui/widgets/__init__.py +9 -0
  114. deepagents_code/tui/widgets/_js_eval_display.py +139 -0
  115. deepagents_code/tui/widgets/_links.py +261 -0
  116. deepagents_code/tui/widgets/agent_selector.py +420 -0
  117. deepagents_code/tui/widgets/approval.py +602 -0
  118. deepagents_code/tui/widgets/ask_user.py +515 -0
  119. deepagents_code/tui/widgets/auth.py +1997 -0
  120. deepagents_code/tui/widgets/autocomplete.py +890 -0
  121. deepagents_code/tui/widgets/chat_input.py +3181 -0
  122. deepagents_code/tui/widgets/codex_auth.py +452 -0
  123. deepagents_code/tui/widgets/cwd_switch.py +242 -0
  124. deepagents_code/tui/widgets/debug_console.py +868 -0
  125. deepagents_code/tui/widgets/diff.py +252 -0
  126. deepagents_code/tui/widgets/effort_selector.py +189 -0
  127. deepagents_code/tui/widgets/goal_review.py +390 -0
  128. deepagents_code/tui/widgets/goal_status.py +50 -0
  129. deepagents_code/tui/widgets/history.py +194 -0
  130. deepagents_code/tui/widgets/install_confirm.py +248 -0
  131. deepagents_code/tui/widgets/launch_init.py +482 -0
  132. deepagents_code/tui/widgets/loading.py +227 -0
  133. deepagents_code/tui/widgets/mcp_login.py +539 -0
  134. deepagents_code/tui/widgets/mcp_reconnect.py +212 -0
  135. deepagents_code/tui/widgets/mcp_viewer.py +1634 -0
  136. deepagents_code/tui/widgets/message_store.py +999 -0
  137. deepagents_code/tui/widgets/messages.py +3846 -0
  138. deepagents_code/tui/widgets/model_selector.py +2343 -0
  139. deepagents_code/tui/widgets/notification_center.py +456 -0
  140. deepagents_code/tui/widgets/notification_detail.py +257 -0
  141. deepagents_code/tui/widgets/notification_settings.py +170 -0
  142. deepagents_code/tui/widgets/restart_prompt.py +158 -0
  143. deepagents_code/tui/widgets/skill_trust.py +131 -0
  144. deepagents_code/tui/widgets/startup_tip.py +91 -0
  145. deepagents_code/tui/widgets/status.py +781 -0
  146. deepagents_code/tui/widgets/subagent_panel.py +969 -0
  147. deepagents_code/tui/widgets/theme_selector.py +401 -0
  148. deepagents_code/tui/widgets/thread_selector.py +2564 -0
  149. deepagents_code/tui/widgets/tool_renderers.py +190 -0
  150. deepagents_code/tui/widgets/tool_widgets.py +304 -0
  151. deepagents_code/tui/widgets/update_available.py +311 -0
  152. deepagents_code/tui/widgets/update_confirm.py +184 -0
  153. deepagents_code/tui/widgets/update_progress.py +327 -0
  154. deepagents_code/tui/widgets/welcome.py +508 -0
  155. deepagents_code/turn_end_summary.py +522 -0
  156. deepagents_code/ui.py +858 -0
  157. deepagents_code/unicode_security.py +563 -0
  158. deepagents_code/update_check.py +3124 -0
  159. zjcode-0.0.1.data/data/deepagents_code/default_agent_prompt.md +12 -0
  160. zjcode-0.0.1.dist-info/METADATA +220 -0
  161. zjcode-0.0.1.dist-info/RECORD +163 -0
  162. zjcode-0.0.1.dist-info/WHEEL +4 -0
  163. 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)