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,137 @@
1
+ """Sandbox provider interface used by Deep Agents Code."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from abc import ABC, abstractmethod
7
+ from dataclasses import dataclass
8
+ from typing import TYPE_CHECKING, Any, Literal
9
+
10
+ if TYPE_CHECKING:
11
+ from deepagents.backends.protocol import SandboxBackendProtocol
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class SandboxInstallHint:
16
+ """How to install the package that provides a sandbox backend.
17
+
18
+ Built-in providers ship as `deepagents-code` extras (`kind="extra"`);
19
+ third-party providers install as arbitrary packages (`kind="package"`).
20
+ The distinction lets error messages emit the correct install command
21
+ (`/install daytona` vs. `/install acme-dcode-sandbox --package`).
22
+ """
23
+
24
+ kind: Literal["extra", "package"]
25
+ name: str
26
+
27
+ def command(self, *, in_app: bool) -> str:
28
+ """Render the install command for this hint.
29
+
30
+ Args:
31
+ in_app: Whether to render the in-app slash command (`/install`)
32
+ rather than the CLI command (`dcode --install`).
33
+
34
+ Returns:
35
+ The install command string.
36
+ """
37
+ prefix = "/install" if in_app else "dcode --install"
38
+ suffix = " --package" if self.kind == "package" else ""
39
+ return f"{prefix} {self.name}{suffix}"
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class SandboxProviderMetadata:
44
+ """Static description of a sandbox provider used by the registry.
45
+
46
+ Lets the CLI and registry describe built-in and config providers without
47
+ instantiating them (which may require credentials or optional
48
+ dependencies). Entry-point providers expose their own instance via the
49
+ `SandboxProvider.metadata` property, which the registry reads only when it
50
+ already needs to construct the provider.
51
+ """
52
+
53
+ name: str
54
+ working_dir: str
55
+ install: SandboxInstallHint | None = None
56
+ supports_sandbox_id: bool = True
57
+ supports_snapshot_name: bool = False
58
+ backend_module: str | None = None
59
+ """Importable backend module checked by the pre-flight dependency probe.
60
+
61
+ `None` skips the probe (e.g. bundled providers, or third-party providers
62
+ whose package is only resolved when the provider is constructed).
63
+ """
64
+
65
+
66
+ class SandboxError(Exception):
67
+ """Base error for sandbox provider operations."""
68
+
69
+ @property
70
+ def original_exc(self) -> BaseException | None:
71
+ """Original exception that caused this error, if any."""
72
+ return self.__cause__
73
+
74
+
75
+ class SandboxNotFoundError(SandboxError):
76
+ """Raised when the requested sandbox cannot be found."""
77
+
78
+
79
+ class SandboxProvider(ABC):
80
+ """Interface for creating and deleting sandbox backends."""
81
+
82
+ @property
83
+ def metadata(self) -> SandboxProviderMetadata | None:
84
+ """Static metadata describing this provider.
85
+
86
+ Third-party providers published under the
87
+ `deepagents_code.sandbox_providers` entry-point group override this so
88
+ the registry can surface their working directory and capability flags
89
+ (snapshot/sandbox-id support) instead of falling back to a generic
90
+ placeholder. Returns `None` by default; the registry then synthesizes a
91
+ minimal default.
92
+ """
93
+ return None
94
+
95
+ @abstractmethod
96
+ def get_or_create(
97
+ self,
98
+ *,
99
+ sandbox_id: str | None = None,
100
+ **kwargs: Any,
101
+ ) -> SandboxBackendProtocol:
102
+ """Get an existing sandbox, or create one if needed."""
103
+ raise NotImplementedError
104
+
105
+ @abstractmethod
106
+ def delete(
107
+ self,
108
+ *,
109
+ sandbox_id: str,
110
+ **kwargs: Any,
111
+ ) -> None:
112
+ """Delete a sandbox by id."""
113
+ raise NotImplementedError
114
+
115
+ async def aget_or_create(
116
+ self,
117
+ *,
118
+ sandbox_id: str | None = None,
119
+ **kwargs: Any,
120
+ ) -> SandboxBackendProtocol:
121
+ """Async wrapper around get_or_create.
122
+
123
+ Returns:
124
+ The created or existing sandbox backend.
125
+ """
126
+ return await asyncio.to_thread(
127
+ self.get_or_create, sandbox_id=sandbox_id, **kwargs
128
+ )
129
+
130
+ async def adelete(
131
+ self,
132
+ *,
133
+ sandbox_id: str,
134
+ **kwargs: Any,
135
+ ) -> None:
136
+ """Async wrapper around delete."""
137
+ await asyncio.to_thread(self.delete, sandbox_id=sandbox_id, **kwargs)
@@ -0,0 +1,350 @@
1
+ """Discovery and instantiation of sandbox providers.
2
+
3
+ Merges three provider sources into one registry:
4
+
5
+ 1. Built-in providers curated in this repo (installed as `deepagents-code`
6
+ extras).
7
+ 2. Entry-point providers published by third-party packages under the
8
+ `deepagents_code.sandbox_providers` group.
9
+ 3. Config-declared providers from `[sandboxes.providers]` in
10
+ `~/.deepagents/config.toml` (escape hatch for internal/local packages).
11
+
12
+ Precedence on name collision: config > entry point > built-in, so a user can
13
+ always override discovery via their config file.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import importlib
19
+ import importlib.metadata
20
+ import logging
21
+ from typing import TYPE_CHECKING
22
+
23
+ from deepagents_code.integrations.sandbox_config import SandboxConfig
24
+ from deepagents_code.integrations.sandbox_provider import (
25
+ SandboxInstallHint,
26
+ SandboxProvider,
27
+ SandboxProviderMetadata,
28
+ )
29
+
30
+ if TYPE_CHECKING:
31
+ from pathlib import Path
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+ ENTRY_POINT_GROUP = "deepagents_code.sandbox_providers"
36
+ """Entry-point group third-party packages publish providers under."""
37
+
38
+ BUILTIN_METADATA: dict[str, SandboxProviderMetadata] = {
39
+ "agentcore": SandboxProviderMetadata(
40
+ name="agentcore",
41
+ working_dir="/tmp", # noqa: S108 # AgentCore Code Interpreter working directory
42
+ install=SandboxInstallHint(kind="extra", name="agentcore"),
43
+ supports_sandbox_id=False,
44
+ backend_module="langchain_agentcore_codeinterpreter",
45
+ ),
46
+ "daytona": SandboxProviderMetadata(
47
+ name="daytona",
48
+ working_dir="/home/daytona",
49
+ install=SandboxInstallHint(kind="extra", name="daytona"),
50
+ backend_module="langchain_daytona",
51
+ ),
52
+ "langsmith": SandboxProviderMetadata(
53
+ name="langsmith",
54
+ working_dir="/root", # `$HOME` in the LangSmith sandbox
55
+ # Bundled with `deepagents-code` via `langsmith[sandbox]`; no extra.
56
+ supports_snapshot_name=True,
57
+ ),
58
+ "modal": SandboxProviderMetadata(
59
+ name="modal",
60
+ working_dir="/workspace",
61
+ install=SandboxInstallHint(kind="extra", name="modal"),
62
+ backend_module="langchain_modal",
63
+ ),
64
+ "runloop": SandboxProviderMetadata(
65
+ name="runloop",
66
+ working_dir="/home/user",
67
+ install=SandboxInstallHint(kind="extra", name="runloop"),
68
+ supports_snapshot_name=True,
69
+ backend_module="langchain_runloop",
70
+ ),
71
+ "vercel": SandboxProviderMetadata(
72
+ name="vercel",
73
+ working_dir="/vercel/sandbox",
74
+ install=SandboxInstallHint(kind="extra", name="vercel"),
75
+ supports_sandbox_id=True,
76
+ supports_snapshot_name=False,
77
+ backend_module="langchain_vercel_sandbox",
78
+ ),
79
+ }
80
+ """Metadata for curated built-in providers, keyed by provider name."""
81
+
82
+
83
+ def _load_class(class_path: str) -> type:
84
+ """Import a `module.path:ClassName` provider class.
85
+
86
+ Args:
87
+ class_path: Fully-qualified class path.
88
+
89
+ Returns:
90
+ The imported class object.
91
+
92
+ Raises:
93
+ ValueError: If `class_path` is malformed.
94
+ ImportError: If the module cannot be imported or lacks the class.
95
+ """
96
+ if ":" not in class_path:
97
+ msg = (
98
+ f"Invalid class_path '{class_path}': must be in "
99
+ "module.path:ClassName format"
100
+ )
101
+ raise ValueError(msg)
102
+ module_path, class_name = class_path.rsplit(":", 1)
103
+ module = importlib.import_module(module_path)
104
+ cls = getattr(module, class_name, None)
105
+ if cls is None or not isinstance(cls, type):
106
+ msg = f"Class '{class_name}' not found in module '{module_path}'"
107
+ raise ImportError(msg)
108
+ return cls
109
+
110
+
111
+ def _provider_metadata(provider: SandboxProvider, name: str) -> SandboxProviderMetadata:
112
+ """Extract metadata from a provider instance or class.
113
+
114
+ Providers may expose a `metadata` attribute/property; otherwise a minimal
115
+ default is synthesized.
116
+
117
+ Args:
118
+ provider: Provider instance.
119
+ name: Provider name to use when synthesizing defaults.
120
+
121
+ Returns:
122
+ The provider's metadata.
123
+ """
124
+ meta = getattr(provider, "metadata", None)
125
+ if isinstance(meta, SandboxProviderMetadata):
126
+ return meta
127
+ return SandboxProviderMetadata(name=name, working_dir="/workspace")
128
+
129
+
130
+ class SandboxRegistry:
131
+ """Merged view of built-in, entry-point, and config sandbox providers."""
132
+
133
+ def __init__(
134
+ self,
135
+ *,
136
+ config: SandboxConfig | None = None,
137
+ include_entry_points: bool = True,
138
+ ) -> None:
139
+ """Build the registry.
140
+
141
+ Args:
142
+ config: Parsed sandbox config. Loaded from the default path when
143
+ omitted.
144
+ include_entry_points: Whether to discover entry-point providers.
145
+ Disabled in tests that need a deterministic provider set.
146
+ """
147
+ self._config = config if config is not None else SandboxConfig.load()
148
+ self._include_entry_points = include_entry_points
149
+ self._entry_points: dict[str, importlib.metadata.EntryPoint] = (
150
+ self._discover_entry_points() if include_entry_points else {}
151
+ )
152
+
153
+ @classmethod
154
+ def load(cls, config_path: Path | None = None) -> SandboxRegistry:
155
+ """Build a registry from the config file at `config_path`.
156
+
157
+ Args:
158
+ config_path: Path to config file. Defaults to the user config.
159
+
160
+ Returns:
161
+ A new `SandboxRegistry`.
162
+ """
163
+ return cls(config=SandboxConfig.load(config_path))
164
+
165
+ @staticmethod
166
+ def _discover_entry_points() -> dict[str, importlib.metadata.EntryPoint]:
167
+ """Return entry-point providers keyed by name (best-effort)."""
168
+ found: dict[str, importlib.metadata.EntryPoint] = {}
169
+ try:
170
+ entries = importlib.metadata.entry_points(group=ENTRY_POINT_GROUP)
171
+ except Exception:
172
+ logger.warning("Failed to discover sandbox entry points", exc_info=True)
173
+ return found
174
+ for entry in entries:
175
+ found[entry.name] = entry
176
+ return found
177
+
178
+ @property
179
+ def default(self) -> str | None:
180
+ """The configured default provider, if any."""
181
+ return self._config.default
182
+
183
+ @property
184
+ def config_error(self) -> str | None:
185
+ """Why the config file failed to load, if it existed but couldn't parse.
186
+
187
+ `None` when the config parsed cleanly or simply wasn't present.
188
+ """
189
+ return self._config.parse_error
190
+
191
+ def available_providers(self) -> list[str]:
192
+ """Return all known provider names, sorted."""
193
+ names = (
194
+ set(BUILTIN_METADATA)
195
+ | set(self._entry_points)
196
+ | set(self._config.providers)
197
+ )
198
+ return sorted(names)
199
+
200
+ def is_available(self, name: str) -> bool:
201
+ """Return whether `name` resolves to a known provider."""
202
+ return (
203
+ name in self._config.providers
204
+ or name in self._entry_points
205
+ or name in BUILTIN_METADATA
206
+ )
207
+
208
+ def get_metadata(self, name: str) -> SandboxProviderMetadata | None:
209
+ """Return metadata for `name`.
210
+
211
+ Config providers may override `working_dir`, `package`, and capability
212
+ flags. Entry-point providers are probed for advertised metadata and
213
+ take precedence over built-ins with the same name.
214
+
215
+ Args:
216
+ name: Provider name.
217
+
218
+ Returns:
219
+ The provider's metadata, or `None` if unknown.
220
+ """
221
+ config_entry = self._config.providers.get(name)
222
+ if config_entry is not None:
223
+ base = BUILTIN_METADATA.get(name)
224
+ package = config_entry.get("package")
225
+ return SandboxProviderMetadata(
226
+ name=name,
227
+ working_dir=config_entry.get(
228
+ "working_dir", base.working_dir if base else "/workspace"
229
+ ),
230
+ install=(
231
+ SandboxInstallHint(kind="package", name=package)
232
+ if package
233
+ else (base.install if base else None)
234
+ ),
235
+ supports_sandbox_id=config_entry.get(
236
+ "supports_sandbox_id",
237
+ base.supports_sandbox_id if base else True,
238
+ ),
239
+ supports_snapshot_name=config_entry.get(
240
+ "supports_snapshot_name",
241
+ base.supports_snapshot_name if base else False,
242
+ ),
243
+ # Carry the built-in's probe module so a config override of a
244
+ # built-in keeps its dependency pre-flight check. A pure config
245
+ # provider (no base) leaves this `None`; its package is resolved
246
+ # when the provider class is imported.
247
+ backend_module=base.backend_module if base else None,
248
+ )
249
+ if name in self._entry_points:
250
+ return self.provider_metadata(name)
251
+ if name in BUILTIN_METADATA:
252
+ return BUILTIN_METADATA[name]
253
+ return None
254
+
255
+ def get_params(self, name: str) -> dict[str, object]:
256
+ """Return config `params` forwarded to the provider's `get_or_create()`."""
257
+ return self._config.get_params(name)
258
+
259
+ def create_provider(self, name: str) -> SandboxProvider:
260
+ """Instantiate the provider named `name`.
261
+
262
+ Resolution order: config `class_path` > entry point > built-in.
263
+
264
+ Args:
265
+ name: Provider name.
266
+
267
+ Returns:
268
+ A `SandboxProvider` instance. Propagates `ImportError` from
269
+ `_load_class` / `EntryPoint.load` if a config or entry-point
270
+ class cannot be imported.
271
+
272
+ Raises:
273
+ ValueError: If `name` is unknown or a config provider omits
274
+ `class_path`.
275
+ """
276
+ config_entry = self._config.providers.get(name)
277
+ if config_entry is not None:
278
+ class_path = config_entry.get("class_path")
279
+ if not class_path:
280
+ msg = f"Sandbox provider '{name}' config is missing 'class_path'"
281
+ raise ValueError(msg)
282
+ return _load_class(class_path)()
283
+
284
+ entry = self._entry_points.get(name)
285
+ if entry is not None:
286
+ return entry.load()()
287
+
288
+ if name in BUILTIN_METADATA:
289
+ return _create_builtin_provider(name)
290
+
291
+ msg = (
292
+ f"Unknown sandbox provider: {name}. "
293
+ f"Available providers: {', '.join(self.available_providers())}"
294
+ )
295
+ raise ValueError(msg)
296
+
297
+ def provider_metadata(self, name: str) -> SandboxProviderMetadata:
298
+ """Return authoritative metadata for `name`.
299
+
300
+ Config providers are described statically. Entry-point providers are
301
+ instantiated so capability flags they expose via a `metadata` attribute
302
+ take effect; on failure this falls back to the static placeholder.
303
+ Built-in metadata is used only when no entry point overrides that name.
304
+
305
+ Args:
306
+ name: Provider name.
307
+
308
+ Returns:
309
+ The provider's metadata.
310
+
311
+ Raises:
312
+ ValueError: If `name` is unknown.
313
+ """
314
+ if name in self._config.providers or (
315
+ name in BUILTIN_METADATA and name not in self._entry_points
316
+ ):
317
+ meta = self.get_metadata(name)
318
+ if meta is not None:
319
+ return meta
320
+ if name in self._entry_points:
321
+ try:
322
+ provider = self.create_provider(name)
323
+ except Exception: # noqa: BLE001 # Metadata probe must not crash discovery
324
+ logger.debug("Could not instantiate provider %r for metadata", name)
325
+ return SandboxProviderMetadata(name=name, working_dir="/workspace")
326
+ return _provider_metadata(provider, name)
327
+ meta = self.get_metadata(name)
328
+ if meta is None:
329
+ msg = f"Unknown sandbox provider: {name}"
330
+ raise ValueError(msg)
331
+ return meta
332
+
333
+
334
+ def _create_builtin_provider(name: str) -> SandboxProvider:
335
+ """Instantiate a built-in provider class (lazy import avoids cycles).
336
+
337
+ Returns:
338
+ The built-in `SandboxProvider` instance for `name`.
339
+ """
340
+ from deepagents_code.integrations import sandbox_factory
341
+
342
+ builders = {
343
+ "agentcore": sandbox_factory._AgentCoreProvider,
344
+ "daytona": sandbox_factory._DaytonaProvider,
345
+ "langsmith": sandbox_factory._LangSmithProvider,
346
+ "modal": sandbox_factory._ModalProvider,
347
+ "runloop": sandbox_factory._RunloopProvider,
348
+ "vercel": sandbox_factory._VercelProvider,
349
+ }
350
+ return builders[name]()
@@ -0,0 +1,176 @@
1
+ """iTerm2 cursor guide workaround for Textual alternate-screen rendering."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+
8
+ # iTerm2's cursor guide (highlight cursor line) causes visual artifacts when
9
+ # Textual takes over the terminal in alternate screen mode. We disable it at
10
+ # module load and restore it on exit only if the active/default iTerm2 profile
11
+ # had cursor guide enabled before launch.
12
+
13
+ # Detection: check env vars AND that stderr is a TTY (avoids false positives
14
+ # when env vars are inherited but running in non-TTY context like CI).
15
+ _IS_ITERM = (
16
+ (
17
+ os.environ.get("LC_TERMINAL", "") == "iTerm2"
18
+ or os.environ.get("TERM_PROGRAM", "") == "iTerm.app"
19
+ )
20
+ and hasattr(os, "isatty")
21
+ and os.isatty(2)
22
+ )
23
+
24
+ # iTerm2 cursor guide escape sequences (OSC 1337)
25
+ # Format: OSC 1337 ; HighlightCursorLine=<yes|no> ST
26
+ # Where OSC = ESC ] (0x1b 0x5d) and ST = ESC \ (0x1b 0x5c)
27
+ _ITERM_CURSOR_GUIDE_OFF = "\x1b]1337;HighlightCursorLine=no\x1b\\"
28
+ _ITERM_CURSOR_GUIDE_ON = "\x1b]1337;HighlightCursorLine=yes\x1b\\"
29
+ _ITERM_PREFS_PATH = Path("~/Library/Preferences/com.googlecode.iterm2.plist")
30
+
31
+
32
+ def _write_iterm_escape(sequence: str) -> None:
33
+ """Write an iTerm2 escape sequence to stderr.
34
+
35
+ Silently fails if the terminal is unavailable (redirected, closed, broken
36
+ pipe). This is a cosmetic feature, so failures should never crash the app.
37
+ """
38
+ if not _IS_ITERM:
39
+ return
40
+ try:
41
+ import sys
42
+
43
+ if sys.__stderr__ is not None:
44
+ sys.__stderr__.write(sequence)
45
+ sys.__stderr__.flush()
46
+ except OSError:
47
+ # Terminal may be unavailable (redirected, closed, broken pipe).
48
+ pass
49
+
50
+
51
+ def _plist_bool(value: object) -> bool | None:
52
+ """Return a plist boolean/int value as `bool`, or `None` if not boolean-like."""
53
+ if isinstance(value, bool):
54
+ return value
55
+ if isinstance(value, int):
56
+ return value != 0
57
+ return None
58
+
59
+
60
+ def _profile_uses_cursor_guide(profile: dict[str, object]) -> bool:
61
+ """Return whether an iTerm2 profile has cursor guide enabled."""
62
+ enabled = _plist_bool(profile.get("Use Cursor Guide"))
63
+ if enabled is not None:
64
+ return enabled
65
+
66
+ # Newer iTerm2 profiles may carry separate light/dark values. If the shared
67
+ # value is absent, restoring when either variant is enabled preserves the
68
+ # user's visible default better than losing the guide for both appearances.
69
+ return any(
70
+ _plist_bool(profile.get(key)) is True
71
+ for key in ("Use Cursor Guide (Dark)", "Use Cursor Guide (Light)")
72
+ )
73
+
74
+
75
+ def _coerce_profile(raw: object) -> dict[str, object] | None:
76
+ """Return a string-keyed profile dictionary from raw plist data."""
77
+ if not isinstance(raw, dict):
78
+ return None
79
+ return {key: value for key, value in raw.items() if isinstance(key, str)}
80
+
81
+
82
+ def _find_iterm_profile(
83
+ profiles: list[object], *, name: str, guid: str
84
+ ) -> dict[str, object] | None:
85
+ """Find the current iTerm2 profile by name, then by default profile GUID.
86
+
87
+ Args:
88
+ profiles: Profile entries from iTerm2 preferences.
89
+ name: Active profile name from `ITERM_PROFILE`.
90
+ guid: Default profile GUID from iTerm2 preferences.
91
+
92
+ Returns:
93
+ The matching profile dictionary, or `None` when no match is found.
94
+ """
95
+ for raw in profiles:
96
+ profile = _coerce_profile(raw)
97
+ if profile is None:
98
+ continue
99
+ if profile.get("Name") == name:
100
+ return profile
101
+ for raw in profiles:
102
+ profile = _coerce_profile(raw)
103
+ if profile is None:
104
+ continue
105
+ if profile.get("Guid") == guid:
106
+ return profile
107
+ return None
108
+
109
+
110
+ def _iterm_profile_cursor_guide_enabled() -> bool:
111
+ """Infer whether iTerm2 cursor guide was enabled before startup.
112
+
113
+ iTerm2's OSC 1337 `HighlightCursorLine` command can set the guide to yes/no
114
+ but does not report the current state. The best cheap signal available at
115
+ startup is the active profile preference, exposed in the iTerm2 plist. The
116
+ `ITERM_PROFILE` environment variable is set by iTerm2; when it is missing,
117
+ fall back to the default profile GUID in preferences.
118
+
119
+ Returns:
120
+ `True` if the matched iTerm2 profile has cursor guide enabled.
121
+ """
122
+ if not _IS_ITERM:
123
+ return False
124
+
125
+ import plistlib
126
+
127
+ try:
128
+ with _ITERM_PREFS_PATH.expanduser().open("rb") as f:
129
+ prefs = plistlib.load(f)
130
+ except (OSError, plistlib.InvalidFileException, ValueError):
131
+ return False
132
+
133
+ if not isinstance(prefs, dict):
134
+ return False
135
+
136
+ profiles = prefs.get("New Bookmarks")
137
+ if not isinstance(profiles, list):
138
+ return False
139
+
140
+ name = os.environ.get("ITERM_PROFILE", "").strip()
141
+ guid = str(prefs.get("Default Bookmark Guid", ""))
142
+ profile = _find_iterm_profile(profiles, name=name, guid=guid)
143
+ if profile is None:
144
+ return False
145
+ return _profile_uses_cursor_guide(profile)
146
+
147
+
148
+ _RESTORE_ITERM_CURSOR_GUIDE = _iterm_profile_cursor_guide_enabled()
149
+ _ITERM_CURSOR_GUIDE_RESTORED = False
150
+
151
+
152
+ def restore_iterm_cursor_guide() -> None:
153
+ """Restore iTerm2 cursor guide when launch-time profile state required it."""
154
+ global _ITERM_CURSOR_GUIDE_RESTORED # noqa: PLW0603 # atexit/exit idempotence
155
+
156
+ if not _RESTORE_ITERM_CURSOR_GUIDE or _ITERM_CURSOR_GUIDE_RESTORED:
157
+ return
158
+ _ITERM_CURSOR_GUIDE_RESTORED = True
159
+ _write_iterm_escape(_ITERM_CURSOR_GUIDE_ON)
160
+
161
+
162
+ def _disable_iterm_cursor_guide() -> None:
163
+ """Disable iTerm2 cursor guide only when the module has a restore path."""
164
+ if not _RESTORE_ITERM_CURSOR_GUIDE:
165
+ return
166
+ _write_iterm_escape(_ITERM_CURSOR_GUIDE_OFF)
167
+
168
+
169
+ # Disable cursor guide at module load (before Textual takes over), but only
170
+ # when launch-time state detection confirmed that exit cleanup will restore it.
171
+ _disable_iterm_cursor_guide()
172
+
173
+ if _RESTORE_ITERM_CURSOR_GUIDE:
174
+ import atexit
175
+
176
+ atexit.register(restore_iterm_cursor_guide)