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.
@@ -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
+ [![CI](https://github.com/ModuleXAI/modulex-integrations/actions/workflows/validate.yml/badge.svg)](https://github.com/ModuleXAI/modulex-integrations/actions/workflows/validate.yml)
35
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
36
+ [![PyPI](https://img.shields.io/pypi/v/modulex-integrations.svg)](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
+ [![CI](https://github.com/ModuleXAI/modulex-integrations/actions/workflows/validate.yml/badge.svg)](https://github.com/ModuleXAI/modulex-integrations/actions/workflows/validate.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
+ [![PyPI](https://img.shields.io/pypi/v/modulex-integrations.svg)](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
@@ -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
+ )