gravity-schema 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.
- gravity_schema/__init__.py +88 -0
- gravity_schema/__main__.py +6 -0
- gravity_schema/catalog.py +93 -0
- gravity_schema/catalog.yaml +221 -0
- gravity_schema/catalog_imported.yaml +7802 -0
- gravity_schema/contract.py +87 -0
- gravity_schema/cron.py +47 -0
- gravity_schema/errors.py +171 -0
- gravity_schema/export.py +67 -0
- gravity_schema/gen_catalog.py +129 -0
- gravity_schema/manifest.py +627 -0
- gravity_schema/validation.py +152 -0
- gravity_schema-0.1.0.dist-info/METADATA +15 -0
- gravity_schema-0.1.0.dist-info/RECORD +17 -0
- gravity_schema-0.1.0.dist-info/WHEEL +4 -0
- gravity_schema-0.1.0.dist-info/licenses/LICENSE +202 -0
- gravity_schema-0.1.0.dist-info/licenses/NOTICE +4 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Gravity agent manifest contract: its shape, validation and JSON Schema export."""
|
|
2
|
+
|
|
3
|
+
from .catalog import DEV_TOOL_NAMES, StaticToolCatalog, ToolCatalog, dev_catalog
|
|
4
|
+
from .contract import (
|
|
5
|
+
AGENT_NAME_RE,
|
|
6
|
+
CURRENT_CONTRACT_VERSION,
|
|
7
|
+
ENV_GATEWAY_URL,
|
|
8
|
+
ENV_LLM_URL,
|
|
9
|
+
ENV_RUN_TOKEN,
|
|
10
|
+
LOCKFILE_PLATFORM,
|
|
11
|
+
SUPPORTED_CONTRACT_VERSIONS,
|
|
12
|
+
SUPPORTED_PYTHON_VERSIONS,
|
|
13
|
+
)
|
|
14
|
+
from .errors import (
|
|
15
|
+
ErrorCode,
|
|
16
|
+
ManifestIssue,
|
|
17
|
+
ManifestValidationError,
|
|
18
|
+
Severity,
|
|
19
|
+
did_you_mean,
|
|
20
|
+
suggest,
|
|
21
|
+
)
|
|
22
|
+
from .export import manifest_json_schema
|
|
23
|
+
from .manifest import (
|
|
24
|
+
ApprovalsSpec,
|
|
25
|
+
DependenciesSpec,
|
|
26
|
+
InputSpec,
|
|
27
|
+
InputType,
|
|
28
|
+
Manifest,
|
|
29
|
+
ModelsSpec,
|
|
30
|
+
ModelTier,
|
|
31
|
+
PermissionsSpec,
|
|
32
|
+
ResourcesSpec,
|
|
33
|
+
RuntimeSpec,
|
|
34
|
+
ScheduleSpec,
|
|
35
|
+
StateSpec,
|
|
36
|
+
TriggersSpec,
|
|
37
|
+
)
|
|
38
|
+
from .validation import (
|
|
39
|
+
check_tool_references,
|
|
40
|
+
load_manifest_file,
|
|
41
|
+
load_manifest_text,
|
|
42
|
+
validate_manifest,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
__all__ = [ # noqa: RUF022 - grouped by concern, not alphabetical
|
|
46
|
+
# contract
|
|
47
|
+
"CURRENT_CONTRACT_VERSION",
|
|
48
|
+
"AGENT_NAME_RE",
|
|
49
|
+
"SUPPORTED_CONTRACT_VERSIONS",
|
|
50
|
+
"SUPPORTED_PYTHON_VERSIONS",
|
|
51
|
+
"LOCKFILE_PLATFORM",
|
|
52
|
+
"ENV_GATEWAY_URL",
|
|
53
|
+
"ENV_LLM_URL",
|
|
54
|
+
"ENV_RUN_TOKEN",
|
|
55
|
+
# models
|
|
56
|
+
"Manifest",
|
|
57
|
+
"RuntimeSpec",
|
|
58
|
+
"DependenciesSpec",
|
|
59
|
+
"InputSpec",
|
|
60
|
+
"InputType",
|
|
61
|
+
"PermissionsSpec",
|
|
62
|
+
"ModelsSpec",
|
|
63
|
+
"ModelTier",
|
|
64
|
+
"ResourcesSpec",
|
|
65
|
+
"TriggersSpec",
|
|
66
|
+
"ScheduleSpec",
|
|
67
|
+
"ApprovalsSpec",
|
|
68
|
+
"StateSpec",
|
|
69
|
+
# validation
|
|
70
|
+
"validate_manifest",
|
|
71
|
+
"load_manifest_text",
|
|
72
|
+
"load_manifest_file",
|
|
73
|
+
"check_tool_references",
|
|
74
|
+
# catalog
|
|
75
|
+
"ToolCatalog",
|
|
76
|
+
"StaticToolCatalog",
|
|
77
|
+
"dev_catalog",
|
|
78
|
+
"DEV_TOOL_NAMES",
|
|
79
|
+
# errors
|
|
80
|
+
"ErrorCode",
|
|
81
|
+
"Severity",
|
|
82
|
+
"ManifestIssue",
|
|
83
|
+
"ManifestValidationError",
|
|
84
|
+
"suggest",
|
|
85
|
+
"did_you_mean",
|
|
86
|
+
# export
|
|
87
|
+
"manifest_json_schema",
|
|
88
|
+
]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""The tool catalog, read from catalog.yaml. Validation takes any ToolCatalog: the control
|
|
2
|
+
plane's live one in production, dev_catalog() locally."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Protocol
|
|
10
|
+
|
|
11
|
+
import yaml
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ToolCatalog(Protocol):
|
|
15
|
+
def exists(self, tool_name: str) -> bool: ...
|
|
16
|
+
|
|
17
|
+
def names(self) -> frozenset[str]: ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class StaticToolCatalog:
|
|
21
|
+
"""A catalog backed by a fixed set of names."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, names: Iterable[str]) -> None:
|
|
24
|
+
self._names = frozenset(names)
|
|
25
|
+
|
|
26
|
+
def exists(self, tool_name: str) -> bool:
|
|
27
|
+
return tool_name in self._names
|
|
28
|
+
|
|
29
|
+
def names(self) -> frozenset[str]:
|
|
30
|
+
return self._names
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ToolEntry:
|
|
35
|
+
name: str
|
|
36
|
+
slug: str | None # None: built into the gateway, no vendor behind it
|
|
37
|
+
description: str
|
|
38
|
+
scopes: tuple[str, ...]
|
|
39
|
+
breadth: str # generic | niche
|
|
40
|
+
guarantee: str # certified | community | experimental
|
|
41
|
+
side_effect: str # read | write
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
_BREADTH = {"generic", "niche"}
|
|
45
|
+
_GUARANTEE = {"certified", "community", "experimental"}
|
|
46
|
+
_SIDE_EFFECT = {"read", "write"}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def load_catalog(path: Path | None = None) -> tuple[ToolEntry, ...]:
|
|
50
|
+
"""Parse and check catalog.yaml; a malformed entry fails at import."""
|
|
51
|
+
raw = yaml.safe_load((path or Path(__file__).with_name("catalog.yaml")).read_text(encoding="utf-8"))
|
|
52
|
+
# catalog_imported.yaml (`gravity-gateway sync-tools`): the same apps' other tools,
|
|
53
|
+
# experimental. Absent until the first sync; never read for an explicit path.
|
|
54
|
+
imported = Path(__file__).with_name("catalog_imported.yaml")
|
|
55
|
+
if path is None and imported.is_file():
|
|
56
|
+
raw = [*raw, *(yaml.safe_load(imported.read_text(encoding="utf-8")) or [])]
|
|
57
|
+
entries: list[ToolEntry] = []
|
|
58
|
+
for item in raw:
|
|
59
|
+
entry = ToolEntry(
|
|
60
|
+
name=item["name"],
|
|
61
|
+
slug=item.get("slug"),
|
|
62
|
+
description=item["description"],
|
|
63
|
+
scopes=tuple(item.get("scopes") or ()),
|
|
64
|
+
breadth=item["breadth"],
|
|
65
|
+
guarantee=item["guarantee"],
|
|
66
|
+
side_effect=item["side_effect"],
|
|
67
|
+
)
|
|
68
|
+
problems = []
|
|
69
|
+
if entry.breadth not in _BREADTH:
|
|
70
|
+
problems.append(f"breadth {entry.breadth!r}")
|
|
71
|
+
if entry.guarantee not in _GUARANTEE:
|
|
72
|
+
problems.append(f"guarantee {entry.guarantee!r}")
|
|
73
|
+
if entry.side_effect not in _SIDE_EFFECT:
|
|
74
|
+
problems.append(f"side_effect {entry.side_effect!r}")
|
|
75
|
+
if problems:
|
|
76
|
+
raise ValueError(f"catalog.yaml: {entry.name}: bad {', '.join(problems)}")
|
|
77
|
+
entries.append(entry)
|
|
78
|
+
names = [e.name for e in entries]
|
|
79
|
+
dupes = sorted({n for n in names if names.count(n) > 1})
|
|
80
|
+
if dupes:
|
|
81
|
+
raise ValueError(f"catalog.yaml: duplicate tool(s): {', '.join(dupes)}")
|
|
82
|
+
return tuple(entries)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Read once, at import.
|
|
86
|
+
CATALOG: tuple[ToolEntry, ...] = load_catalog()
|
|
87
|
+
|
|
88
|
+
# What `gravity validate` accepts.
|
|
89
|
+
DEV_TOOL_NAMES: frozenset[str] = frozenset(e.name for e in CATALOG)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def dev_catalog() -> StaticToolCatalog:
|
|
93
|
+
return StaticToolCatalog(DEV_TOOL_NAMES)
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# The tool catalog: the ONE place a tool is declared.
|
|
2
|
+
#
|
|
3
|
+
# gravity_schema.catalog reads it (CATALOG, DEV_TOOL_NAMES); the platform's
|
|
4
|
+
# gateway slug table and control-plane list are generated from CATALOG there.
|
|
5
|
+
#
|
|
6
|
+
# Adding a tool is adding an entry here and regenerating on the platform side. Removing one is
|
|
7
|
+
# never done: a published manifest is frozen, so a name must keep meaning the
|
|
8
|
+
# same thing forever. Deprecate instead.
|
|
9
|
+
#
|
|
10
|
+
# Fields
|
|
11
|
+
# name the Gravity name builders write in permissions.tools
|
|
12
|
+
# slug the vendor action it routes to; null for a gateway built-in
|
|
13
|
+
# description what a builder or a model reads to choose it
|
|
14
|
+
# scopes the consent a consumer grants when connecting
|
|
15
|
+
# breadth generic: ordinary plumbing any agent might use
|
|
16
|
+
# niche: custom-written for a narrow job; ranked apart in search
|
|
17
|
+
# guarantee certified: Gravity maintains it and it may run hosted
|
|
18
|
+
# community: third party, reviewed
|
|
19
|
+
# experimental: may change; never runs hosted
|
|
20
|
+
# side_effect read | write. A write is what approvals are for.
|
|
21
|
+
|
|
22
|
+
- name: example.echo
|
|
23
|
+
slug: null
|
|
24
|
+
description: echoes back its input, for templates and plumbing tests
|
|
25
|
+
scopes: []
|
|
26
|
+
breadth: generic
|
|
27
|
+
guarantee: certified
|
|
28
|
+
side_effect: read
|
|
29
|
+
|
|
30
|
+
- name: gmail.read
|
|
31
|
+
slug: GMAIL_FETCH_EMAILS
|
|
32
|
+
description: read the connected Gmail inbox
|
|
33
|
+
scopes: [gmail.readonly]
|
|
34
|
+
breadth: generic
|
|
35
|
+
guarantee: certified
|
|
36
|
+
side_effect: read
|
|
37
|
+
|
|
38
|
+
- name: gmail.draft
|
|
39
|
+
slug: GMAIL_CREATE_EMAIL_DRAFT
|
|
40
|
+
description: create a Gmail draft
|
|
41
|
+
scopes: [gmail.compose]
|
|
42
|
+
breadth: generic
|
|
43
|
+
guarantee: certified
|
|
44
|
+
side_effect: write
|
|
45
|
+
|
|
46
|
+
- name: gmail.send
|
|
47
|
+
slug: GMAIL_SEND_EMAIL
|
|
48
|
+
description: send an email from the connected Gmail
|
|
49
|
+
scopes: [gmail.send]
|
|
50
|
+
breadth: generic
|
|
51
|
+
guarantee: certified
|
|
52
|
+
side_effect: write
|
|
53
|
+
|
|
54
|
+
- name: sheets.read
|
|
55
|
+
slug: GOOGLESHEETS_BATCH_GET
|
|
56
|
+
description: read values from a connected Google Sheet
|
|
57
|
+
scopes: [sheets]
|
|
58
|
+
breadth: generic
|
|
59
|
+
guarantee: certified
|
|
60
|
+
side_effect: read
|
|
61
|
+
|
|
62
|
+
- name: sheets.append
|
|
63
|
+
slug: GOOGLESHEETS_BATCH_UPDATE
|
|
64
|
+
description: append a row to a connected Google Sheet
|
|
65
|
+
scopes: [sheets]
|
|
66
|
+
breadth: generic
|
|
67
|
+
guarantee: certified
|
|
68
|
+
side_effect: write
|
|
69
|
+
|
|
70
|
+
- name: slack.send
|
|
71
|
+
slug: SLACK_SEND_MESSAGE
|
|
72
|
+
description: post a message to a connected Slack channel
|
|
73
|
+
scopes: [slack.chat:write]
|
|
74
|
+
breadth: generic
|
|
75
|
+
guarantee: certified
|
|
76
|
+
side_effect: write
|
|
77
|
+
|
|
78
|
+
- name: slack.list_channels
|
|
79
|
+
slug: SLACK_LIST_ALL_CHANNELS
|
|
80
|
+
description: list channels in the connected Slack workspace
|
|
81
|
+
scopes: [slack.channels:read]
|
|
82
|
+
breadth: generic
|
|
83
|
+
guarantee: certified
|
|
84
|
+
side_effect: read
|
|
85
|
+
|
|
86
|
+
- name: slack.read_history
|
|
87
|
+
slug: SLACK_FETCH_CONVERSATION_HISTORY
|
|
88
|
+
description: read message history of a Slack channel
|
|
89
|
+
scopes: [slack.channels:history]
|
|
90
|
+
breadth: generic
|
|
91
|
+
guarantee: certified
|
|
92
|
+
side_effect: read
|
|
93
|
+
|
|
94
|
+
- name: slack.find_user
|
|
95
|
+
slug: SLACK_FIND_USERS
|
|
96
|
+
description: find a Slack user by name, handle or email, for mentions
|
|
97
|
+
scopes: [slack.users:read]
|
|
98
|
+
breadth: generic
|
|
99
|
+
guarantee: certified
|
|
100
|
+
side_effect: read
|
|
101
|
+
|
|
102
|
+
- name: calendar.read
|
|
103
|
+
slug: GOOGLECALENDAR_EVENTS_LIST
|
|
104
|
+
description: list events on the connected Google Calendar
|
|
105
|
+
scopes: [calendar.readonly]
|
|
106
|
+
breadth: generic
|
|
107
|
+
guarantee: certified
|
|
108
|
+
side_effect: read
|
|
109
|
+
|
|
110
|
+
- name: drive.read
|
|
111
|
+
slug: GOOGLEDRIVE_DOWNLOAD_FILE
|
|
112
|
+
description: download a file from the connected Google Drive
|
|
113
|
+
scopes: [drive.readonly]
|
|
114
|
+
breadth: generic
|
|
115
|
+
guarantee: certified
|
|
116
|
+
side_effect: read
|
|
117
|
+
|
|
118
|
+
- name: linear.create_issue
|
|
119
|
+
slug: LINEAR_CREATE_LINEAR_ISSUE
|
|
120
|
+
description: create an issue in a connected Linear team
|
|
121
|
+
scopes: [linear.write]
|
|
122
|
+
breadth: generic
|
|
123
|
+
guarantee: certified
|
|
124
|
+
side_effect: write
|
|
125
|
+
|
|
126
|
+
- name: linear.list_teams
|
|
127
|
+
slug: LINEAR_LIST_LINEAR_TEAMS
|
|
128
|
+
description: list the connected Linear workspace teams
|
|
129
|
+
scopes: [linear.read]
|
|
130
|
+
breadth: generic
|
|
131
|
+
guarantee: certified
|
|
132
|
+
side_effect: read
|
|
133
|
+
|
|
134
|
+
- name: linear.list_issues
|
|
135
|
+
slug: LINEAR_LIST_LINEAR_ISSUES
|
|
136
|
+
description: list open issues in the connected Linear workspace
|
|
137
|
+
scopes: [linear.read]
|
|
138
|
+
breadth: generic
|
|
139
|
+
guarantee: certified
|
|
140
|
+
side_effect: read
|
|
141
|
+
|
|
142
|
+
- name: linear.get_issue
|
|
143
|
+
slug: LINEAR_GET_LINEAR_ISSUE
|
|
144
|
+
description: read one Linear issue by id
|
|
145
|
+
scopes: [linear.read]
|
|
146
|
+
breadth: generic
|
|
147
|
+
guarantee: certified
|
|
148
|
+
side_effect: read
|
|
149
|
+
|
|
150
|
+
# GitHub: read-only, for change summaries and contribution profiles.
|
|
151
|
+
- name: github.list_commits
|
|
152
|
+
slug: GITHUB_LIST_COMMITS
|
|
153
|
+
description: list commits on a repository branch, filterable by date range, author or path
|
|
154
|
+
scopes: [repo]
|
|
155
|
+
breadth: generic
|
|
156
|
+
guarantee: certified
|
|
157
|
+
side_effect: read
|
|
158
|
+
|
|
159
|
+
- name: github.get_commit
|
|
160
|
+
slug: GITHUB_GET_A_COMMIT
|
|
161
|
+
description: read one commit with its changed files, line counts and patches
|
|
162
|
+
scopes: [repo]
|
|
163
|
+
breadth: generic
|
|
164
|
+
guarantee: certified
|
|
165
|
+
side_effect: read
|
|
166
|
+
|
|
167
|
+
- name: github.compare
|
|
168
|
+
slug: GITHUB_COMPARE_TWO_COMMITS
|
|
169
|
+
description: compare two refs (base...head) with the commits and changed files between them
|
|
170
|
+
scopes: [repo]
|
|
171
|
+
breadth: generic
|
|
172
|
+
guarantee: certified
|
|
173
|
+
side_effect: read
|
|
174
|
+
|
|
175
|
+
- name: github.list_pull_requests
|
|
176
|
+
slug: GITHUB_LIST_PULL_REQUESTS
|
|
177
|
+
description: list pull requests on a repository, filterable by state and branch
|
|
178
|
+
scopes: [repo]
|
|
179
|
+
breadth: generic
|
|
180
|
+
guarantee: certified
|
|
181
|
+
side_effect: read
|
|
182
|
+
|
|
183
|
+
- name: github.list_pr_files
|
|
184
|
+
slug: GITHUB_LIST_PULL_REQUESTS_FILES
|
|
185
|
+
description: list the files a pull request changes, with line counts and patches
|
|
186
|
+
scopes: [repo]
|
|
187
|
+
breadth: generic
|
|
188
|
+
guarantee: certified
|
|
189
|
+
side_effect: read
|
|
190
|
+
|
|
191
|
+
- name: github.list_pr_reviews
|
|
192
|
+
slug: GITHUB_LIST_REVIEWS_FOR_A_PULL_REQUEST
|
|
193
|
+
description: list the reviews submitted on a pull request
|
|
194
|
+
scopes: [repo]
|
|
195
|
+
breadth: generic
|
|
196
|
+
guarantee: certified
|
|
197
|
+
side_effect: read
|
|
198
|
+
|
|
199
|
+
- name: github.search_issues
|
|
200
|
+
slug: GITHUB_SEARCH_ISSUES_AND_PULL_REQUESTS
|
|
201
|
+
description: search issues and pull requests with GitHub qualifiers (repo:, author:, reviewed-by:, is:pr, created:)
|
|
202
|
+
scopes: [repo]
|
|
203
|
+
breadth: generic
|
|
204
|
+
guarantee: certified
|
|
205
|
+
side_effect: read
|
|
206
|
+
|
|
207
|
+
- name: linkedin.whoami
|
|
208
|
+
slug: LINKEDIN_GET_MY_INFO
|
|
209
|
+
description: read the connected LinkedIn member's id, name and headline (the author of their posts)
|
|
210
|
+
scopes: [linkedin.profile]
|
|
211
|
+
breadth: generic
|
|
212
|
+
guarantee: certified
|
|
213
|
+
side_effect: read
|
|
214
|
+
|
|
215
|
+
- name: linkedin.create_post
|
|
216
|
+
slug: LINKEDIN_CREATE_LINKED_IN_POST
|
|
217
|
+
description: publish a post on LinkedIn as the connected member or an organisation they manage
|
|
218
|
+
scopes: [linkedin.w_member_social]
|
|
219
|
+
breadth: generic
|
|
220
|
+
guarantee: certified
|
|
221
|
+
side_effect: write
|