Files
deepagents/libs/code/deepagents_code/plugins/substitution.py
Johannes du Plessis 0187d14b83 feat(code): add plugin marketplace support (#4554)
`deepagents-code` now supports experimental plugin marketplaces with
namespaced skills and MCP servers. Enable with
`DEEPAGENTS_CODE_EXPERIMENTAL=1`.

Docs: https://github.com/langchain-ai/docs/pull/4905

---

`deepagents-code` can now manage experimental plugin marketplaces and
load installed plugins that contribute namespaced skills and MCP
servers. The new plugin manager establishes the `tui/modals`,
`tui/screens`, and `tui/widgets` UI convention, with colocated styling
and focused state/content modules.

- Supports local, GitHub, Git, and marketplace JSON sources.
- Adds install, enable, disable, uninstall, and safe marketplace removal
through `/plugins` and `dcode plugin`.
- Reloads plugin skills and plugin MCP configuration with `/reload` when
experimental mode is enabled.
- Namespaces plugin skills as `{plugin_id}:{skill_name}` through a
code-local `PluginSkillsMiddleware` adapter, so no SDK changes are
required.

Made by [Open
SWE](https://openswe.vercel.app/agents/019f5dea-0f1f-73fe-9803-32d1b13fbd4c)

---------

Signed-off-by: Johannes du Plessis <johannes@langchain.dev>
Co-authored-by: Alexander Olsen <aolsenjazz@gmail.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Co-authored-by: open-swe[bot] <215916821+open-swe[bot]@users.noreply.github.com>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
2026-07-15 09:11:28 -04:00

108 lines
2.9 KiB
Python

"""Variable substitution for plugin-provided configuration."""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from pathlib import Path
from deepagents_code.plugins.models import JsonValue
def plugin_environment(
*, plugin_root: Path, plugin_data: Path, project_dir: Path | None = None
) -> dict[str, str]:
"""Build environment variables exposed to plugin subprocesses.
Args:
plugin_root: Plugin root directory.
plugin_data: Plugin data directory.
project_dir: Optional project directory.
Returns:
Environment variables for plugin subprocesses.
"""
root = str(plugin_root)
data = str(plugin_data)
env = {
"CLAUDE_PLUGIN_ROOT": root,
"CLAUDE_PLUGIN_DATA": data,
"PLUGIN_ROOT": root,
"PLUGIN_DATA": data,
}
if project_dir is not None:
env["CLAUDE_PROJECT_DIR"] = str(project_dir)
return env
def substitute_string(
value: str, *, plugin_root: Path, plugin_data: Path, project_dir: Path | None = None
) -> str:
"""Substitute plugin path variables in a string.
Args:
value: String to transform.
plugin_root: Plugin root directory.
plugin_data: Plugin data directory.
project_dir: Optional project directory.
Returns:
String with supported plugin variables substituted.
"""
env = plugin_environment(
plugin_root=plugin_root, plugin_data=plugin_data, project_dir=project_dir
)
result = value
for key, replacement in env.items():
result = result.replace(f"${{{key}}}", replacement)
return result
def substitute_json(
value: JsonValue,
*,
plugin_root: Path,
plugin_data: Path,
project_dir: Path | None = None,
) -> JsonValue:
"""Substitute plugin variables throughout a JSON-compatible value.
Args:
value: JSON-compatible value to transform.
plugin_root: Plugin root directory.
plugin_data: Plugin data directory.
project_dir: Optional project directory.
Returns:
Value with strings recursively substituted.
"""
if isinstance(value, str):
return substitute_string(
value,
plugin_root=plugin_root,
plugin_data=plugin_data,
project_dir=project_dir,
)
if isinstance(value, list):
return [
substitute_json(
item,
plugin_root=plugin_root,
plugin_data=plugin_data,
project_dir=project_dir,
)
for item in value
]
if isinstance(value, dict):
return {
key: substitute_json(
item,
plugin_root=plugin_root,
plugin_data=plugin_data,
project_dir=project_dir,
)
for key, item in value.items()
}
return value