modulex-integrations 0.0.1a0__tar.gz
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.
- modulex_integrations-0.0.1a0/.gitignore +53 -0
- modulex_integrations-0.0.1a0/CHANGELOG.md +35 -0
- modulex_integrations-0.0.1a0/CONTRIBUTING.md +59 -0
- modulex_integrations-0.0.1a0/LICENSE +21 -0
- modulex_integrations-0.0.1a0/PKG-INFO +134 -0
- modulex_integrations-0.0.1a0/README.md +103 -0
- modulex_integrations-0.0.1a0/pyproject.toml +107 -0
- modulex_integrations-0.0.1a0/scripts/README.md +10 -0
- modulex_integrations-0.0.1a0/src/modulex_integrations/__init__.py +55 -0
- modulex_integrations-0.0.1a0/src/modulex_integrations/_version.py +24 -0
- modulex_integrations-0.0.1a0/src/modulex_integrations/py.typed +0 -0
- modulex_integrations-0.0.1a0/src/modulex_integrations/schema.py +212 -0
- modulex_integrations-0.0.1a0/src/modulex_integrations/tools/__init__.py +7 -0
- modulex_integrations-0.0.1a0/tests/conftest.py +7 -0
- modulex_integrations-0.0.1a0/tests/test_schema.py +224 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
|
|
8
|
+
# Distribution
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
dist/
|
|
12
|
+
build/
|
|
13
|
+
wheels/
|
|
14
|
+
sdist/
|
|
15
|
+
|
|
16
|
+
# Virtual envs
|
|
17
|
+
.venv/
|
|
18
|
+
venv/
|
|
19
|
+
env/
|
|
20
|
+
|
|
21
|
+
# Tooling caches
|
|
22
|
+
.pytest_cache/
|
|
23
|
+
.mypy_cache/
|
|
24
|
+
.ruff_cache/
|
|
25
|
+
.tox/
|
|
26
|
+
.coverage
|
|
27
|
+
.coverage.*
|
|
28
|
+
htmlcov/
|
|
29
|
+
|
|
30
|
+
# IDEs
|
|
31
|
+
.vscode/
|
|
32
|
+
.idea/
|
|
33
|
+
*.swp
|
|
34
|
+
*.swo
|
|
35
|
+
|
|
36
|
+
# OS
|
|
37
|
+
.DS_Store
|
|
38
|
+
Thumbs.db
|
|
39
|
+
|
|
40
|
+
# Generated by hatch-vcs at build time
|
|
41
|
+
src/modulex_integrations/_version.py
|
|
42
|
+
|
|
43
|
+
# Local environment
|
|
44
|
+
.env
|
|
45
|
+
.env.local
|
|
46
|
+
*.local
|
|
47
|
+
|
|
48
|
+
# Internal-only artifacts — never push to this public repo.
|
|
49
|
+
# (History was scrubbed of CLAUDE.md, external-briefs/, and .claude/
|
|
50
|
+
# on 2026-05-15; see the cleanup commit on main.)
|
|
51
|
+
CLAUDE.md
|
|
52
|
+
.claude/
|
|
53
|
+
external-briefs/
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `modulex-integrations` are recorded here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Initial repository skeleton (src/ layout, hatchling build, pytest/ruff/mypy config).
|
|
12
|
+
- `IntegrationManifest` pydantic schema with discriminated-union `auth_schemas` covering oauth2, bearer_token, api_key, modulex_key, custom, internal.
|
|
13
|
+
- Contract tests on a github-shaped manifest.
|
|
14
|
+
- `CLAUDE.md` — project rules for Claude Code sessions in this repo.
|
|
15
|
+
- `external-briefs/` workflow scaffold (`README.md` spec + `modulex/` placeholder) for coordinating changes in sibling repos.
|
|
16
|
+
- Community meta files: `.github/CODEOWNERS`, `.github/PULL_REQUEST_TEMPLATE.md`, `.github/ISSUE_TEMPLATE/{bug_report.md,integration_request.md,config.yml}`, `SECURITY.md`, `CODE_OF_CONDUCT.md` (Contributor Covenant v2.1).
|
|
17
|
+
- `.github/workflows/validate.yml` — lint + type-check + test on Python 3.12 and 3.13.
|
|
18
|
+
- `.editorconfig` and `.python-version` for consistent local tooling.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- `README.md` expanded with "Why this repo exists", badges, per-integration layout, and a roadmap section.
|
|
23
|
+
- `.gitignore` no longer ignores `.python-version` (file is now committed); now also ignores the hatch-vcs-generated `src/modulex_integrations/_version.py`.
|
|
24
|
+
- `pyproject.toml`: switched to `dynamic = ["version"]` driven by `hatch-vcs`; static `version = "0.0.1"` removed. Static `__version__` in `__init__.py` replaced by `importlib.metadata`-based resolution.
|
|
25
|
+
|
|
26
|
+
### Added (release infrastructure)
|
|
27
|
+
|
|
28
|
+
- `.github/workflows/release.yml` — tag-triggered (`v*`) workflow that classifies pre-release vs stable via PEP 440, enforces branch-of-origin (stable from `main`, pre-release from `staging`), builds sdist + wheel, and publishes to PyPI via Trusted Publishing OIDC in the `release-pypi` environment.
|
|
29
|
+
- `RELEASING.md` — release process, PyPI Trusted Publisher setup checklist, and the modulex-side per-branch pinning policy.
|
|
30
|
+
- `external-briefs/modulex/001-bump-python-floor-to-3-12.md` — blocking brief: modulex must bump its `requires-python` floor and runtime Python to 3.12 before the upcoming `modulex-integrations` dependency line can resolve.
|
|
31
|
+
- `CLAUDE.md` — "Release model" section pointing at `RELEASING.md`.
|
|
32
|
+
|
|
33
|
+
## [0.0.1] — 2026-05-15
|
|
34
|
+
|
|
35
|
+
Project bootstrap.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Contributing to modulex-integrations
|
|
2
|
+
|
|
3
|
+
This repository expects every integration to ship with the same set of files in the same shape. Reviews are mechanical: if the structure is wrong, the PR is sent back. If the structure is right, review focuses on correctness of the API calls.
|
|
4
|
+
|
|
5
|
+
## Project layout
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/modulex_integrations/tools/<name>/
|
|
9
|
+
├── __init__.py # re-exports `manifest` and `TOOLS`
|
|
10
|
+
├── manifest.py # IntegrationManifest instance
|
|
11
|
+
├── tools.py # LangChain @tool functions
|
|
12
|
+
├── outputs.py # pydantic response models
|
|
13
|
+
├── dependencies.toml # runtime deps (assembled into root pyproject by CI)
|
|
14
|
+
├── README.md # 5 required sections (see template below)
|
|
15
|
+
└── tests/
|
|
16
|
+
└── test_<name>.py # ≥1 happy-path test per @tool
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Required README sections
|
|
20
|
+
|
|
21
|
+
Every integration's `README.md` must contain these top-level headings, in this order:
|
|
22
|
+
|
|
23
|
+
1. `# <Display Name>` + 1–2 sentence summary
|
|
24
|
+
2. `## Authentication` — auth methods supported, where to get credentials, OAuth scopes if applicable
|
|
25
|
+
3. `## Tools` — table of `name | description | required params`
|
|
26
|
+
4. `## Limits & Quotas` — rate limits, known issues
|
|
27
|
+
5. `## Maintainer` — github handle or "ModuleX core team"
|
|
28
|
+
|
|
29
|
+
CI enforces the section list.
|
|
30
|
+
|
|
31
|
+
## Test requirements
|
|
32
|
+
|
|
33
|
+
- **HTTP tools (using `httpx`):** at least one `httpx.MockTransport` test per action's happy path.
|
|
34
|
+
- **SDK tools** (using a vendor SDK such as `hubspot-api-client`): at least one `unittest.mock.patch` test stubbing the SDK client per action.
|
|
35
|
+
|
|
36
|
+
## Schema contract
|
|
37
|
+
|
|
38
|
+
The full set of types is in [`src/modulex_integrations/schema.py`](src/modulex_integrations/schema.py). Use these symbols — do not invent fields:
|
|
39
|
+
|
|
40
|
+
- `IntegrationManifest` — top-level
|
|
41
|
+
- `ActionDefinition`, `ParameterDef`
|
|
42
|
+
- `OAuth2AuthSchema`, `BearerTokenAuthSchema`, `ApiKeyAuthSchema`, `ModulexKeyAuthSchema`, `CustomAuthSchema`, `InternalAuthSchema`
|
|
43
|
+
- `OAuthConfig`, `EnvVar`, `TestEndpoint`, `SuccessIndicators`
|
|
44
|
+
|
|
45
|
+
All models use `extra="forbid"` — typos fail at import time.
|
|
46
|
+
|
|
47
|
+
## Local workflow
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install -e ".[dev]"
|
|
51
|
+
pytest # all tests
|
|
52
|
+
pytest src/modulex_integrations/tools/<name>/tests/ # one integration
|
|
53
|
+
ruff check src tests
|
|
54
|
+
mypy src/modulex_integrations
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Adding a new integration
|
|
58
|
+
|
|
59
|
+
> TODO: flesh out once the POC migration of the github tool lands. The intent is straightforward — copy an existing integration's folder, edit the manifest/tools/outputs/tests, open a PR.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ModuleX
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: modulex-integrations
|
|
3
|
+
Version: 0.0.1a0
|
|
4
|
+
Summary: Community-contributable integrations (tools) for ModuleX
|
|
5
|
+
Project-URL: Homepage, https://github.com/ModuleXAI/modulex-integrations
|
|
6
|
+
Project-URL: Repository, https://github.com/ModuleXAI/modulex-integrations
|
|
7
|
+
Project-URL: Issues, https://github.com/ModuleXAI/modulex-integrations/issues
|
|
8
|
+
Author-email: ModuleX <team@modulex.dev>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai,integrations,langchain,modulex,tools
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: httpx>=0.25.0
|
|
21
|
+
Requires-Dist: langchain-core>=0.3.0
|
|
22
|
+
Requires-Dist: pydantic>=2.5.0
|
|
23
|
+
Provides-Extra: all
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.6.0; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# modulex-integrations
|
|
33
|
+
|
|
34
|
+
[](https://github.com/ModuleXAI/modulex-integrations/actions/workflows/validate.yml)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+
[](https://pypi.org/project/modulex-integrations/)
|
|
37
|
+
|
|
38
|
+
Community-contributable integrations (tools) for the [ModuleX](https://github.com/ModuleXAI/modulex) runtime.
|
|
39
|
+
|
|
40
|
+
## Why this repo exists
|
|
41
|
+
|
|
42
|
+
`modulex` is a FastAPI/Python backend that, until now, bundled 45
|
|
43
|
+
LangChain tool integrations inline. We are extracting those 45 tools
|
|
44
|
+
into this standalone, publicly pip-installable package so that:
|
|
45
|
+
|
|
46
|
+
- adding a new tool is a single-repo PR, not a multi-file edit
|
|
47
|
+
across the modulex monorepo,
|
|
48
|
+
- integration code is publicly inspectable,
|
|
49
|
+
- community contributions flow through the standard GitHub PR model.
|
|
50
|
+
|
|
51
|
+
LLM providers and knowledge providers stay in modulex. Only the
|
|
52
|
+
`tool` integrations migrate here.
|
|
53
|
+
|
|
54
|
+
## Status
|
|
55
|
+
|
|
56
|
+
**Phase 1 — early development.** The schema contract and packaging
|
|
57
|
+
are in place; integrations themselves will be migrated from the
|
|
58
|
+
modulex monorepo one at a time, starting with a `github` POC and
|
|
59
|
+
continuing via a scripted bulk migration. See [CHANGELOG.md](CHANGELOG.md)
|
|
60
|
+
for progress and [CONTRIBUTING.md](CONTRIBUTING.md) for layout rules.
|
|
61
|
+
|
|
62
|
+
## What is this?
|
|
63
|
+
|
|
64
|
+
Each integration in this package exposes one or more LangChain
|
|
65
|
+
`@tool` functions to the ModuleX runtime, together with credential
|
|
66
|
+
metadata (auth schemas, env vars, test endpoints). The runtime
|
|
67
|
+
discovers integrations via the Python `modulex.tools` entry-point
|
|
68
|
+
group and loads them at startup.
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install modulex-integrations
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
With every integration's optional dependencies:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install "modulex-integrations[all]"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Or only the integrations you need (extras are populated as
|
|
83
|
+
integrations are migrated):
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pip install "modulex-integrations[github,slack]"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Per-integration layout
|
|
90
|
+
|
|
91
|
+
Every integration ships in the same shape:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
src/modulex_integrations/tools/<name>/
|
|
95
|
+
├── __init__.py # re-exports manifest + TOOLS
|
|
96
|
+
├── manifest.py # IntegrationManifest instance
|
|
97
|
+
├── tools.py # LangChain @tool functions
|
|
98
|
+
├── outputs.py # pydantic response models (UI/docs derive JSONSchema)
|
|
99
|
+
├── dependencies.toml # per-integration runtime deps
|
|
100
|
+
├── README.md # 5-section strict template (see CONTRIBUTING.md)
|
|
101
|
+
└── tests/
|
|
102
|
+
└── test_<name>.py
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The contract is enforced by pydantic types in
|
|
106
|
+
[`src/modulex_integrations/schema.py`](src/modulex_integrations/schema.py)
|
|
107
|
+
with `extra="forbid"` everywhere — a typo in a manifest fails at
|
|
108
|
+
import time, not at runtime.
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
git clone https://github.com/ModuleXAI/modulex-integrations.git
|
|
114
|
+
cd modulex-integrations
|
|
115
|
+
pip install -e ".[dev]"
|
|
116
|
+
pytest
|
|
117
|
+
ruff check src tests
|
|
118
|
+
mypy src/modulex_integrations
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Roadmap
|
|
122
|
+
|
|
123
|
+
- **Phase 1 — bootstrap (this commit).** Schema, packaging, CI,
|
|
124
|
+
community meta files, cross-repo brief workflow.
|
|
125
|
+
- **Phase 2 — github POC migration.** One integration end-to-end:
|
|
126
|
+
manifest, tools, outputs, tests, entry point, real
|
|
127
|
+
`httpx.MockTransport` coverage of at least one action.
|
|
128
|
+
- **Phase 3 — scripted bulk migration.** The remaining 44
|
|
129
|
+
integrations migrated by a one-shot script, then reviewed
|
|
130
|
+
individually.
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# modulex-integrations
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ModuleXAI/modulex-integrations/actions/workflows/validate.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://pypi.org/project/modulex-integrations/)
|
|
6
|
+
|
|
7
|
+
Community-contributable integrations (tools) for the [ModuleX](https://github.com/ModuleXAI/modulex) runtime.
|
|
8
|
+
|
|
9
|
+
## Why this repo exists
|
|
10
|
+
|
|
11
|
+
`modulex` is a FastAPI/Python backend that, until now, bundled 45
|
|
12
|
+
LangChain tool integrations inline. We are extracting those 45 tools
|
|
13
|
+
into this standalone, publicly pip-installable package so that:
|
|
14
|
+
|
|
15
|
+
- adding a new tool is a single-repo PR, not a multi-file edit
|
|
16
|
+
across the modulex monorepo,
|
|
17
|
+
- integration code is publicly inspectable,
|
|
18
|
+
- community contributions flow through the standard GitHub PR model.
|
|
19
|
+
|
|
20
|
+
LLM providers and knowledge providers stay in modulex. Only the
|
|
21
|
+
`tool` integrations migrate here.
|
|
22
|
+
|
|
23
|
+
## Status
|
|
24
|
+
|
|
25
|
+
**Phase 1 — early development.** The schema contract and packaging
|
|
26
|
+
are in place; integrations themselves will be migrated from the
|
|
27
|
+
modulex monorepo one at a time, starting with a `github` POC and
|
|
28
|
+
continuing via a scripted bulk migration. See [CHANGELOG.md](CHANGELOG.md)
|
|
29
|
+
for progress and [CONTRIBUTING.md](CONTRIBUTING.md) for layout rules.
|
|
30
|
+
|
|
31
|
+
## What is this?
|
|
32
|
+
|
|
33
|
+
Each integration in this package exposes one or more LangChain
|
|
34
|
+
`@tool` functions to the ModuleX runtime, together with credential
|
|
35
|
+
metadata (auth schemas, env vars, test endpoints). The runtime
|
|
36
|
+
discovers integrations via the Python `modulex.tools` entry-point
|
|
37
|
+
group and loads them at startup.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install modulex-integrations
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
With every integration's optional dependencies:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install "modulex-integrations[all]"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or only the integrations you need (extras are populated as
|
|
52
|
+
integrations are migrated):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install "modulex-integrations[github,slack]"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Per-integration layout
|
|
59
|
+
|
|
60
|
+
Every integration ships in the same shape:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
src/modulex_integrations/tools/<name>/
|
|
64
|
+
├── __init__.py # re-exports manifest + TOOLS
|
|
65
|
+
├── manifest.py # IntegrationManifest instance
|
|
66
|
+
├── tools.py # LangChain @tool functions
|
|
67
|
+
├── outputs.py # pydantic response models (UI/docs derive JSONSchema)
|
|
68
|
+
├── dependencies.toml # per-integration runtime deps
|
|
69
|
+
├── README.md # 5-section strict template (see CONTRIBUTING.md)
|
|
70
|
+
└── tests/
|
|
71
|
+
└── test_<name>.py
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The contract is enforced by pydantic types in
|
|
75
|
+
[`src/modulex_integrations/schema.py`](src/modulex_integrations/schema.py)
|
|
76
|
+
with `extra="forbid"` everywhere — a typo in a manifest fails at
|
|
77
|
+
import time, not at runtime.
|
|
78
|
+
|
|
79
|
+
## Development
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git clone https://github.com/ModuleXAI/modulex-integrations.git
|
|
83
|
+
cd modulex-integrations
|
|
84
|
+
pip install -e ".[dev]"
|
|
85
|
+
pytest
|
|
86
|
+
ruff check src tests
|
|
87
|
+
mypy src/modulex_integrations
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Roadmap
|
|
91
|
+
|
|
92
|
+
- **Phase 1 — bootstrap (this commit).** Schema, packaging, CI,
|
|
93
|
+
community meta files, cross-repo brief workflow.
|
|
94
|
+
- **Phase 2 — github POC migration.** One integration end-to-end:
|
|
95
|
+
manifest, tools, outputs, tests, entry point, real
|
|
96
|
+
`httpx.MockTransport` coverage of at least one action.
|
|
97
|
+
- **Phase 3 — scripted bulk migration.** The remaining 44
|
|
98
|
+
integrations migrated by a one-shot script, then reviewed
|
|
99
|
+
individually.
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.20", "hatch-vcs>=0.4"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "modulex-integrations"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Community-contributable integrations (tools) for ModuleX"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "ModuleX", email = "team@modulex.dev" }]
|
|
14
|
+
keywords = ["modulex", "integrations", "ai", "tools", "langchain"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Typing :: Typed",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"pydantic>=2.5.0",
|
|
26
|
+
"httpx>=0.25.0",
|
|
27
|
+
"langchain-core>=0.3.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
# `all` is auto-assembled from per-integration `dependencies.toml`
|
|
32
|
+
# files by scripts/assemble_dependencies.py. Do not edit by hand.
|
|
33
|
+
all = []
|
|
34
|
+
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=8.0.0",
|
|
37
|
+
"pytest-asyncio>=0.23.0",
|
|
38
|
+
"pytest-httpx>=0.30.0",
|
|
39
|
+
"ruff>=0.6.0",
|
|
40
|
+
"mypy>=1.10.0",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/ModuleXAI/modulex-integrations"
|
|
45
|
+
Repository = "https://github.com/ModuleXAI/modulex-integrations"
|
|
46
|
+
Issues = "https://github.com/ModuleXAI/modulex-integrations/issues"
|
|
47
|
+
|
|
48
|
+
# Entry points — each migrated tool gets one line here.
|
|
49
|
+
[project.entry-points."modulex.tools"]
|
|
50
|
+
# Example (added when github POC lands):
|
|
51
|
+
# github = "modulex_integrations.tools.github"
|
|
52
|
+
|
|
53
|
+
[tool.hatch.version]
|
|
54
|
+
# Version is derived from the latest git tag matching v* (e.g. v0.1.0,
|
|
55
|
+
# v0.2.0a1). Between tags, builds carry a PEP 440 dev-suffixed version.
|
|
56
|
+
# Release CI only publishes on a tag push, so PyPI never sees a dev
|
|
57
|
+
# version.
|
|
58
|
+
source = "vcs"
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.hooks.vcs]
|
|
61
|
+
# Writes a `_version.py` into the source tree at build time. Kept out
|
|
62
|
+
# of git via .gitignore; importlib.metadata is the runtime path we use.
|
|
63
|
+
version-file = "src/modulex_integrations/_version.py"
|
|
64
|
+
|
|
65
|
+
[tool.hatch.build.targets.wheel]
|
|
66
|
+
packages = ["src/modulex_integrations"]
|
|
67
|
+
|
|
68
|
+
[tool.hatch.build.targets.sdist]
|
|
69
|
+
include = [
|
|
70
|
+
"src",
|
|
71
|
+
"tests",
|
|
72
|
+
"scripts",
|
|
73
|
+
"README.md",
|
|
74
|
+
"CONTRIBUTING.md",
|
|
75
|
+
"CHANGELOG.md",
|
|
76
|
+
"LICENSE",
|
|
77
|
+
"pyproject.toml",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
[tool.pytest.ini_options]
|
|
81
|
+
testpaths = ["tests", "src/modulex_integrations"]
|
|
82
|
+
python_files = "test_*.py"
|
|
83
|
+
asyncio_mode = "auto"
|
|
84
|
+
addopts = [
|
|
85
|
+
"--strict-markers",
|
|
86
|
+
"--import-mode=importlib",
|
|
87
|
+
"-ra",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
[tool.ruff]
|
|
91
|
+
line-length = 100
|
|
92
|
+
target-version = "py312"
|
|
93
|
+
src = ["src", "tests"]
|
|
94
|
+
# Generated by hatch-vcs at build time; not under our control.
|
|
95
|
+
extend-exclude = ["src/modulex_integrations/_version.py"]
|
|
96
|
+
|
|
97
|
+
[tool.ruff.lint]
|
|
98
|
+
select = ["E", "F", "I", "N", "W", "B", "C4", "UP", "RUF"]
|
|
99
|
+
|
|
100
|
+
[tool.ruff.lint.per-file-ignores]
|
|
101
|
+
"tests/**/*.py" = ["N802", "N803", "N806"]
|
|
102
|
+
|
|
103
|
+
[tool.mypy]
|
|
104
|
+
python_version = "3.12"
|
|
105
|
+
strict = true
|
|
106
|
+
warn_unused_configs = true
|
|
107
|
+
files = ["src/modulex_integrations"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# scripts/
|
|
2
|
+
|
|
3
|
+
Utility scripts maintained alongside the package.
|
|
4
|
+
|
|
5
|
+
Planned scripts:
|
|
6
|
+
|
|
7
|
+
- `migrate_from_modulex.py` — one-shot conversion of legacy `py/app/integrations/tools/<name>_integration.json` files into `src/modulex_integrations/tools/<name>/` directories (manifest + outputs + tests stubs).
|
|
8
|
+
- `assemble_dependencies.py` — read every `tools/<name>/dependencies.toml` and regenerate `[project.optional-dependencies]` in the root `pyproject.toml`. Run by CI on every PR.
|
|
9
|
+
|
|
10
|
+
Neither script is implemented yet; placeholders only.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""modulex-integrations — community-contributable integrations for ModuleX.
|
|
2
|
+
|
|
3
|
+
Every integration lives at ``modulex_integrations.tools.<name>`` and is
|
|
4
|
+
discovered by the modulex runtime via the ``modulex.tools`` entry-point
|
|
5
|
+
group declared in ``pyproject.toml``.
|
|
6
|
+
|
|
7
|
+
This top-level module re-exports the schema types so contributors can
|
|
8
|
+
write a manifest with::
|
|
9
|
+
|
|
10
|
+
from modulex_integrations import (
|
|
11
|
+
IntegrationManifest, ActionDefinition, OAuth2AuthSchema, ...
|
|
12
|
+
)
|
|
13
|
+
"""
|
|
14
|
+
from importlib.metadata import PackageNotFoundError
|
|
15
|
+
from importlib.metadata import version as _pkg_version
|
|
16
|
+
|
|
17
|
+
from modulex_integrations.schema import (
|
|
18
|
+
ActionDefinition,
|
|
19
|
+
ApiKeyAuthSchema,
|
|
20
|
+
AuthSchema,
|
|
21
|
+
BearerTokenAuthSchema,
|
|
22
|
+
CustomAuthSchema,
|
|
23
|
+
EnvVar,
|
|
24
|
+
IntegrationManifest,
|
|
25
|
+
InternalAuthSchema,
|
|
26
|
+
ModulexKeyAuthSchema,
|
|
27
|
+
OAuth2AuthSchema,
|
|
28
|
+
OAuthConfig,
|
|
29
|
+
ParameterDef,
|
|
30
|
+
SuccessIndicators,
|
|
31
|
+
TestEndpoint,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
__version__ = _pkg_version("modulex-integrations")
|
|
36
|
+
except PackageNotFoundError: # source checkout without `pip install -e .`
|
|
37
|
+
__version__ = "0.0.0+unknown"
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"ActionDefinition",
|
|
41
|
+
"ApiKeyAuthSchema",
|
|
42
|
+
"AuthSchema",
|
|
43
|
+
"BearerTokenAuthSchema",
|
|
44
|
+
"CustomAuthSchema",
|
|
45
|
+
"EnvVar",
|
|
46
|
+
"IntegrationManifest",
|
|
47
|
+
"InternalAuthSchema",
|
|
48
|
+
"ModulexKeyAuthSchema",
|
|
49
|
+
"OAuth2AuthSchema",
|
|
50
|
+
"OAuthConfig",
|
|
51
|
+
"ParameterDef",
|
|
52
|
+
"SuccessIndicators",
|
|
53
|
+
"TestEndpoint",
|
|
54
|
+
"__version__",
|
|
55
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.0.1a0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 0, 1, 'a0')
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
File without changes
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Schema definitions for the modulex-integrations package.
|
|
2
|
+
|
|
3
|
+
Every integration declares its metadata by constructing an
|
|
4
|
+
``IntegrationManifest`` instance in its own ``manifest.py``. The modulex
|
|
5
|
+
runtime imports these manifests via the ``modulex.tools`` entry-point
|
|
6
|
+
group and uses them to drive credential UI, validation, and tool
|
|
7
|
+
discovery.
|
|
8
|
+
|
|
9
|
+
Design choices worth knowing:
|
|
10
|
+
|
|
11
|
+
- All models use ``extra="forbid"`` so a typo in a contributor's
|
|
12
|
+
manifest fails at import time, not at runtime.
|
|
13
|
+
- ``auth_schemas`` is a discriminated union keyed on ``auth_type``;
|
|
14
|
+
the same integration can expose multiple credential methods (GitHub
|
|
15
|
+
supports both OAuth2 and a personal access token).
|
|
16
|
+
- Action ``output_schema`` is intentionally absent. The shape of every
|
|
17
|
+
action's return value is derived from the LangChain ``@tool``
|
|
18
|
+
function's return-type annotation (defined in each integration's
|
|
19
|
+
``outputs.py``); the runtime obtains it via
|
|
20
|
+
``Model.model_json_schema()`` at startup.
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import Annotated, Any, Literal
|
|
25
|
+
|
|
26
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"ActionDefinition",
|
|
30
|
+
"ApiKeyAuthSchema",
|
|
31
|
+
"AuthSchema",
|
|
32
|
+
"BearerTokenAuthSchema",
|
|
33
|
+
"CustomAuthSchema",
|
|
34
|
+
"EnvVar",
|
|
35
|
+
"IntegrationManifest",
|
|
36
|
+
"InternalAuthSchema",
|
|
37
|
+
"ModulexKeyAuthSchema",
|
|
38
|
+
"OAuth2AuthSchema",
|
|
39
|
+
"OAuthConfig",
|
|
40
|
+
"ParameterDef",
|
|
41
|
+
"SuccessIndicators",
|
|
42
|
+
"TestEndpoint",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# --- Parameters --------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
ParameterType = Literal[
|
|
49
|
+
"string",
|
|
50
|
+
"integer",
|
|
51
|
+
"number",
|
|
52
|
+
"boolean",
|
|
53
|
+
"array",
|
|
54
|
+
"object",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ParameterDef(BaseModel):
|
|
59
|
+
"""Definition of a single action parameter."""
|
|
60
|
+
|
|
61
|
+
model_config = ConfigDict(extra="forbid")
|
|
62
|
+
|
|
63
|
+
type: ParameterType
|
|
64
|
+
description: str
|
|
65
|
+
default: Any = None
|
|
66
|
+
required: bool = False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# --- Actions -----------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ActionDefinition(BaseModel):
|
|
73
|
+
"""One callable action exposed by the integration.
|
|
74
|
+
|
|
75
|
+
The return shape is derived from the matching ``@tool`` function's
|
|
76
|
+
return-type annotation in ``tools.py`` — do not duplicate it here.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
model_config = ConfigDict(extra="forbid")
|
|
80
|
+
|
|
81
|
+
name: str = Field(pattern=r"^[a-z][a-z0-9_]*$")
|
|
82
|
+
description: str
|
|
83
|
+
parameters: dict[str, ParameterDef] = Field(default_factory=dict)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# --- Credentials -------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class EnvVar(BaseModel):
|
|
90
|
+
"""A configurable secret/setting the operator must provide."""
|
|
91
|
+
|
|
92
|
+
model_config = ConfigDict(extra="forbid")
|
|
93
|
+
|
|
94
|
+
name: str
|
|
95
|
+
display_name: str
|
|
96
|
+
description: str
|
|
97
|
+
required: bool = True
|
|
98
|
+
sensitive: bool = False
|
|
99
|
+
only_for_custom: bool = False
|
|
100
|
+
sample_format: str | None = None
|
|
101
|
+
about_url: str | None = None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class SuccessIndicators(BaseModel):
|
|
105
|
+
"""How to tell a credential test endpoint succeeded."""
|
|
106
|
+
|
|
107
|
+
model_config = ConfigDict(extra="forbid")
|
|
108
|
+
|
|
109
|
+
status_codes: list[int]
|
|
110
|
+
response_fields: list[str] | None = None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TestEndpoint(BaseModel):
|
|
114
|
+
"""Endpoint hit to validate a configured credential.
|
|
115
|
+
|
|
116
|
+
Header values may contain placeholders such as ``{access_token}`` or
|
|
117
|
+
``{token}`` which the runtime substitutes with the resolved
|
|
118
|
+
credential value.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
__test__ = False # pydantic model, not a pytest test class
|
|
122
|
+
model_config = ConfigDict(extra="forbid")
|
|
123
|
+
|
|
124
|
+
url: str
|
|
125
|
+
method: Literal["GET", "POST", "PUT", "DELETE", "PATCH"] = "GET"
|
|
126
|
+
headers: dict[str, str] = Field(default_factory=dict)
|
|
127
|
+
success_indicators: SuccessIndicators
|
|
128
|
+
cost_level: str = "free"
|
|
129
|
+
description: str | None = None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# --- Auth schemas (discriminated union) --------------------------------
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class _AuthSchemaBase(BaseModel):
|
|
136
|
+
"""Fields shared by every auth_schema variant."""
|
|
137
|
+
|
|
138
|
+
model_config = ConfigDict(extra="forbid")
|
|
139
|
+
|
|
140
|
+
display_name: str
|
|
141
|
+
description: str
|
|
142
|
+
setup_instructions: list[str] | None = None
|
|
143
|
+
setup_environment_variables: list[EnvVar] = Field(default_factory=list)
|
|
144
|
+
test_endpoint: TestEndpoint
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class OAuthConfig(BaseModel):
|
|
148
|
+
"""OAuth 2.0 authorize/token URL bundle."""
|
|
149
|
+
|
|
150
|
+
model_config = ConfigDict(extra="forbid")
|
|
151
|
+
|
|
152
|
+
auth_url: str
|
|
153
|
+
token_url: str
|
|
154
|
+
scopes: list[str] = Field(default_factory=list)
|
|
155
|
+
token_auth_method: Literal["body", "basic"] = "body"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class OAuth2AuthSchema(_AuthSchemaBase):
|
|
159
|
+
auth_type: Literal["oauth2"] = "oauth2"
|
|
160
|
+
oauth_config: OAuthConfig
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class BearerTokenAuthSchema(_AuthSchemaBase):
|
|
164
|
+
auth_type: Literal["bearer_token"] = "bearer_token"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class ApiKeyAuthSchema(_AuthSchemaBase):
|
|
168
|
+
auth_type: Literal["api_key"] = "api_key"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class ModulexKeyAuthSchema(_AuthSchemaBase):
|
|
172
|
+
auth_type: Literal["modulex_key"] = "modulex_key"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class CustomAuthSchema(_AuthSchemaBase):
|
|
176
|
+
auth_type: Literal["custom"] = "custom"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class InternalAuthSchema(_AuthSchemaBase):
|
|
180
|
+
auth_type: Literal["internal"] = "internal"
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
AuthSchema = Annotated[
|
|
184
|
+
OAuth2AuthSchema
|
|
185
|
+
| BearerTokenAuthSchema
|
|
186
|
+
| ApiKeyAuthSchema
|
|
187
|
+
| ModulexKeyAuthSchema
|
|
188
|
+
| CustomAuthSchema
|
|
189
|
+
| InternalAuthSchema,
|
|
190
|
+
Field(discriminator="auth_type"),
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# --- Top-level manifest ------------------------------------------------
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class IntegrationManifest(BaseModel):
|
|
198
|
+
"""The contract every integration's ``manifest.py`` produces."""
|
|
199
|
+
|
|
200
|
+
model_config = ConfigDict(extra="forbid")
|
|
201
|
+
|
|
202
|
+
integration_type: Literal["tool"] = "tool"
|
|
203
|
+
name: str = Field(pattern=r"^[a-z][a-z0-9_]*$")
|
|
204
|
+
display_name: str
|
|
205
|
+
description: str
|
|
206
|
+
version: str = "1.0.0"
|
|
207
|
+
author: str = "ModuleX"
|
|
208
|
+
logo: str | None = None
|
|
209
|
+
app_url: str | None = None
|
|
210
|
+
categories: list[str] = Field(default_factory=list)
|
|
211
|
+
actions: list[ActionDefinition] = Field(default_factory=list)
|
|
212
|
+
auth_schemas: list[AuthSchema] = Field(default_factory=list)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Namespace for individual integration packages.
|
|
2
|
+
|
|
3
|
+
Each integration lives in its own directory under this module:
|
|
4
|
+
``modulex_integrations.tools.<name>``. The modulex runtime discovers
|
|
5
|
+
them via the ``modulex.tools`` entry-point group; do not import from
|
|
6
|
+
here directly.
|
|
7
|
+
"""
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Top-level pytest configuration for modulex-integrations.
|
|
2
|
+
|
|
3
|
+
Per-integration tests live next to each integration
|
|
4
|
+
(``src/modulex_integrations/tools/<name>/tests/``). Shared fixtures
|
|
5
|
+
applicable to many integrations belong here. Right now this file is a
|
|
6
|
+
placeholder; fixtures will be added as the github POC migration lands.
|
|
7
|
+
"""
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Contract tests for the IntegrationManifest schema.
|
|
2
|
+
|
|
3
|
+
These tests pin down the shape that every integration's manifest must
|
|
4
|
+
satisfy. They use a github-shaped manifest because it exercises every
|
|
5
|
+
schema feature: multi-action, multi-auth (OAuth2 + bearer), parameters
|
|
6
|
+
with defaults, env vars with rich UI metadata.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from pydantic import ValidationError
|
|
12
|
+
|
|
13
|
+
from modulex_integrations import (
|
|
14
|
+
ActionDefinition,
|
|
15
|
+
BearerTokenAuthSchema,
|
|
16
|
+
EnvVar,
|
|
17
|
+
IntegrationManifest,
|
|
18
|
+
OAuth2AuthSchema,
|
|
19
|
+
OAuthConfig,
|
|
20
|
+
ParameterDef,
|
|
21
|
+
SuccessIndicators,
|
|
22
|
+
TestEndpoint,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _github_like_manifest() -> IntegrationManifest:
|
|
27
|
+
"""Construct a manifest shaped like the existing github_integration.json.
|
|
28
|
+
|
|
29
|
+
Truncated to two actions; the goal is exercising every schema
|
|
30
|
+
feature, not duplicating the 1180-line JSON.
|
|
31
|
+
"""
|
|
32
|
+
return IntegrationManifest(
|
|
33
|
+
name="github",
|
|
34
|
+
display_name="GitHub",
|
|
35
|
+
description="GitHub repository and code management platform",
|
|
36
|
+
version="1.0.0",
|
|
37
|
+
author="ModuleX",
|
|
38
|
+
logo="https://cdn.jsdelivr.net/gh/ModuleXAI/logox@main/tools/github.svg",
|
|
39
|
+
app_url="https://github.com",
|
|
40
|
+
categories=["Developer Tools & Infrastructure", "version-control", "productivity"],
|
|
41
|
+
actions=[
|
|
42
|
+
ActionDefinition(
|
|
43
|
+
name="list_repositories",
|
|
44
|
+
description="List repositories for the authenticated user or organization",
|
|
45
|
+
parameters={
|
|
46
|
+
"visibility": ParameterDef(
|
|
47
|
+
type="string",
|
|
48
|
+
description="Filter by visibility: all, public, private",
|
|
49
|
+
default="all",
|
|
50
|
+
),
|
|
51
|
+
"per_page": ParameterDef(
|
|
52
|
+
type="integer",
|
|
53
|
+
description="Results per page (max 100)",
|
|
54
|
+
default=30,
|
|
55
|
+
),
|
|
56
|
+
},
|
|
57
|
+
),
|
|
58
|
+
ActionDefinition(
|
|
59
|
+
name="create_issue",
|
|
60
|
+
description="Create a new issue",
|
|
61
|
+
parameters={
|
|
62
|
+
"owner": ParameterDef(
|
|
63
|
+
type="string", description="Repository owner", required=True
|
|
64
|
+
),
|
|
65
|
+
"repo": ParameterDef(
|
|
66
|
+
type="string", description="Repository name", required=True
|
|
67
|
+
),
|
|
68
|
+
"title": ParameterDef(
|
|
69
|
+
type="string", description="Issue title", required=True
|
|
70
|
+
),
|
|
71
|
+
"labels": ParameterDef(type="array", description="Issue labels"),
|
|
72
|
+
},
|
|
73
|
+
),
|
|
74
|
+
],
|
|
75
|
+
auth_schemas=[
|
|
76
|
+
OAuth2AuthSchema(
|
|
77
|
+
display_name="OAuth2 Authentication",
|
|
78
|
+
description="Connect using GitHub OAuth (recommended for most use cases)",
|
|
79
|
+
setup_environment_variables=[
|
|
80
|
+
EnvVar(
|
|
81
|
+
name="GITHUB_OAUTH2_CLIENT_ID",
|
|
82
|
+
display_name="Client ID",
|
|
83
|
+
description="GitHub OAuth App Client ID",
|
|
84
|
+
sensitive=False,
|
|
85
|
+
only_for_custom=True,
|
|
86
|
+
sample_format="Iv1.xxxxxxxxxxxxxxxx",
|
|
87
|
+
about_url="https://github.com/settings/applications/new",
|
|
88
|
+
),
|
|
89
|
+
EnvVar(
|
|
90
|
+
name="GITHUB_OAUTH2_CLIENT_SECRET",
|
|
91
|
+
display_name="Client Secret",
|
|
92
|
+
description="GitHub OAuth App Client Secret",
|
|
93
|
+
sensitive=True,
|
|
94
|
+
only_for_custom=True,
|
|
95
|
+
sample_format="x" * 40,
|
|
96
|
+
about_url="https://github.com/settings/applications/new",
|
|
97
|
+
),
|
|
98
|
+
],
|
|
99
|
+
oauth_config=OAuthConfig(
|
|
100
|
+
auth_url="https://github.com/login/oauth/authorize",
|
|
101
|
+
token_url="https://github.com/login/oauth/access_token",
|
|
102
|
+
scopes=["repo", "user", "read:org", "workflow"],
|
|
103
|
+
token_auth_method="body",
|
|
104
|
+
),
|
|
105
|
+
test_endpoint=TestEndpoint(
|
|
106
|
+
url="https://api.github.com/user",
|
|
107
|
+
method="GET",
|
|
108
|
+
headers={
|
|
109
|
+
"Authorization": "Bearer {access_token}",
|
|
110
|
+
"Accept": "application/vnd.github+json",
|
|
111
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
112
|
+
},
|
|
113
|
+
success_indicators=SuccessIndicators(
|
|
114
|
+
status_codes=[200], response_fields=["login", "id"]
|
|
115
|
+
),
|
|
116
|
+
cost_level="free",
|
|
117
|
+
),
|
|
118
|
+
),
|
|
119
|
+
BearerTokenAuthSchema(
|
|
120
|
+
display_name="Personal Access Token",
|
|
121
|
+
description="Use your GitHub Personal Access Token (Classic or Fine-grained)",
|
|
122
|
+
setup_instructions=[
|
|
123
|
+
"Go to GitHub Settings -> Developer settings -> Personal access tokens",
|
|
124
|
+
"Choose 'Tokens (classic)' OR 'Fine-grained tokens (Beta)'",
|
|
125
|
+
"Generate, copy, and configure GITHUB_PERSONAL_TOKEN.",
|
|
126
|
+
],
|
|
127
|
+
setup_environment_variables=[
|
|
128
|
+
EnvVar(
|
|
129
|
+
name="GITHUB_PERSONAL_TOKEN",
|
|
130
|
+
display_name="Personal Access Token",
|
|
131
|
+
description="Your GitHub Personal Access Token",
|
|
132
|
+
sensitive=True,
|
|
133
|
+
sample_format="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
134
|
+
about_url="https://github.com/settings/tokens",
|
|
135
|
+
),
|
|
136
|
+
],
|
|
137
|
+
test_endpoint=TestEndpoint(
|
|
138
|
+
url="https://api.github.com/user",
|
|
139
|
+
headers={"Authorization": "Bearer {token}"},
|
|
140
|
+
success_indicators=SuccessIndicators(
|
|
141
|
+
status_codes=[200], response_fields=["login", "id"]
|
|
142
|
+
),
|
|
143
|
+
),
|
|
144
|
+
),
|
|
145
|
+
],
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TestManifestConstruction:
|
|
150
|
+
def test_github_like_manifest_validates(self) -> None:
|
|
151
|
+
manifest = _github_like_manifest()
|
|
152
|
+
assert manifest.name == "github"
|
|
153
|
+
assert manifest.integration_type == "tool"
|
|
154
|
+
assert len(manifest.actions) == 2
|
|
155
|
+
assert len(manifest.auth_schemas) == 2
|
|
156
|
+
|
|
157
|
+
def test_discriminator_picks_oauth2_variant(self) -> None:
|
|
158
|
+
manifest = _github_like_manifest()
|
|
159
|
+
first = manifest.auth_schemas[0]
|
|
160
|
+
assert isinstance(first, OAuth2AuthSchema)
|
|
161
|
+
assert first.oauth_config.scopes == ["repo", "user", "read:org", "workflow"]
|
|
162
|
+
|
|
163
|
+
def test_discriminator_picks_bearer_variant(self) -> None:
|
|
164
|
+
manifest = _github_like_manifest()
|
|
165
|
+
second = manifest.auth_schemas[1]
|
|
166
|
+
assert isinstance(second, BearerTokenAuthSchema)
|
|
167
|
+
# bearer variant has no oauth_config; extra="forbid" guards this
|
|
168
|
+
assert not hasattr(second, "oauth_config")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class TestManifestRoundtrip:
|
|
172
|
+
def test_serialize_then_validate(self) -> None:
|
|
173
|
+
original = _github_like_manifest()
|
|
174
|
+
dumped = original.model_dump()
|
|
175
|
+
restored = IntegrationManifest.model_validate(dumped)
|
|
176
|
+
assert restored == original
|
|
177
|
+
|
|
178
|
+
def test_model_json_schema_describes_auth_union(self) -> None:
|
|
179
|
+
schema = IntegrationManifest.model_json_schema()
|
|
180
|
+
# The generated JSONSchema is what the UI consumes; make sure
|
|
181
|
+
# the auth_schemas field is present and discriminated.
|
|
182
|
+
assert "auth_schemas" in schema["properties"]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class TestValidation:
|
|
186
|
+
def test_invalid_auth_type_rejected(self) -> None:
|
|
187
|
+
bad = {
|
|
188
|
+
"name": "x",
|
|
189
|
+
"display_name": "X",
|
|
190
|
+
"description": "y",
|
|
191
|
+
"auth_schemas": [
|
|
192
|
+
{
|
|
193
|
+
"auth_type": "not_a_real_auth",
|
|
194
|
+
"display_name": "z",
|
|
195
|
+
"description": "z",
|
|
196
|
+
"test_endpoint": {
|
|
197
|
+
"url": "https://example.com",
|
|
198
|
+
"headers": {},
|
|
199
|
+
"success_indicators": {"status_codes": [200]},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
}
|
|
204
|
+
with pytest.raises(ValidationError):
|
|
205
|
+
IntegrationManifest.model_validate(bad)
|
|
206
|
+
|
|
207
|
+
def test_invalid_name_slug_rejected(self) -> None:
|
|
208
|
+
with pytest.raises(ValidationError):
|
|
209
|
+
IntegrationManifest(
|
|
210
|
+
name="GitHub",
|
|
211
|
+
display_name="X",
|
|
212
|
+
description="y",
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def test_extra_field_rejected(self) -> None:
|
|
216
|
+
with pytest.raises(ValidationError):
|
|
217
|
+
IntegrationManifest.model_validate(
|
|
218
|
+
{
|
|
219
|
+
"name": "x",
|
|
220
|
+
"display_name": "X",
|
|
221
|
+
"description": "y",
|
|
222
|
+
"bogus_field": "nope",
|
|
223
|
+
}
|
|
224
|
+
)
|