Repository navigation
Expand file tree
/
Copy pathmodels.py
More file actions
247 lines (182 loc) · 6.85 KB
/
Copy pathmodels.py
File metadata and controls
247 lines (182 loc) · 6.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
"""Data models for plugins."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Literal
from deepagents_code.json_types import JsonObject, JsonValue # noqa: TC001, F401
if TYPE_CHECKING:
from pathlib import Path
MarketplaceSourceType = Literal["directory", "file", "github", "git", "url"]
ExternalPluginRepositorySourceType = Literal["github", "git-subdir", "url"]
UnsupportedComponent = Literal["agents", "commands"]
"""Plugin component directory that `deepagents-code` does not load."""
@dataclass(frozen=True, slots=True, kw_only=True)
class LocalMarketplaceSource:
"""Local directory or JSON file used as a marketplace source."""
source_type: Literal["directory", "file"]
value: str
@dataclass(frozen=True, slots=True, kw_only=True)
class RepositoryMarketplaceSource:
"""GitHub or Git repository used as a marketplace source.
`ref` selects an optional branch or tag. Commit SHA checkout is not part of
the shallow-clone flow.
"""
source_type: Literal["github", "git"]
value: str
ref: str | None
@dataclass(frozen=True, slots=True, kw_only=True)
class UrlMarketplaceSource:
"""Marketplace manifest downloaded from an HTTP URL."""
source_type: Literal["url"]
value: str
MarketplaceSource = (
LocalMarketplaceSource | RepositoryMarketplaceSource | UrlMarketplaceSource
)
@dataclass(frozen=True, slots=True, kw_only=True)
class PluginManifest:
"""Parsed plugin manifest.
Attributes:
name: Plugin name from the manifest, or `None` for manifest-less plugins.
display_name: Optional human-readable label from `displayName`.
version: Version string from the plugin manifest.
component_paths: Validated skill, MCP, and hook paths keyed by component
name.
inline_mcp: Inline MCP servers declared in the manifest.
inline_hooks: Inline hook configuration declared in the manifest, in
`hooks.json` document form.
python_extensions: Python extension entry files declared in dcode's
namespaced manifest settings.
auto_update: Whether this plugin permits automatic updates.
"""
name: str | None
version: str | None
component_paths: dict[str, tuple[Path, ...]]
inline_mcp: JsonObject
inline_hooks: JsonObject = field(default_factory=dict)
python_extensions: tuple[Path, ...] = ()
display_name: str | None = None
auto_update: bool = False
@dataclass(frozen=True, slots=True, kw_only=True)
class ComponentInventory:
"""Inventory of supported plugin components.
`unsupported` lists plugin component directories that `deepagents-code` does
not load (e.g. `agents/`, `commands/`).
"""
skills: tuple[Path, ...] = ()
mcp_files: tuple[Path, ...] = ()
hook_files: tuple[Path, ...] = ()
unsupported: tuple[UnsupportedComponent, ...] = ()
warnings: tuple[str, ...] = ()
@dataclass(frozen=True, slots=True, kw_only=True)
class PluginInstance:
"""A discovered plugin ready to feed dcode adapters.
Attributes:
plugin_id: Stable id in `{name}@{marketplace}` form.
name: Plugin namespace name.
marketplace: Parent marketplace used for identity and namespacing.
version: Version declared by the plugin manifest, if any.
root: Plugin root directory.
data_dir: Writable data directory for this plugin.
manifest: Parsed manifest, if any.
inventory: Component inventory.
"""
plugin_id: str
name: str
marketplace: str
version: str | None
root: Path
data_dir: Path
manifest: PluginManifest | None
inventory: ComponentInventory
def __post_init__(self) -> None:
"""Validate the canonical plugin identity.
Raises:
ValueError: If `plugin_id` disagrees with `name` and `marketplace`.
"""
expected = f"{self.name}@{self.marketplace}"
if self.plugin_id != expected:
msg = f"Plugin id {self.plugin_id!r} does not match {expected!r}"
raise ValueError(msg)
@dataclass(frozen=True, slots=True, kw_only=True)
class LocalPluginSource:
"""A plugin stored relative to its marketplace."""
source_type: Literal["local"]
path: str
@dataclass(frozen=True, slots=True, kw_only=True)
class GithubPluginSource:
"""A plugin sourced from a GitHub repository."""
source_type: Literal["github"]
repo: str
ref: str | None = None
path: str | None = None
@dataclass(frozen=True, slots=True, kw_only=True)
class GitSubdirectoryPluginSource:
"""A plugin sourced from a subdirectory in a Git repository."""
source_type: Literal["git-subdir"]
url: str
ref: str | None = None
path: str | None = None
@dataclass(frozen=True, slots=True, kw_only=True)
class UrlPluginSource:
"""A plugin sourced from a Git repository URL."""
source_type: Literal["url"]
url: str
ref: str | None = None
path: str | None = None
PluginSource = (
LocalPluginSource
| GithubPluginSource
| GitSubdirectoryPluginSource
| UrlPluginSource
)
@dataclass(frozen=True, slots=True, kw_only=True)
class MarketplacePluginEntry:
"""A catalog entry from a marketplace manifest."""
name: str
source: PluginSource
description: str | None = None
author: str | JsonObject | None = None
display_name: str | None = None
@dataclass(frozen=True, slots=True, kw_only=True)
class PluginMarketplace:
"""A parsed marketplace manifest."""
name: str
root: Path
manifest_path: Path
metadata: JsonObject
plugins: tuple[MarketplacePluginEntry, ...]
warnings: tuple[str, ...] = ()
@dataclass(frozen=True, slots=True, kw_only=True)
class MarketplaceRecord:
"""Persisted marketplace source record."""
name: str
source_type: MarketplaceSourceType
source: str
install_location: str
ref: str | None = None
@dataclass(frozen=True, slots=True, kw_only=True)
class InstalledPluginEntry:
"""Install record for a plugin.
`version` is the value declared by the plugin manifest, if any.
"""
install_path: str
version: str | None
@dataclass(frozen=True, slots=True, kw_only=True)
class PluginDiscoveryResult:
"""Result from plugin discovery."""
plugins: tuple[PluginInstance, ...]
warnings: tuple[str, ...] = ()
def split_plugin_id(plugin_id: str) -> tuple[str, str]:
"""Split a plugin id in `{plugin}@{marketplace}` form.
Returns:
Plugin and marketplace names.
Raises:
ValueError: If either part is missing.
"""
if "@" not in plugin_id:
msg = f"Invalid plugin id {plugin_id!r}; expected name@marketplace"
raise ValueError(msg)
plugin, marketplace = plugin_id.rsplit("@", 1)
if not plugin or not marketplace:
msg = f"Invalid plugin id {plugin_id!r}; expected name@marketplace"
raise ValueError(msg)
return plugin, marketplace