hermes-mai-image 0.2.0__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.
- hermes_mai_image-0.2.0/.github/dependabot.yml +10 -0
- hermes_mai_image-0.2.0/.github/workflows/ci.yml +66 -0
- hermes_mai_image-0.2.0/.github/workflows/release.yml +44 -0
- hermes_mai_image-0.2.0/.gitignore +16 -0
- hermes_mai_image-0.2.0/AGENTS.md +128 -0
- hermes_mai_image-0.2.0/CHANGELOG.md +71 -0
- hermes_mai_image-0.2.0/LICENSE +21 -0
- hermes_mai_image-0.2.0/PKG-INFO +345 -0
- hermes_mai_image-0.2.0/README.md +315 -0
- hermes_mai_image-0.2.0/catalog/README.md +19 -0
- hermes_mai_image-0.2.0/catalog/mai-image.yaml +36 -0
- hermes_mai_image-0.2.0/pyproject.toml +60 -0
- hermes_mai_image-0.2.0/src/hermes_mai_image/__init__.py +5 -0
- hermes_mai_image-0.2.0/src/hermes_mai_image/plugin.yaml +46 -0
- hermes_mai_image-0.2.0/src/hermes_mai_image/provider.py +738 -0
- hermes_mai_image-0.2.0/tests/test_mai_image_plugin.py +1154 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
matrix:
|
|
17
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
20
|
+
- name: Set up Python
|
|
21
|
+
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python-version }}
|
|
24
|
+
allow-prereleases: true
|
|
25
|
+
- name: Install test dependencies
|
|
26
|
+
run: python -m pip install -e '.[test,lint]'
|
|
27
|
+
- name: Lint
|
|
28
|
+
run: ruff check src tests
|
|
29
|
+
- name: Run tests
|
|
30
|
+
run: python -m pytest -q
|
|
31
|
+
- name: Compile Python
|
|
32
|
+
run: python -m compileall -q src tests
|
|
33
|
+
- name: Build wheel
|
|
34
|
+
run: python -m pip wheel --no-deps --wheel-dir dist .
|
|
35
|
+
- name: Verify plugin.yaml is in the wheel
|
|
36
|
+
run: python -m zipfile -l dist/*.whl | grep plugin.yaml
|
|
37
|
+
|
|
38
|
+
catalog-validation:
|
|
39
|
+
# Validates the plugin against Hermes' own plugin validator, which is the gate the
|
|
40
|
+
# Hermes plugin catalog applies. Hermes must be installed for this to run, so it is a
|
|
41
|
+
# separate job that pins a Hermes version rather than pulling it into the test matrix.
|
|
42
|
+
#
|
|
43
|
+
# The plugin manifest lives at src/hermes_mai_image/plugin.yaml, so the validator must
|
|
44
|
+
# target that directory. Validating the repository root fails with
|
|
45
|
+
# "no plugin.yaml (or portable plugin.json) in the plugin directory".
|
|
46
|
+
#
|
|
47
|
+
# Reproduce locally with the Hermes CLI on PATH:
|
|
48
|
+
# hermes plugins validate src/hermes_mai_image
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
steps:
|
|
51
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
52
|
+
- name: Set up Python
|
|
53
|
+
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
54
|
+
with:
|
|
55
|
+
python-version: "3.12"
|
|
56
|
+
- name: Install Hermes (pinned release tag)
|
|
57
|
+
# Pinned so catalog validation is deterministic; bump deliberately. Hermes is not
|
|
58
|
+
# installable from PyPI (the published hermes-agent is stale) and refuses wheel
|
|
59
|
+
# builds, so clone the release tag and install it editable, as Hermes itself does.
|
|
60
|
+
env:
|
|
61
|
+
HERMES_REF: v2026.9.24
|
|
62
|
+
run: |
|
|
63
|
+
git clone --depth 1 --branch "$HERMES_REF" https://github.com/NousResearch/hermes-agent.git "$RUNNER_TEMP/hermes-agent"
|
|
64
|
+
python -m pip install -e "$RUNNER_TEMP/hermes-agent"
|
|
65
|
+
- name: Validate plugin manifest
|
|
66
|
+
run: hermes plugins validate src/hermes_mai_image
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
15
|
+
- name: Set up Python
|
|
16
|
+
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
- name: Build sdist and wheel
|
|
20
|
+
run: |
|
|
21
|
+
python -m pip install build twine
|
|
22
|
+
python -m build
|
|
23
|
+
- name: Check distribution metadata
|
|
24
|
+
run: python -m twine check --strict dist/*
|
|
25
|
+
- name: Verify plugin.yaml is in the wheel
|
|
26
|
+
run: python -m zipfile -l dist/*.whl | grep plugin.yaml
|
|
27
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
28
|
+
with:
|
|
29
|
+
name: dist
|
|
30
|
+
path: dist/
|
|
31
|
+
|
|
32
|
+
publish:
|
|
33
|
+
needs: build
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
environment: pypi
|
|
36
|
+
permissions:
|
|
37
|
+
id-token: write # PyPI Trusted Publishing (OIDC); no stored token
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
40
|
+
with:
|
|
41
|
+
name: dist
|
|
42
|
+
path: dist/
|
|
43
|
+
- name: Publish to PyPI
|
|
44
|
+
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hermes-mai-image
|
|
3
|
+
description: "Repository guidance for the standalone Hermes Agent Microsoft Foundry MAI image-generation plugin."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Hermes MAI Image Plugin
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
This repository contains a standalone Hermes Agent image-generation provider for Microsoft Foundry MAI image models. It is distributed from GitHub and is designed to become a PyPI package later.
|
|
11
|
+
|
|
12
|
+
## Repository layout
|
|
13
|
+
|
|
14
|
+
- `src/hermes_mai_image/provider.py` — provider implementation, model discovery, generation, editing, response handling, and cache integration.
|
|
15
|
+
- `src/hermes_mai_image/plugin.yaml` — Hermes plugin metadata.
|
|
16
|
+
- `src/hermes_mai_image/__init__.py` — public package exports and the module-level `register` hook.
|
|
17
|
+
- `tests/test_mai_image_plugin.py` — mocked HTTP contract tests; tests never use live Azure credentials.
|
|
18
|
+
- `README.md` — installation, configuration, Web UI, CLI, troubleshooting, and update instructions.
|
|
19
|
+
- `pyproject.toml` — package metadata, dependencies, test extra, and the `hermes_agent.plugins` entry point.
|
|
20
|
+
|
|
21
|
+
## Hermes plugin contract
|
|
22
|
+
|
|
23
|
+
The package must keep this entry point format:
|
|
24
|
+
|
|
25
|
+
```toml
|
|
26
|
+
[project.entry-points."hermes_agent.plugins"]
|
|
27
|
+
azure-mai-image = "hermes_mai_image"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Hermes discovers this provider as a module import. Do not change it to `hermes_mai_image:register` without verifying Hermes' provider discovery contract; that target is treated differently by Hermes and previously caused the provider not to appear in `hermes tools`.
|
|
31
|
+
|
|
32
|
+
The provider identity is:
|
|
33
|
+
|
|
34
|
+
- Name: `azure-mai`
|
|
35
|
+
- Display name: `Microsoft Foundry MAI`
|
|
36
|
+
- Plugin key: `azure-mai-image`
|
|
37
|
+
|
|
38
|
+
The module-level `register(ctx)` function calls `ctx.register_image_gen_provider(MAIImageProvider())`.
|
|
39
|
+
|
|
40
|
+
## API contract
|
|
41
|
+
|
|
42
|
+
Use Microsoft Foundry's native MAI API, not the standard OpenAI image API:
|
|
43
|
+
|
|
44
|
+
- Generation: `POST {endpoint}/mai/v1/images/generations`
|
|
45
|
+
- Editing: `POST {endpoint}/mai/v1/images/edits`
|
|
46
|
+
- Authentication: `api-key` request header
|
|
47
|
+
- Generation JSON fields: `model`, `prompt`, `width`, `height`
|
|
48
|
+
- Editing: multipart form fields `model`, `prompt`, and `image`
|
|
49
|
+
|
|
50
|
+
The configured endpoint is the resource root, such as `https://resource.services.ai.azure.com`. The provider appends `/mai/v1`; never require users to enter that suffix.
|
|
51
|
+
|
|
52
|
+
Required credentials/settings:
|
|
53
|
+
|
|
54
|
+
- `MAI_FOUNDRY_API_KEY` — secret; store in the active Hermes profile `.env` only.
|
|
55
|
+
- `MAI_FOUNDRY_ENDPOINT` — Foundry resource root.
|
|
56
|
+
- `MAI_IMAGE_MODEL` — optional deployment/model override; the exact Foundry deployment name is preferred.
|
|
57
|
+
- `MAI_FOUNDRY_MODELS_PATH` — optional discovery path override, default `/models`.
|
|
58
|
+
|
|
59
|
+
Never place credentials in source, tests, `config.yaml`, README examples, commits, logs, or chat messages.
|
|
60
|
+
|
|
61
|
+
## Development workflow
|
|
62
|
+
|
|
63
|
+
Work on a feature branch, not directly on `main`:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git switch main
|
|
67
|
+
git pull --ff-only origin main
|
|
68
|
+
git switch -c feature/<short-description>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Create a local environment and install test dependencies:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
python3 -m venv .venv
|
|
75
|
+
. .venv/bin/activate
|
|
76
|
+
pip install -e '.[test]'
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Run the focused suite:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
python -m pytest -q
|
|
83
|
+
python -m compileall -q src tests
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Build a wheel before packaging changes:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
python -m pip wheel --no-deps --wheel-dir dist .
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Testing rules
|
|
93
|
+
|
|
94
|
+
- Keep HTTP tests mocked; no test should require or transmit a real Azure key.
|
|
95
|
+
- Test request URLs, headers, payload shapes, multipart edit fields, response parsing, missing credentials, and cache materialization.
|
|
96
|
+
- Follow red-green-refactor for behavior changes: write a failing test first, confirm the expected failure, implement the smallest fix, then run the full suite.
|
|
97
|
+
- Do not weaken tests to accommodate an implementation error.
|
|
98
|
+
|
|
99
|
+
For live validation, use a separately configured Hermes profile and explicit user-provided credentials. Never add live credentials to test fixtures or CI.
|
|
100
|
+
|
|
101
|
+
## Release and distribution
|
|
102
|
+
|
|
103
|
+
The primary installation path is the GitHub URL:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pip install --upgrade "git+https://github.com/joescars/hermes-mai-image.git"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
After changing package metadata or provider code:
|
|
110
|
+
|
|
111
|
+
1. Run tests, compilation, and wheel build.
|
|
112
|
+
2. Review `git diff --check` and the staged file list.
|
|
113
|
+
3. Commit with a focused message.
|
|
114
|
+
4. Push the feature branch and open a PR unless the user explicitly asks to work directly on `main`.
|
|
115
|
+
5. After merge, verify the GitHub default branch contains the commit.
|
|
116
|
+
|
|
117
|
+
If the Hermes Python virtual environment is replaced by a Hermes update, users may need to reinstall the package into the new environment. Profile credentials and configuration are separate and should not be deleted by a plugin release.
|
|
118
|
+
|
|
119
|
+
## Documentation requirements
|
|
120
|
+
|
|
121
|
+
Update `README.md` when installation, configuration, endpoints, provider discovery, or update behavior changes. Keep the CLI and Web UI steps accurate. Explicitly state that the endpoint is the resource root and that `/mai/v1` is appended by the provider.
|
|
122
|
+
|
|
123
|
+
## Safety boundaries
|
|
124
|
+
|
|
125
|
+
- Do not modify Hermes core for provider-specific behavior.
|
|
126
|
+
- Do not add unauthenticated live API calls to tests or build steps.
|
|
127
|
+
- Do not print API keys, authorization headers, or full credential-bearing URLs.
|
|
128
|
+
- Do not use destructive Git commands (`reset --hard`, unapproved stash, broad clean) to resolve unrelated local changes.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
_Nothing yet._
|
|
11
|
+
|
|
12
|
+
## [0.2.0] - 2026-11-15
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Opt-in MAI-Image-2.6 `auto_aspect_ratio` and `web_grounding` options (config keys under
|
|
17
|
+
`image_gen.azure-mai` or `MAI_AUTO_ASPECT_RATIO` / `MAI_WEB_GROUNDING`), sent on generation and
|
|
18
|
+
edits only when enabled and skipped with a warning on non-2.6 deployments; `web_grounding` is
|
|
19
|
+
disclosed in the README and catalog. (#22)
|
|
20
|
+
- Image edits accept up to five reference images (repeated multipart `image` fields, in order), with
|
|
21
|
+
per-source checks, a total upload cap, and rejection of more than five before any request (#21).
|
|
22
|
+
- Model picker lists the configured deployment first (de-duplicated, bare id) and labels fallback
|
|
23
|
+
entries as model names that must match your deployment name; README documents best-effort discovery and
|
|
24
|
+
`az` deployment listing. (#27)
|
|
25
|
+
- Endpoint normalization and validation: pasted `/mai/v1`, `/models`, `/openai/v1` and project URL
|
|
26
|
+
suffixes resolve to the resource root, bare hosts get `https://`, non-https endpoints fail with
|
|
27
|
+
`invalid_config` without a request, and model discovery is skipped without credentials.
|
|
28
|
+
- Credential resolution through the Hermes secret scope, so provider settings are read from the
|
|
29
|
+
active profile's secret store instead of ambient environment lookup.
|
|
30
|
+
- An in-plugin, SSRF-safe image downloader that validates reference image URLs with the published
|
|
31
|
+
`tools.url_safety.is_safe_url` before fetching them. Local reference files are still checked with
|
|
32
|
+
the private `agent.file_safety.raise_if_read_blocked` import (documented in the README; tracked by #7).
|
|
33
|
+
- A draft catalog entry under `catalog/` for submission to the Hermes plugin catalog.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- Source images stream into memory (no temp file), local files are size-checked via `stat` before reading, `MAX_REMOTE_IMAGE_BYTES` is renamed `MAX_SOURCE_IMAGE_BYTES`, and the contradictory `agent.file_safety` comment is fixed.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
- CI hardening: ruff lint, Python 3.13/3.14 matrix, SHA-pinned actions, Dependabot, wheel `plugin.yaml` check, a tag-triggered PyPI Trusted Publishing release workflow, and SPDX license metadata (#29).
|
|
41
|
+
- Image sizes are now model-aware: MAI-Image-2.5 models use presets within the 1,048,576-pixel limit
|
|
42
|
+
(landscape `1248x832`, portrait `832x1248`); 2.6 deployments (detected from the name, or via
|
|
43
|
+
`MAI_IMAGE_SIZE_PROFILE` / `image_gen.azure-mai.size_profile` = `2.5`/`2.6`) use larger presets. (#17)
|
|
44
|
+
- Safer retry policy: 429 is always retried honouring `Retry-After` (seconds or HTTP-date) with a
|
|
45
|
+
long jittered backoff otherwise; only 502/503 are retried among 5xx (never 500/504, to avoid
|
|
46
|
+
double billing); retries share an overall `MAI_REQUEST_DEADLINE` budget (default 600s).
|
|
47
|
+
- Honor Hermes secret scope for `MAI_FOUNDRY_API_KEY` (no env fallback inside Hermes); endpoint is a non-secret setting resolvable from `image_gen.azure-mai.*` config or env, and model/models-path are declared optional (#25).
|
|
48
|
+
- HTTP error messages are now accurate per status: 400 reports a rejected request (with the service
|
|
49
|
+
`error.code`/`message` and size/format limits) instead of a missing deployment, 404 covers deployment/endpoint,
|
|
50
|
+
401/403 give authentication guidance, 429 mentions quota tiers, and detail is truncated to ~500 characters (#18).
|
|
51
|
+
- GIF and WebP source images are now rejected before upload with `invalid_input` (MAI accepts only
|
|
52
|
+
JPEG or PNG); README and catalog known issues list the supported formats.
|
|
53
|
+
- Security: remote source-image downloads no longer follow redirects (`allow_redirects=False`; 3xx and redirect history are rejected), closing an SSRF bypass (#19).
|
|
54
|
+
- Provider robustness: model discovery no longer raises when the resource `/models` endpoint is
|
|
55
|
+
unavailable, falling back to the documented MAI catalog.
|
|
56
|
+
- Provider robustness: remote image downloads enforce a byte cap to bound memory use.
|
|
57
|
+
- Runtime dependency is bounded: `requests>=2.28,<3` (catalog rule 10 asks for a floor and an upper bound).
|
|
58
|
+
- Manifest metadata completed; `pyproject.toml` and `plugin.yaml` declare the same version, enforced
|
|
59
|
+
by a test that parses both.
|
|
60
|
+
- README updated for catalog submission (installation, configuration, and discovery instructions).
|
|
61
|
+
|
|
62
|
+
## [0.1.2]
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
|
|
66
|
+
- README installation, configuration, Web UI, CLI, troubleshooting, and update instructions
|
|
67
|
+
refreshed for the current Hermes plugin discovery flow.
|
|
68
|
+
|
|
69
|
+
[Unreleased]: https://github.com/joescars/hermes-mai-image/compare/v0.2.0...HEAD
|
|
70
|
+
[0.2.0]: https://github.com/joescars/hermes-mai-image/releases/tag/v0.2.0
|
|
71
|
+
[0.1.2]: https://github.com/joescars/hermes-mai-image/releases/tag/v0.1.2
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 joescars
|
|
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.
|