pyflyrail 0.1.0__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.
- flyrail/__init__.py +231 -0
- flyrail/_codec.py +109 -0
- flyrail/_config_manifest.py +322 -0
- flyrail/_filesystem.py +113 -0
- flyrail/_hook_translation.py +346 -0
- flyrail/_inventory.py +39 -0
- flyrail/_json_document.py +240 -0
- flyrail/_lifecycle.py +910 -0
- flyrail/_manifest.py +71 -0
- flyrail/_mcp_translation.py +169 -0
- flyrail/_memory.py +37 -0
- flyrail/_observation.py +177 -0
- flyrail/_resource_io.py +264 -0
- flyrail/_resource_models.py +333 -0
- flyrail/_resource_plan.py +482 -0
- flyrail/_resource_transaction.py +372 -0
- flyrail/_schema.py +133 -0
- flyrail/_security.py +318 -0
- flyrail/_sources.py +276 -0
- flyrail/_toml_document.py +691 -0
- flyrail/_toml_layout.py +73 -0
- flyrail/_validation.py +54 -0
- flyrail/artifacts.py +279 -0
- flyrail/authority.py +255 -0
- flyrail/bundle.py +327 -0
- flyrail/configuration.py +252 -0
- flyrail/content.py +226 -0
- flyrail/destinations.py +233 -0
- flyrail/editors.py +1096 -0
- flyrail/hooks.py +39 -0
- flyrail/inspection.py +270 -0
- flyrail/lifecycle.py +148 -0
- flyrail/models.py +74 -0
- flyrail/observations.py +193 -0
- flyrail/py.typed +0 -0
- flyrail/rendered.py +206 -0
- flyrail/runtime/bridge.mts +145 -0
- flyrail/runtime/process.mts +38 -0
- flyrail/runtime/runner.mts +192 -0
- flyrail/targets.py +197 -0
- flyrail/translation.py +383 -0
- flyrail/values.py +168 -0
- pyflyrail-0.1.0.dist-info/METADATA +122 -0
- pyflyrail-0.1.0.dist-info/RECORD +46 -0
- pyflyrail-0.1.0.dist-info/WHEEL +4 -0
- pyflyrail-0.1.0.dist-info/licenses/LICENSE +21 -0
flyrail/__init__.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
from flyrail._inventory import InventoryEntry
|
|
2
|
+
from flyrail._resource_models import Ancestor, ResourceKind, ResourceRef
|
|
3
|
+
from flyrail.artifacts import (
|
|
4
|
+
Artifact,
|
|
5
|
+
AssetRef,
|
|
6
|
+
Audience,
|
|
7
|
+
Command,
|
|
8
|
+
EnvRef,
|
|
9
|
+
Family,
|
|
10
|
+
HookArtifact,
|
|
11
|
+
HookEvent,
|
|
12
|
+
HookOutcome,
|
|
13
|
+
HttpTransport,
|
|
14
|
+
InstructionArtifact,
|
|
15
|
+
McpArtifact,
|
|
16
|
+
NativeArtifact,
|
|
17
|
+
SkillArtifact,
|
|
18
|
+
SupportAsset,
|
|
19
|
+
)
|
|
20
|
+
from flyrail.authority import Claim, Ownership, ResourceAuthority
|
|
21
|
+
from flyrail.bundle import Bundle
|
|
22
|
+
from flyrail.configuration import (
|
|
23
|
+
ClaimObservation,
|
|
24
|
+
InstallationObservation,
|
|
25
|
+
InstallationResult,
|
|
26
|
+
InstallationTarget,
|
|
27
|
+
LifecyclePreview,
|
|
28
|
+
ResourceObservation,
|
|
29
|
+
ResourcePlan,
|
|
30
|
+
ResourceResult,
|
|
31
|
+
)
|
|
32
|
+
from flyrail.content import (
|
|
33
|
+
Content,
|
|
34
|
+
DocumentFormat,
|
|
35
|
+
DocumentSchema,
|
|
36
|
+
FileContent,
|
|
37
|
+
FileMode,
|
|
38
|
+
Key,
|
|
39
|
+
Member,
|
|
40
|
+
Placement,
|
|
41
|
+
Position,
|
|
42
|
+
SectionBoundaries,
|
|
43
|
+
SectionContent,
|
|
44
|
+
Selector,
|
|
45
|
+
StructuredContent,
|
|
46
|
+
TreeContent,
|
|
47
|
+
)
|
|
48
|
+
from flyrail.destinations import (
|
|
49
|
+
Capability,
|
|
50
|
+
RenderContext,
|
|
51
|
+
capabilities,
|
|
52
|
+
instruction_destination,
|
|
53
|
+
mcp_destination,
|
|
54
|
+
)
|
|
55
|
+
from flyrail.editors import (
|
|
56
|
+
Acquisition,
|
|
57
|
+
ArrayPosition,
|
|
58
|
+
CreatedContainer,
|
|
59
|
+
Disposition,
|
|
60
|
+
DocumentEdit,
|
|
61
|
+
DocumentProvenance,
|
|
62
|
+
DocumentRequest,
|
|
63
|
+
EditObservation,
|
|
64
|
+
EditStatus,
|
|
65
|
+
OwnedSelection,
|
|
66
|
+
SelectionSnapshot,
|
|
67
|
+
SharedContent,
|
|
68
|
+
content_claim,
|
|
69
|
+
edit_document,
|
|
70
|
+
)
|
|
71
|
+
from flyrail.hooks import HookRuntime, HookShell
|
|
72
|
+
from flyrail.inspection import inspect, render_skills
|
|
73
|
+
from flyrail.lifecycle import (
|
|
74
|
+
apply_preview,
|
|
75
|
+
inspect_installation,
|
|
76
|
+
install,
|
|
77
|
+
preview,
|
|
78
|
+
preview_removal,
|
|
79
|
+
recover_installation,
|
|
80
|
+
remove,
|
|
81
|
+
sync,
|
|
82
|
+
uninstall,
|
|
83
|
+
update,
|
|
84
|
+
)
|
|
85
|
+
from flyrail.models import BundleEntry, BundleIdentity, OperationStatus, SkillSpec
|
|
86
|
+
from flyrail.observations import (
|
|
87
|
+
Conflict,
|
|
88
|
+
ErrorCode,
|
|
89
|
+
Installation,
|
|
90
|
+
Modification,
|
|
91
|
+
ModificationKind,
|
|
92
|
+
Observation,
|
|
93
|
+
ObservationState,
|
|
94
|
+
TargetError,
|
|
95
|
+
TargetInspection,
|
|
96
|
+
TargetResult,
|
|
97
|
+
)
|
|
98
|
+
from flyrail.rendered import (
|
|
99
|
+
Dependency,
|
|
100
|
+
DependencyMode,
|
|
101
|
+
Notice,
|
|
102
|
+
NoticeKind,
|
|
103
|
+
RenderedArtifact,
|
|
104
|
+
RenderedBundle,
|
|
105
|
+
)
|
|
106
|
+
from flyrail.targets import Agent, Platform, Surface, Target, TargetScope
|
|
107
|
+
from flyrail.translation import render, render_many
|
|
108
|
+
from flyrail.values import (
|
|
109
|
+
ArrayValue,
|
|
110
|
+
ObjectValue,
|
|
111
|
+
Scalar,
|
|
112
|
+
Value,
|
|
113
|
+
freeze_value,
|
|
114
|
+
semantic_bytes,
|
|
115
|
+
value_from_record,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
__all__ = [
|
|
119
|
+
"Acquisition",
|
|
120
|
+
"Agent",
|
|
121
|
+
"Ancestor",
|
|
122
|
+
"ArrayPosition",
|
|
123
|
+
"ArrayValue",
|
|
124
|
+
"Artifact",
|
|
125
|
+
"AssetRef",
|
|
126
|
+
"Audience",
|
|
127
|
+
"Bundle",
|
|
128
|
+
"BundleEntry",
|
|
129
|
+
"BundleIdentity",
|
|
130
|
+
"Capability",
|
|
131
|
+
"Claim",
|
|
132
|
+
"ClaimObservation",
|
|
133
|
+
"Command",
|
|
134
|
+
"Conflict",
|
|
135
|
+
"Content",
|
|
136
|
+
"CreatedContainer",
|
|
137
|
+
"Dependency",
|
|
138
|
+
"DependencyMode",
|
|
139
|
+
"Disposition",
|
|
140
|
+
"DocumentEdit",
|
|
141
|
+
"DocumentFormat",
|
|
142
|
+
"DocumentProvenance",
|
|
143
|
+
"DocumentRequest",
|
|
144
|
+
"DocumentSchema",
|
|
145
|
+
"EditObservation",
|
|
146
|
+
"EditStatus",
|
|
147
|
+
"EnvRef",
|
|
148
|
+
"ErrorCode",
|
|
149
|
+
"Family",
|
|
150
|
+
"FileContent",
|
|
151
|
+
"FileMode",
|
|
152
|
+
"HookArtifact",
|
|
153
|
+
"HookEvent",
|
|
154
|
+
"HookOutcome",
|
|
155
|
+
"HookRuntime",
|
|
156
|
+
"HookShell",
|
|
157
|
+
"HttpTransport",
|
|
158
|
+
"Installation",
|
|
159
|
+
"InstallationObservation",
|
|
160
|
+
"InstallationResult",
|
|
161
|
+
"InstallationTarget",
|
|
162
|
+
"InstructionArtifact",
|
|
163
|
+
"InventoryEntry",
|
|
164
|
+
"Key",
|
|
165
|
+
"LifecyclePreview",
|
|
166
|
+
"McpArtifact",
|
|
167
|
+
"Member",
|
|
168
|
+
"Modification",
|
|
169
|
+
"ModificationKind",
|
|
170
|
+
"NativeArtifact",
|
|
171
|
+
"Notice",
|
|
172
|
+
"NoticeKind",
|
|
173
|
+
"ObjectValue",
|
|
174
|
+
"Observation",
|
|
175
|
+
"ObservationState",
|
|
176
|
+
"OperationStatus",
|
|
177
|
+
"OwnedSelection",
|
|
178
|
+
"Ownership",
|
|
179
|
+
"Placement",
|
|
180
|
+
"Platform",
|
|
181
|
+
"Position",
|
|
182
|
+
"RenderContext",
|
|
183
|
+
"RenderedArtifact",
|
|
184
|
+
"RenderedBundle",
|
|
185
|
+
"ResourceAuthority",
|
|
186
|
+
"ResourceKind",
|
|
187
|
+
"ResourceObservation",
|
|
188
|
+
"ResourcePlan",
|
|
189
|
+
"ResourceRef",
|
|
190
|
+
"ResourceResult",
|
|
191
|
+
"Scalar",
|
|
192
|
+
"SectionBoundaries",
|
|
193
|
+
"SectionContent",
|
|
194
|
+
"SelectionSnapshot",
|
|
195
|
+
"Selector",
|
|
196
|
+
"SharedContent",
|
|
197
|
+
"SkillArtifact",
|
|
198
|
+
"SkillSpec",
|
|
199
|
+
"StructuredContent",
|
|
200
|
+
"SupportAsset",
|
|
201
|
+
"Surface",
|
|
202
|
+
"Target",
|
|
203
|
+
"TargetError",
|
|
204
|
+
"TargetInspection",
|
|
205
|
+
"TargetResult",
|
|
206
|
+
"TargetScope",
|
|
207
|
+
"TreeContent",
|
|
208
|
+
"Value",
|
|
209
|
+
"apply_preview",
|
|
210
|
+
"capabilities",
|
|
211
|
+
"content_claim",
|
|
212
|
+
"edit_document",
|
|
213
|
+
"freeze_value",
|
|
214
|
+
"inspect",
|
|
215
|
+
"inspect_installation",
|
|
216
|
+
"install",
|
|
217
|
+
"instruction_destination",
|
|
218
|
+
"mcp_destination",
|
|
219
|
+
"preview",
|
|
220
|
+
"preview_removal",
|
|
221
|
+
"recover_installation",
|
|
222
|
+
"remove",
|
|
223
|
+
"render",
|
|
224
|
+
"render_many",
|
|
225
|
+
"render_skills",
|
|
226
|
+
"semantic_bytes",
|
|
227
|
+
"sync",
|
|
228
|
+
"uninstall",
|
|
229
|
+
"update",
|
|
230
|
+
"value_from_record",
|
|
231
|
+
]
|
flyrail/_codec.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from dataclasses import fields, is_dataclass
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from flyrail._manifest import _reject_constant, _unique_object
|
|
8
|
+
from flyrail.authority import Claim, Ownership
|
|
9
|
+
from flyrail.content import DocumentFormat, DocumentSchema, Key, Member, SectionBoundaries, Selector
|
|
10
|
+
from flyrail.editors import (
|
|
11
|
+
ArrayPosition,
|
|
12
|
+
CreatedContainer,
|
|
13
|
+
Disposition,
|
|
14
|
+
DocumentProvenance,
|
|
15
|
+
OwnedSelection,
|
|
16
|
+
SelectionSnapshot,
|
|
17
|
+
)
|
|
18
|
+
from flyrail.values import ArrayValue, ObjectValue, Scalar, semantic_record, value_from_record
|
|
19
|
+
|
|
20
|
+
_TYPES: dict[str, Callable[..., object]] = {
|
|
21
|
+
cls.__name__: cls
|
|
22
|
+
for cls in (
|
|
23
|
+
Claim,
|
|
24
|
+
Ownership,
|
|
25
|
+
DocumentFormat,
|
|
26
|
+
DocumentSchema,
|
|
27
|
+
Key,
|
|
28
|
+
Member,
|
|
29
|
+
SectionBoundaries,
|
|
30
|
+
Selector,
|
|
31
|
+
ArrayPosition,
|
|
32
|
+
CreatedContainer,
|
|
33
|
+
Disposition,
|
|
34
|
+
DocumentProvenance,
|
|
35
|
+
OwnedSelection,
|
|
36
|
+
SelectionSnapshot,
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def register(cls: type[object]) -> None:
|
|
42
|
+
_TYPES[cls.__name__] = cls
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def record(value: object) -> object:
|
|
46
|
+
if isinstance(value, Scalar | ObjectValue | ArrayValue):
|
|
47
|
+
return {"value": semantic_record(value)}
|
|
48
|
+
if isinstance(value, StrEnum):
|
|
49
|
+
return {"enum": type(value).__name__, "value": str(value)}
|
|
50
|
+
if is_dataclass(value) and not isinstance(value, type):
|
|
51
|
+
return {
|
|
52
|
+
"type": type(value).__name__,
|
|
53
|
+
"fields": {field.name: record(getattr(value, field.name)) for field in fields(value)},
|
|
54
|
+
}
|
|
55
|
+
if isinstance(value, bytes):
|
|
56
|
+
return {"bytes": value.hex()}
|
|
57
|
+
if isinstance(value, Path):
|
|
58
|
+
return {"path": value.as_posix()}
|
|
59
|
+
if isinstance(value, tuple):
|
|
60
|
+
return [record(item) for item in value]
|
|
61
|
+
if value is None or type(value) in {str, int, bool}:
|
|
62
|
+
return value
|
|
63
|
+
raise TypeError("unsupported state value")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _decode(value: object) -> object:
|
|
67
|
+
if isinstance(value, list):
|
|
68
|
+
return tuple(_decode(item) for item in value)
|
|
69
|
+
if not isinstance(value, dict):
|
|
70
|
+
if value is None or type(value) in {str, int, bool}:
|
|
71
|
+
return value
|
|
72
|
+
raise ValueError("invalid state scalar")
|
|
73
|
+
if value.keys() == {"value"}:
|
|
74
|
+
return value_from_record(value["value"])
|
|
75
|
+
if value.keys() == {"bytes"} and isinstance(value["bytes"], str):
|
|
76
|
+
return bytes.fromhex(value["bytes"])
|
|
77
|
+
if value.keys() == {"path"} and isinstance(value["path"], str):
|
|
78
|
+
path = Path(value["path"])
|
|
79
|
+
if not path.is_absolute() or ".." in path.parts:
|
|
80
|
+
raise ValueError("state paths must be absolute without traversal")
|
|
81
|
+
return path
|
|
82
|
+
if value.keys() == {"enum", "value"} and value["enum"] in _TYPES:
|
|
83
|
+
return _TYPES[value["enum"]](value["value"])
|
|
84
|
+
if value.keys() == {"type", "fields"} and value["type"] in _TYPES:
|
|
85
|
+
constructor = _TYPES[value["type"]]
|
|
86
|
+
arguments = value["fields"]
|
|
87
|
+
if not isinstance(arguments, dict) or not is_dataclass(constructor):
|
|
88
|
+
raise ValueError("invalid state record")
|
|
89
|
+
if arguments.keys() != {field.name for field in fields(constructor)}:
|
|
90
|
+
raise ValueError("state record fields do not match schema")
|
|
91
|
+
return constructor(**{key: _decode(item) for key, item in arguments.items()})
|
|
92
|
+
raise ValueError("unknown state record")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def encode(value: object) -> bytes:
|
|
96
|
+
return (
|
|
97
|
+
json.dumps(record(value), sort_keys=True, separators=(",", ":"), ensure_ascii=False) + "\n"
|
|
98
|
+
).encode()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def decode(data: bytes) -> object:
|
|
102
|
+
try:
|
|
103
|
+
raw = json.loads(data, object_pairs_hook=_unique_object, parse_constant=_reject_constant)
|
|
104
|
+
value = _decode(raw)
|
|
105
|
+
if record(value) != raw:
|
|
106
|
+
raise ValueError("state record is not canonical")
|
|
107
|
+
return value
|
|
108
|
+
except (RecursionError, TypeError, KeyError, AttributeError) as error:
|
|
109
|
+
raise ValueError("invalid state record") from error
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
from typing import Protocol
|
|
2
|
+
|
|
3
|
+
from flyrail._manifest import _object, _string
|
|
4
|
+
from flyrail._validation import validate_relative_path
|
|
5
|
+
from flyrail.artifacts import (
|
|
6
|
+
Artifact,
|
|
7
|
+
AssetRef,
|
|
8
|
+
Audience,
|
|
9
|
+
Command,
|
|
10
|
+
EnvRef,
|
|
11
|
+
Family,
|
|
12
|
+
HookArtifact,
|
|
13
|
+
HookEvent,
|
|
14
|
+
HookOutcome,
|
|
15
|
+
HttpTransport,
|
|
16
|
+
InstructionArtifact,
|
|
17
|
+
McpArtifact,
|
|
18
|
+
NativeArtifact,
|
|
19
|
+
SkillArtifact,
|
|
20
|
+
SupportAsset,
|
|
21
|
+
)
|
|
22
|
+
from flyrail.content import (
|
|
23
|
+
Content,
|
|
24
|
+
DocumentFormat,
|
|
25
|
+
FileContent,
|
|
26
|
+
FileMode,
|
|
27
|
+
Key,
|
|
28
|
+
Member,
|
|
29
|
+
Placement,
|
|
30
|
+
Position,
|
|
31
|
+
SectionBoundaries,
|
|
32
|
+
SectionContent,
|
|
33
|
+
Selector,
|
|
34
|
+
StructuredContent,
|
|
35
|
+
TreeContent,
|
|
36
|
+
)
|
|
37
|
+
from flyrail.models import BundleEntry, BundleIdentity
|
|
38
|
+
from flyrail.rendered import Dependency, DependencyMode
|
|
39
|
+
from flyrail.targets import Agent, Platform, Surface, TargetScope
|
|
40
|
+
from flyrail.values import Value, freeze_value, value_from_record
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ConfigSource(Protocol):
|
|
44
|
+
def read_file(self, path: str) -> bytes: ...
|
|
45
|
+
|
|
46
|
+
def tree_entries(
|
|
47
|
+
self, path: str, name: str, executables: tuple[str, ...]
|
|
48
|
+
) -> tuple[BundleEntry, ...]: ...
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _list(value: object) -> list[object]:
|
|
52
|
+
if not isinstance(value, list):
|
|
53
|
+
raise ValueError("expected a JSON array")
|
|
54
|
+
return value
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _path(value: object) -> str:
|
|
58
|
+
path = _string(value, "source path")
|
|
59
|
+
validate_relative_path(path, "source path")
|
|
60
|
+
return path
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _text(fields: dict[str, object], source: ConfigSource) -> str:
|
|
64
|
+
if ("text" in fields) == ("path" in fields):
|
|
65
|
+
raise ValueError("provide exactly one of text or path")
|
|
66
|
+
return (
|
|
67
|
+
_string(fields["text"], "text")
|
|
68
|
+
if "text" in fields
|
|
69
|
+
else source.read_file(_path(fields["path"])).decode("utf-8")
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _tree(fields: dict[str, object], source: ConfigSource) -> TreeContent:
|
|
74
|
+
executables = tuple(_path(value) for value in _list(fields.get("executables", [])))
|
|
75
|
+
if len(set(executables)) != len(executables):
|
|
76
|
+
raise ValueError("duplicate executable path")
|
|
77
|
+
entries = source.tree_entries(_path(fields["path"]), "content", executables)
|
|
78
|
+
result = TreeContent(
|
|
79
|
+
BundleEntry(entry.path.removeprefix("content/"), entry.data, entry.executable)
|
|
80
|
+
for entry in entries
|
|
81
|
+
if entry.path != "content"
|
|
82
|
+
)
|
|
83
|
+
files = {entry.path for entry in result.entries if not entry.is_directory}
|
|
84
|
+
if any(executable not in files for executable in executables):
|
|
85
|
+
raise ValueError("every executable must name an existing file")
|
|
86
|
+
return result
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _argument(value: object) -> str | AssetRef:
|
|
90
|
+
if isinstance(value, dict):
|
|
91
|
+
fields = _object(value, {"asset"}, {"path"})
|
|
92
|
+
return AssetRef(
|
|
93
|
+
_string(fields["asset"], "referenced asset id"),
|
|
94
|
+
None if "path" not in fields else _path(fields["path"]),
|
|
95
|
+
)
|
|
96
|
+
return _string(value, "command argument")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _command(value: object) -> Command:
|
|
100
|
+
fields = _object(value, {"argv"}, {"env", "cwd"})
|
|
101
|
+
raw_env = fields.get("env", {})
|
|
102
|
+
if not isinstance(raw_env, dict):
|
|
103
|
+
raise ValueError("command env must be a JSON object")
|
|
104
|
+
env: list[tuple[str, str | EnvRef]] = []
|
|
105
|
+
for key, item in raw_env.items():
|
|
106
|
+
if isinstance(item, dict):
|
|
107
|
+
reference = _object(item, {"env"}, set())
|
|
108
|
+
env.append((key, EnvRef(_string(reference["env"], "environment reference"))))
|
|
109
|
+
else:
|
|
110
|
+
env.append((key, _string(item, "environment value")))
|
|
111
|
+
return Command(
|
|
112
|
+
[_argument(item) for item in _list(fields["argv"])],
|
|
113
|
+
env,
|
|
114
|
+
None if "cwd" not in fields else _argument(fields["cwd"]),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _member(value: object) -> Member:
|
|
119
|
+
fields = _object(value, set(), {"identity", "semantic_identity", "key"})
|
|
120
|
+
return Member(
|
|
121
|
+
_value(fields, "identity", "semantic_identity"),
|
|
122
|
+
[_string(name, "member key") for name in _list(fields.get("key", []))],
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _value(fields: dict[str, object], plain: str, typed: str) -> Value:
|
|
127
|
+
if (plain in fields) == (typed in fields):
|
|
128
|
+
raise ValueError(f"provide exactly one of {plain} or {typed}")
|
|
129
|
+
return freeze_value(fields[plain]) if plain in fields else value_from_record(fields[typed])
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _selector(value: object) -> Selector:
|
|
133
|
+
return Selector(Key(item) if isinstance(item, str) else _member(item) for item in _list(value))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _content(value: object, source: ConfigSource) -> Content:
|
|
137
|
+
header = _object(
|
|
138
|
+
value,
|
|
139
|
+
{"kind"},
|
|
140
|
+
{
|
|
141
|
+
"path",
|
|
142
|
+
"text",
|
|
143
|
+
"mode",
|
|
144
|
+
"executables",
|
|
145
|
+
"marker",
|
|
146
|
+
"format",
|
|
147
|
+
"selector",
|
|
148
|
+
"value",
|
|
149
|
+
"semantic_value",
|
|
150
|
+
"placement",
|
|
151
|
+
"boundaries",
|
|
152
|
+
},
|
|
153
|
+
)
|
|
154
|
+
kind = header["kind"]
|
|
155
|
+
if kind == "file":
|
|
156
|
+
fields = _object(value, {"kind", "path"}, {"mode"})
|
|
157
|
+
return FileContent(
|
|
158
|
+
source.read_file(_path(fields["path"])),
|
|
159
|
+
FileMode(_string(fields.get("mode", "private"), "file mode")),
|
|
160
|
+
)
|
|
161
|
+
if kind == "tree":
|
|
162
|
+
return _tree(_object(value, {"kind", "path"}, {"executables"}), source)
|
|
163
|
+
if kind == "section":
|
|
164
|
+
fields = _object(value, {"kind", "marker"}, {"text", "path", "boundaries"})
|
|
165
|
+
boundaries = None
|
|
166
|
+
if "boundaries" in fields:
|
|
167
|
+
raw_boundaries = _object(fields["boundaries"], {"start", "end"}, set())
|
|
168
|
+
boundaries = SectionBoundaries(
|
|
169
|
+
_string(raw_boundaries["start"], "start boundary"),
|
|
170
|
+
_string(raw_boundaries["end"], "end boundary"),
|
|
171
|
+
)
|
|
172
|
+
return SectionContent(
|
|
173
|
+
_string(fields["marker"], "section marker"), _text(fields, source), boundaries
|
|
174
|
+
)
|
|
175
|
+
if kind == "structured":
|
|
176
|
+
fields = _object(
|
|
177
|
+
value, {"kind", "format", "selector"}, {"value", "semantic_value", "placement"}
|
|
178
|
+
)
|
|
179
|
+
placement = None
|
|
180
|
+
if "placement" in fields:
|
|
181
|
+
raw = _object(fields["placement"], {"position"}, {"anchor"})
|
|
182
|
+
placement = Placement(
|
|
183
|
+
Position(_string(raw["position"], "placement position")),
|
|
184
|
+
None if "anchor" not in raw else _member(raw["anchor"]),
|
|
185
|
+
)
|
|
186
|
+
return StructuredContent(
|
|
187
|
+
DocumentFormat(_string(fields["format"], "document format")),
|
|
188
|
+
_selector(fields["selector"]),
|
|
189
|
+
_value(fields, "value", "semantic_value"),
|
|
190
|
+
placement,
|
|
191
|
+
)
|
|
192
|
+
raise ValueError("unknown native content kind")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _artifact(value: object, source: ConfigSource) -> Artifact:
|
|
196
|
+
header = _object(
|
|
197
|
+
value,
|
|
198
|
+
{"kind", "id"},
|
|
199
|
+
{
|
|
200
|
+
"name",
|
|
201
|
+
"path",
|
|
202
|
+
"executables",
|
|
203
|
+
"text",
|
|
204
|
+
"transport",
|
|
205
|
+
"event",
|
|
206
|
+
"command",
|
|
207
|
+
"timeout_ms",
|
|
208
|
+
"outcomes",
|
|
209
|
+
"tools",
|
|
210
|
+
"protocol_version",
|
|
211
|
+
"family",
|
|
212
|
+
"audience",
|
|
213
|
+
"destination",
|
|
214
|
+
"content",
|
|
215
|
+
},
|
|
216
|
+
)
|
|
217
|
+
identifier = _string(header["id"], "artifact id")
|
|
218
|
+
kind = header["kind"]
|
|
219
|
+
if kind == "skill":
|
|
220
|
+
fields = _object(value, {"kind", "id", "name", "path"}, {"executables"})
|
|
221
|
+
return SkillArtifact(
|
|
222
|
+
identifier, _string(fields["name"], "skill name"), _tree(fields, source)
|
|
223
|
+
)
|
|
224
|
+
if kind == "instruction":
|
|
225
|
+
fields = _object(value, {"kind", "id"}, {"text", "path"})
|
|
226
|
+
return InstructionArtifact(identifier, _text(fields, source))
|
|
227
|
+
if kind == "mcp":
|
|
228
|
+
fields = _object(value, {"kind", "id", "name", "transport"}, set())
|
|
229
|
+
transport = _object(fields["transport"], {"kind"}, {"command", "url", "bearer_token_env"})
|
|
230
|
+
if transport["kind"] == "stdio":
|
|
231
|
+
transport = _object(fields["transport"], {"kind", "command"}, set())
|
|
232
|
+
definition: Command | HttpTransport = _command(transport["command"])
|
|
233
|
+
elif transport["kind"] == "http":
|
|
234
|
+
transport = _object(fields["transport"], {"kind", "url"}, {"bearer_token_env"})
|
|
235
|
+
definition = HttpTransport(
|
|
236
|
+
_string(transport["url"], "MCP URL"),
|
|
237
|
+
None
|
|
238
|
+
if "bearer_token_env" not in transport
|
|
239
|
+
else EnvRef(_string(transport["bearer_token_env"], "bearer environment reference")),
|
|
240
|
+
)
|
|
241
|
+
else:
|
|
242
|
+
raise ValueError("unknown MCP transport")
|
|
243
|
+
return McpArtifact(identifier, _string(fields["name"], "MCP server name"), definition)
|
|
244
|
+
if kind == "hook":
|
|
245
|
+
fields = _object(
|
|
246
|
+
value,
|
|
247
|
+
{"kind", "id", "event", "command", "protocol_version"},
|
|
248
|
+
{"timeout_ms", "outcomes", "tools"},
|
|
249
|
+
)
|
|
250
|
+
if type(fields["protocol_version"]) is not int or fields["protocol_version"] != 1:
|
|
251
|
+
raise ValueError("hook protocol_version must be integer 1")
|
|
252
|
+
timeout = fields.get("timeout_ms", 10000)
|
|
253
|
+
if type(timeout) is not int:
|
|
254
|
+
raise ValueError("hook timeout must be an integer")
|
|
255
|
+
return HookArtifact(
|
|
256
|
+
identifier,
|
|
257
|
+
HookEvent(_string(fields["event"], "hook event")),
|
|
258
|
+
_command(fields["command"]),
|
|
259
|
+
timeout,
|
|
260
|
+
[
|
|
261
|
+
HookOutcome(_string(item, "hook outcome"))
|
|
262
|
+
for item in _list(fields.get("outcomes", ["continue"]))
|
|
263
|
+
],
|
|
264
|
+
[_string(item, "hook tool") for item in _list(fields.get("tools", []))],
|
|
265
|
+
)
|
|
266
|
+
if kind == "native":
|
|
267
|
+
fields = _object(
|
|
268
|
+
value, {"kind", "id", "family", "audience", "destination", "content"}, set()
|
|
269
|
+
)
|
|
270
|
+
audience = _object(fields["audience"], {"agent", "scope"}, {"surface", "platform"})
|
|
271
|
+
return NativeArtifact(
|
|
272
|
+
identifier,
|
|
273
|
+
Family(_string(fields["family"], "native family")),
|
|
274
|
+
Audience(
|
|
275
|
+
Agent(_string(audience["agent"], "agent")),
|
|
276
|
+
TargetScope(_string(audience["scope"], "scope")),
|
|
277
|
+
Surface(_string(audience.get("surface", "cli"), "surface")),
|
|
278
|
+
None
|
|
279
|
+
if "platform" not in audience
|
|
280
|
+
else Platform(_string(audience["platform"], "platform")),
|
|
281
|
+
),
|
|
282
|
+
_path(fields["destination"]),
|
|
283
|
+
_content(fields["content"], source),
|
|
284
|
+
)
|
|
285
|
+
raise ValueError("unknown artifact kind")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def read_config_manifest(
|
|
289
|
+
raw: object, source: ConfigSource
|
|
290
|
+
) -> tuple[BundleIdentity, tuple[Artifact, ...], tuple[SupportAsset, ...], tuple[Dependency, ...]]:
|
|
291
|
+
fields = _object(
|
|
292
|
+
raw, {"schema_version", "id", "version", "artifacts"}, {"assets", "dependencies"}
|
|
293
|
+
)
|
|
294
|
+
if type(fields["schema_version"]) is not int or fields["schema_version"] != 2:
|
|
295
|
+
raise ValueError("configuration schema_version must be integer 2")
|
|
296
|
+
artifacts = tuple(_artifact(item, source) for item in _list(fields["artifacts"]))
|
|
297
|
+
assets: list[SupportAsset] = []
|
|
298
|
+
for item in _list(fields.get("assets", [])):
|
|
299
|
+
asset = _object(item, {"id", "family", "path"}, {"executables"})
|
|
300
|
+
assets.append(
|
|
301
|
+
SupportAsset(
|
|
302
|
+
_string(asset["id"], "asset id"),
|
|
303
|
+
Family(_string(asset["family"], "asset family")),
|
|
304
|
+
_tree(asset, source),
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
dependencies: list[Dependency] = []
|
|
308
|
+
for item in _list(fields.get("dependencies", [])):
|
|
309
|
+
edge = _object(item, {"dependent", "required"}, {"mode"})
|
|
310
|
+
dependencies.append(
|
|
311
|
+
Dependency(
|
|
312
|
+
_string(edge["dependent"], "dependent"),
|
|
313
|
+
_string(edge["required"], "required"),
|
|
314
|
+
DependencyMode(_string(edge.get("mode", "revision"), "dependency mode")),
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
return (
|
|
318
|
+
BundleIdentity(_string(fields["id"], "bundle id"), _string(fields["version"], "version")),
|
|
319
|
+
artifacts,
|
|
320
|
+
tuple(assets),
|
|
321
|
+
tuple(dependencies),
|
|
322
|
+
)
|