Skip to content

feat(image_gen): port FAL backend to plugins/image_gen/fal (salvage #27966)#30380

Merged
teknium1 merged 1 commit into
mainfrom
try-27966
May 22, 2026
Merged

feat(image_gen): port FAL backend to plugins/image_gen/fal (salvage #27966)#30380
teknium1 merged 1 commit into
mainfrom
try-27966

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Salvages PR #27966 onto current main. Ports the FAL.ai image-gen backend out of tools/image_generation_tool.py into plugins/image_gen/fal/, completing the four-way architectural parity established by web (#25182), browser (#25214), and video_gen (#25126). TOOL_CATEGORIES["image_gen"]["providers"] now carries only the "Nous Subscription" UX row — same shape browser/video_gen ship today.

Original PR was 475 commits behind main; cherry-pick was clean with one trivial auto-merge in hermes_cli/tools_config.py. Author @0xDevNinja preserved via rebase-merge.

Closes #26241.

What changed

New

  • tools/fal_common.py — stateless atoms shared by both FAL-backed plugins (lazy fal_client import, _ManagedFalSyncClient, _normalize_fal_queue_url_format, _extract_http_status).
  • plugins/image_gen/fal/{__init__.py,plugin.yaml}FalImageGenProvider thin registration adapter. Resolves tools.image_generation_tool at call time via _it = import tools.image_generation_tool so existing monkeypatch.setattr(image_tool, ...) patch sites in tests/tools/test_image_generation*.py and tests/tools/test_managed_media_gateways.py keep working unchanged.
  • tests/plugins/image_gen/test_fal_provider.py — 14 cases covering ABC surface, call-time indirection, response-shape stamping, exception handling, registry wiring.
  • tests/plugins/image_gen/check_parity_vs_main.py — subprocess parity harness mirroring tests/plugins/browser/check_parity_vs_main.py. Six scenarios; only "legacy_fal → plugin (fal)" deltas are acceptable.

Changed

  • tools/image_generation_tool.py — drops the configured == "fal" skip in _dispatch_to_plugin_provider. Setting image_gen.provider: fal now routes through the registry like any other provider; the plugin re-enters this module's pipeline so behaviour is identical. Unset image_gen.provider still falls through (preserves no-config-with-FAL_KEY UX from refactor(memory): remove flush_memories entirely #15696). Stateful pieces (fal_client module global, _managed_fal_client* cache, _submit_fal_request, _resolve_managed_fal_gateway, _get_managed_fal_client) intentionally stay here so the existing monkeypatch.setattr(image_tool, ...) test surface is preserved.
  • hermes_cli/tools_config.py — drops the hardcoded "FAL.ai" row and the provider.name == "fal" skip in _plugin_image_gen_providers. The "Nous Subscription" setup-flow row stays.
  • plugins/video_gen/fal/__init__.py — drops its inline _load_fal_client duplicate; consumes tools.fal_common.import_fal_client.

Validation

Before After
TOOL_CATEGORIES["image_gen"]["providers"] hardcoded FAL row + Nous Subscription row Nous Subscription UX row only
_dispatch_to_plugin_provider for provider: fal skip → fall through to in-tree pipeline route through plugin registry
_plugin_image_gen_providers dedup-skip FAL surface every plugin
FAL test patch surface patches image_tool.<name> unchanged — call-time indirection preserves it

195/195 tests pass on the salvaged commit:

tests/tools/test_image_generation.py                     55
tests/tools/test_managed_media_gateways.py                5
tests/tools/test_image_generation_plugin_dispatch.py      3
tests/tools/test_image_generation_env.py                  6
tests/plugins/image_gen/                                 76
tests/plugins/video_gen/                                 36
tests/hermes_cli/test_image_gen_picker.py                14

E2E sanity (smoke import + plugin ABC surface + registry wiring + dispatcher patch verification) confirms the configured == "fal" skip is gone, the fal_common imports are wired, and register() correctly registers FalImageGenProvider through the plugin context.

Credit

@0xDevNinja did all the substantive work — design, implementation, tests, and the parity harness. This salvage just rebases their commit onto current main (475 commits ahead of their branch base).

Infographic

pr-27966-fal-plugin

Mirrors the architecture established by the web (#25182), browser
(#25214), and video_gen (#25126) plugin migrations:

* `tools/fal_common.py` — stateless atoms shared by both FAL-backed
  plugins (image_gen + video_gen). Holds the lazy `fal_client` import
  helper, `_ManagedFalSyncClient`, `_normalize_fal_queue_url_format`,
  `_extract_http_status`. Stateful pieces (`fal_client` module global,
  `_managed_fal_client*` cache, `_submit_fal_request`,
  `_resolve_managed_fal_gateway`, `_get_managed_fal_client`)
  intentionally stay on `tools.image_generation_tool` so the existing
  `monkeypatch.setattr(image_tool, ...)` patch sites keep working
  unchanged.

* `plugins/video_gen/fal/__init__.py` — drops its inline
  `_load_fal_client` duplicate; consumes `tools.fal_common.import_fal_client`.

* `plugins/image_gen/fal/{plugin.yaml,__init__.py}` — new plugin.
  `FalImageGenProvider` is a thin registration adapter that resolves
  the legacy module via `import tools.image_generation_tool as _it`
  and calls `_it.image_generate_tool` + `_it._resolve_fal_model` at
  call time. The 18-model catalog, `_build_fal_payload`, managed-
  gateway selection, and Clarity Upscaler chaining all remain in
  `tools.image_generation_tool` as the single source of truth —
  the plugin is a registration adapter, not a parallel implementation.

* `tools/image_generation_tool.py::_dispatch_to_plugin_provider` —
  drops the `configured == "fal"` skip. Setting `image_gen.provider:
  fal` now routes through the registry like any other provider; the
  plugin re-enters this module's pipeline so behavior is identical.
  Unset `image_gen.provider` still falls through to the in-tree
  pipeline (preserves no-config-with-FAL_KEY UX from #15696).

* `hermes_cli/tools_config.py` — drops the hardcoded "FAL.ai" row from
  `TOOL_CATEGORIES["image_gen"]["providers"]` (now injected by
  `_plugin_image_gen_providers` like every other backend) and the
  `getattr(provider, "name") == "fal"` skip that protected against
  duplication with the hardcoded row. The "Nous Subscription" row
  stays as a setup-flow entry — same shape browser kept "Nous
  Subscription (Browser Use cloud)" after #25214.

* `tests/plugins/image_gen/test_fal_provider.py` — 14 cases covering
  the ABC surface, call-time indirection (verifying
  `monkeypatch.setattr(image_tool, "image_generate_tool", ...)` takes
  effect through the plugin), response-shape stamping, exception
  handling, and registry wiring.

* `tests/plugins/image_gen/check_parity_vs_main.py` — subprocess
  harness mirroring `tests/plugins/browser/check_parity_vs_main.py`.
  Pins one path to origin/main, one to the worktree; runs six
  scenarios (unset, explicit-fal-no-creds, explicit-fal-with-creds,
  explicit-fal-with-model, typo provider, managed-gateway-only) and
  diffs the reduced shape `{dispatch_kind, provider_name, model}`
  per scenario. The only acceptable diff is "legacy_fal → plugin
  (fal)" for explicit-FAL paths — every other delta is flagged as
  a regression.

* `tests/hermes_cli/test_image_gen_picker.py::test_fal_surfaced_alongside_other_plugins`
  — flips the previous `test_fal_skipped_to_avoid_duplicate` to
  match the new shape (FAL is a plugin now, no dedup needed).

Verified: 195/195 tests across
`tests/{tools/test_image_generation*,tools/test_managed_media_gateways,plugins/image_gen,plugins/video_gen,hermes_cli/test_image_gen_picker}.py`
pass on this branch with no test patches modified outside the picker
test that asserted the old skip behaviour.

Fixes #26241
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: try-27966 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9012 on HEAD, 9012 on base (➖ 0)

🆕 New issues (3):

Rule Count
unsupported-operator 1
unresolved-import 1
call-non-callable 1
First entries
hermes_cli/tools_config.py:2654: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `dict[Unknown, Unknown]` and `str | list[dict[str, str | list[Unknown] | bool | list[str]] | dict[str, str | list[Unknown]] | dict[str, str | list[dict[str, str]]]] | list[dict[str, str | list[Unknown] | bool | list[str]] | dict[str, str | list[dict[str, str]]]] | ... omitted 5 union elements`
tests/plugins/image_gen/test_fal_provider.py:19: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tools/fal_common.py:157: [call-non-callable] call-non-callable: Object of type `None` is not callable

✅ Fixed issues (2):

Rule Count
unsupported-operator 1
call-non-callable 1
First entries
hermes_cli/tools_config.py:2659: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `dict[Unknown, Unknown]` and `str | list[dict[str, str | list[Unknown] | bool | list[str]] | dict[str, str | list[Unknown]] | dict[str, str | list[dict[str, str]]]] | list[dict[str, str | list[Unknown] | bool | list[str]] | dict[str, str | list[dict[str, str]]]] | ... omitted 4 union elements`
tools/image_generation_tool.py:443: [call-non-callable] call-non-callable: Object of type `None` is not callable

Unchanged: 4761 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@teknium1 teknium1 merged commit 3ac2125 into main May 22, 2026
18 checks passed
@teknium1 teknium1 deleted the try-27966 branch May 22, 2026 11:10
@alt-glitch alt-glitch added type/refactor Code restructuring, no behavior change P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins tool/vision Vision analysis and image generation labels May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have tool/vision Vision analysis and image generation type/refactor Code restructuring, no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mirror web/browser plugin migration for FAL image generation backend

3 participants