文件最后提交记录最后更新时间
refactor(image_gen): port FAL backend to plugins/image_gen/fal 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 14 天前
feat(xai-oauth): add xAI Grok OAuth (SuperGrok Subscription) provider Adds a new authentication provider that lets SuperGrok subscribers sign in to Hermes with their xAI account via the standard OAuth 2.0 PKCE loopback flow, instead of pasting a raw API key from console.x.ai. Highlights ---------- * OAuth 2.0 PKCE loopback login against accounts.x.ai with discovery, state/nonce, and a strict CORS-origin allowlist on the callback. * Authorize URL carries plan=generic (required for non-allowlisted loopback clients) and referrer=hermes-agent for best-effort attribution in xAI's OAuth server logs. * Token storage in auth.json with file-locked atomic writes; JWT exp-based expiry detection with skew; refresh-token rotation synced both ways between the singleton store and the credential pool so multi-process / multi-profile setups don't tear each other's refresh tokens. * Reactive 401 retry: on a 401 from the xAI Responses API, the agent refreshes the token, swaps it back into self.api_key, and retries the call once. Guarded against silent account swaps when the active key was sourced from a different (manual) pool entry. * Auxiliary tasks (curator, vision, embeddings, etc.) route through a dedicated xAI Responses-mode auxiliary client instead of falling back to OpenRouter billing. * Direct HTTP tools (tools/xai_http.py, transcription, TTS, image-gen plugin) resolve credentials through a unified runtime → singleton → env-var fallback chain so xai-oauth users get them for free. * hermes auth add xai-oauth and hermes auth remove xai-oauth N are wired through the standard auth-commands surface; remove cleans up the singleton loopback_pkce entry so it doesn't silently reinstate. * hermes model provider picker shows "xAI Grok OAuth (SuperGrok Subscription)" and the model-flow falls back to pool credentials when the singleton is missing. Hardening --------- * Discovery and refresh responses validate the returned token_endpoint host against the same *.x.ai allowlist as the authorization endpoint, blocking MITM persistence of a hostile endpoint. * Discovery / refresh / token-exchange response.json() calls are wrapped to raise typed AuthError on malformed bodies (captive portals, proxy error pages) instead of leaking JSONDecodeError tracebacks. * prompt_cache_key is routed through extra_body on the codex transport (sending it as a top-level kwarg trips xAI's SDK with a TypeError). * Credential-pool sync-back preserves active_provider so refreshing an OAuth entry doesn't silently flip the active provider out from under the running agent. Testing ------- * New tests/hermes_cli/test_auth_xai_oauth_provider.py (~63 tests) covers JWT expiry, OAuth URL params (plan + referrer), CORS origins, redirect URI validation, singleton↔pool sync, concurrency races, refresh error paths, runtime resolution, and malformed-JSON guards. * Extended test_credential_pool.py, test_codex_transport.py, and test_run_agent_codex_responses.py cover the pool sync-back, extra_body routing, and 401 reactive refresh paths. * 165 tests passing on this branch via scripts/run_tests.sh. 20 天前