Skip to content

feat: improve AI image failures and refresh progress#607

Merged
jtn0123 merged 3 commits into
mainfrom
codex/ai-image-progress-status
Apr 29, 2026
Merged

feat: improve AI image failures and refresh progress#607
jtn0123 merged 3 commits into
mainfrom
codex/ai-image-progress-status

Conversation

@jtn0123
Copy link
Copy Markdown
Owner

@jtn0123 jtn0123 commented Apr 28, 2026

Summary

  • Surface OpenAI image-generation provider rejections with response-safe details like moderation_blocked and request ids.
  • Add an opt-in AI Image safe-rewrite retry for blocked prompts, while leaving default behavior as explicit failure.
  • Publish richer refresh progress stages for all plugins, with provider-credential checks for API-key plugins and live SSE updates in the plugin form.

Base Branch Confirmation

  • This PR is based on origin/main (not a stale long-lived branch)
  • I rebased/merged latest origin/main before opening

Parent-Fork Sync Checklist

  • If this PR syncs from fatihak/InkyPi, changes were cherry-picked by feature
  • Relevant upstream behavior differences were documented in PR description
  • Plugin/add-to-playlist/update flows were smoke-tested after sync

N/A: this is not an upstream sync PR.

Compatibility/Release Checklist

  • pytest relevant suites pass locally
  • No breaking API route/path changes
  • Error responses follow JSON contract (success:false,error,code,details,request_id)
  • Docs updated for new flags/endpoints/UI
  • Frontend changes (src/static/**, src/templates/**): ran browser tests (SKIP_BROWSER=0 .venv/bin/python -m pytest tests/) and all passed

Frontend note: full browser suite was not run; node --check src/static/scripts/plugin_form.js and route/progress integration tests passed. The new AI Image setting is documented inline in the settings schema hint.

Testing

  • python3 -m pytest tests/plugins/test_ai_image.py tests/unit/test_refresh_task_display_pipeline.py tests/unit/test_refresh_task_executor.py -q
  • python3 -m pytest tests/integration/test_plugin_routes.py tests/unit/test_progress_events.py tests/integration/test_observability_api.py -q
  • python3 -m black --check src/plugins/ai_image/ai_image.py src/utils/plugin_errors.py src/refresh_task/worker.py src/blueprints/plugin.py src/refresh_task/task.py src/refresh_task/executor.py src/refresh_task/display_pipeline.py tests/plugins/test_ai_image.py tests/unit/test_refresh_task_display_pipeline.py tests/unit/test_refresh_task_executor.py
  • python3 -m py_compile src/plugins/ai_image/ai_image.py src/utils/plugin_errors.py src/refresh_task/worker.py src/blueprints/plugin.py src/refresh_task/task.py src/refresh_task/executor.py src/refresh_task/display_pipeline.py
  • node --check src/static/scripts/plugin_form.js
  • git diff --check

Summary by CodeRabbit

Release Notes

  • New Features

    • Live progress streaming displays detailed step-by-step updates during image generation
    • Automatic retry mechanism for blocked image generation with prompt rewriting
    • Granular progress events throughout the render pipeline lifecycle
  • Bug Fixes

    • Improved error handling and user-facing messages for provider rejections
    • Better exception type preservation in asynchronous plugin execution
  • Tests

    • Added coverage for moderation block handling and retry behavior
    • Enhanced progress event tracking validation across refresh pipeline

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

Warning

Rate limit exceeded

@jtn0123 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 49 minutes and 20 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d251387a-9b59-4eb0-ab5a-2a02862b521f

📥 Commits

Reviewing files that changed from the base of the PR and between 7fbe64a and 09afc40.

📒 Files selected for processing (6)
  • src/blueprints/plugin.py
  • src/plugins/ai_image/ai_image.py
  • src/refresh_task/display_pipeline.py
  • src/refresh_task/task.py
  • src/utils/plugin_errors.py
  • tests/plugins/test_ai_image.py
📝 Walkthrough

Walkthrough

This pull request introduces provider error handling, a safe rewrite mechanism for moderation-blocked image generation, and comprehensive progress instrumentation across the refresh pipeline. Changes include a new ProviderReportedPluginError exception type, OpenAI moderation block recovery via prompt rewriting, granular recorder step events throughout task execution, and live server-sent progress streaming to the frontend.

Changes

Cohort / File(s) Summary
Error Handling Infrastructure
src/utils/plugin_errors.py, src/blueprints/plugin.py, src/refresh_task/worker.py
Introduced ProviderReportedPluginError as a permanent plugin error subclass for provider-originated rejections. Updated blueprint handler to catch this error and return HTTP 400 with provider message and code="provider_rejected". Enhanced worker exception reconstruction to map and preserve this error type across subprocess boundaries.
AI Image Generation Resilience
src/plugins/ai_image/ai_image.py
Added opt-in safe rewrite flow for moderation-blocked requests: extracts safe error fields and retries by generating a rewritten prompt via GPT-3.5-nano chat completion, then reattempts image generation. Falls back to ProviderReportedPluginError with sanitized message if retry fails. Updated prompt instructions for both OpenAI and Google to avoid named artists, franchises, and use "visual genre" wording.
Refresh Pipeline Instrumentation
src/refresh_task/task.py, src/refresh_task/executor.py, src/refresh_task/display_pipeline.py
Added granular recorder step events at multiple checkpoints: render attempt starts, plugin preparation, credential/settings checks, image generation, image validation, and display lifecycle (cache skip, image save, display complete). Replaced static manual callback with instance method for recorder publishing. Enhanced pipeline to track display success state.
Frontend Progress Streaming
src/static/scripts/plugin_form.js
Implemented live server-sent event (SSE) streaming from /api/progress/stream endpoint: parses events filtered by plugin_id, converts state/error info into step updates via progress.setStep(), and gates incremental "Rendering" updates when live events are recent. Ensures stream cleanup on completion or error.
Test Coverage
tests/plugins/test_ai_image.py, tests/unit/test_refresh_task_*.py
Added tests validating moderation rejection without rewrite, safe rewrite retry flow with dual API calls, and schema exposure of rewrite setting. Updated executor and display pipeline unit tests to capture and assert recorder step sequences across success, retry, skip, and failure scenarios.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server
    participant OpenAI
    
    Client->>Server: POST /api/plugin/{plugin_id}/image (with moderation-prone prompt)
    Server->>OpenAI: POST /v1/images/generations
    OpenAI-->>Server: 400 error with moderation_blocked code
    
    alt safeRewriteBlockedPrompt enabled
        Server->>OpenAI: POST /v1/chat/completions (rewrite prompt via gpt-3.5-nano)
        OpenAI-->>Server: Rewritten, sanitized prompt
        Server->>OpenAI: POST /v1/images/generations (retry with rewritten prompt)
        OpenAI-->>Server: 200 OK with image data
        Server-->>Client: Image successfully generated
    else rewrite disabled or fails
        Server-->>Client: 400 ProviderReportedPluginError with moderation message
    end
Loading
sequenceDiagram
    participant Client
    participant Server
    participant EventStream
    participant Plugin/Display
    
    Client->>Server: POST /api/plugin/{plugin_id}/image
    Server-->>Client: 202 Accepted (background job)
    Client->>EventStream: GET /api/progress/stream?plugin_id=X
    EventStream-->>Client: SSE connection established
    
    par Server Processing
        Server->>Plugin/Display: Execute refresh task
        Server->>EventStream: publish_step("Starting render attempt 1/1")
        EventStream-->>Client: step event received
        Server->>Plugin/Display: Render image
        Server->>EventStream: publish_step("Image generated")
        EventStream-->>Client: step event received
        Server->>Plugin/Display: Save to display
        Server->>EventStream: publish_step("Display complete")
        EventStream-->>Client: step event received
    and Client Polling
        Client->>Server: Poll /api/plugin/{plugin_id}/image (job status)
        Note over Client: Live events suppress incremental updates
        Server-->>Client: Status update
    end
    
    Server->>EventStream: publish_step("done")
    EventStream-->>Client: done event
    Client->>EventStream: Close connection
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through error flows,
With safer prompts where rewrite goes,
Step-by-step the progress shows,
Live streams dance as the image grows,
Provider whispers meet their woes! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: improving AI image error handling and publishing richer refresh progress updates.
Description check ✅ Passed The PR description covers the main objectives, includes completed base branch checks, documents test execution, and notes frontend limitations transparently.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/ai-image-progress-status

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 28, 2026

Memory diff vs base

Metric Base PR Delta
Peak RSS 61.51 MB 61.15 MB -372.0 KB
sys.modules count 592 592 0

Largest grouped allocator deltas

Group Base PR Delta
werkzeug 230.6 KB 2.23 MB +2.00 MB
lark 12.77 MB 11.76 MB -1.02 MB
attr 229.8 KB 1.23 MB +1.00 MB
flask 59.2 KB 1.06 MB +1.00 MB
arrow 1.10 MB 124.0 KB -1001.9 KB
python stdlib 1.57 MB 2.52 MB +981.1 KB
python import system 14.32 MB 13.63 MB -707.3 KB
Source-location detail: top 18 deltas (sampled base=500, PR=500)
# Location Base PR Delta
1 <frozen importlib._bootstrap_external>:757 8.56 MB 5.46 MB -3.10 MB
2 parsers/lalr_parser_state.py:105 4.00 MB 1.00 MB -3.00 MB
3 <frozen importlib._bootstrap>:488 5.63 MB 7.93 MB +2.30 MB
4 python3.12/functools.py:58 0 B 2.00 MB +2.00 MB
5 lark/lexer.py:215 2.00 MB 1.00 MB -1.00 MB
6 lark/visitors.py:117 1.00 MB 2.00 MB +1.00 MB
7 lark/lexer.py:389 1.00 MB 0 B -1.00 MB
8 python3.12/ast.py:78 1.00 MB 0 B -1.00 MB
9 parsers/lalr_analysis.py:285 1.00 MB 0 B -1.00 MB
10 arrow/locales.py:3873 1.00 MB 0 B -1.00 MB
11 attr/_make.py:226 34.0 KB 1.03 MB +1.00 MB
12 lark/tree.py:145 0 B 1.00 MB +1.00 MB
13 sansio/response.py:488 0 B 1.00 MB +1.00 MB
14 parsers/lalr_analysis.py:137 0 B 1.00 MB +1.00 MB
15 lark/load_grammar.py:537 0 B 1.00 MB +1.00 MB
16 werkzeug/http.py:1211 0 B 1.00 MB +1.00 MB
17 lark/visitors.py:120 0 B 1.00 MB +1.00 MB
18 flask/sessions.py:337 0 B 1.00 MB +1.00 MB

JTN-610 · backend=base:memray, pr:memray · informational only, does not block merge. Hard RSS budgets are enforced separately by JTN-608. Source-location rows are sampled allocator attribution, not exact module ownership.

Comment thread src/blueprints/plugin.py Fixed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/refresh_task/display_pipeline.py (1)

225-250: 🧹 Nitpick | 🔵 Trivial

Minor type hint inconsistency.

The return type Callable[[Mapping[str, Any]], None] | None includes None but the method always returns on_image_saved. If the | None is for protocol compatibility or future use, consider adding a brief comment; otherwise, the type could be simplified.

♻️ Simplify return type if None is not a valid return
     def _image_saved_callback(
         self,
         *,
         manual_request: ManualUpdateRequest | None,
         plugin_id: str,
         request_id: str | None,
-    ) -> Callable[[Mapping[str, Any]], None] | None:
+    ) -> Callable[[Mapping[str, Any]], None]:
         """Return a callback that reports disk-save progress and releases waiters."""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/refresh_task/display_pipeline.py` around lines 225 - 250, The return type
annotation for _image_saved_callback is overly permissive: change the return
type from Callable[[Mapping[str, Any]], None] | None to Callable[[Mapping[str,
Any]], None] (or if None must remain for compatibility, add a short comment
explaining why) so that the signature matches the implementation that always
returns on_image_saved; update the function signature for _image_saved_callback
and ensure any callers or type stubs expect Callable[[Mapping[str, Any]], None];
reference symbols: _image_saved_callback, on_image_saved, ManualUpdateRequest,
image_saved_metrics, image_saved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/plugins/ai_image/ai_image.py`:
- Around line 395-428: You extract the OpenAI payload once via
_openai_error_payload(exc) but then call _is_openai_moderation_block(exc) which
recomputes it; change the logic to reuse the already-extracted payload by
either: (a) updating _is_openai_moderation_block to accept the payload (e.g.,
_is_openai_moderation_block(payload)) and call it with the payload, or (b) add
an inline check using the payload fields directly before invoking
_safe_rewrite_openai_prompt; apply this where safe_rewrite_blocked_prompt is
checked and keep all other flows (logging via _openai_user_error_message, retry
with _safe_rewrite_openai_prompt, and raising ProviderReportedPluginError)
unchanged.

In `@src/refresh_task/task.py`:
- Around line 64-74: Replace the hardcoded _API_KEY_PLUGIN_IDS frozenset with a
dynamic computation that inspects plugin metadata at runtime: iterate the plugin
registry/collection used by the task (e.g., the same source that loads plugins)
and for each plugin check a standardized signal such as a class attribute (e.g.,
requires_api_key) or the output of generate_settings_template() for an 'api_key'
entry, collecting those plugin ids into a frozenset; update any references to
_API_KEY_PLUGIN_IDS to use this computed set so new plugins that declare the
metadata are included automatically.

In `@tests/plugins/test_ai_image.py`:
- Around line 334-378: Add a new test alongside
test_ai_image_openai_safe_rewrite_retries_moderation_block that simulates the
rewritten prompt also being rejected and asserts the code raises
ProviderReportedPluginError with the retry error details: create an AIImage
instance, patch OpenAI to return a failing images.generate twice (first
original, then rewritten) by setting mock_client.images.generate.side_effect =
[_FakeOpenAIImageError(), _FakeOpenAIImageError()] (or another simulated
provider error), stub the chat completion to return the rewrite, call
AIImage.generate_image with safeRewriteBlockedPrompt set, and assert that
ProviderReportedPluginError is raised and contains the second error's message;
reference the test function name
test_ai_image_openai_safe_rewrite_retries_moderation_block, the
AIImage.generate_image method, and ProviderReportedPluginError to locate where
to add this new test.

---

Outside diff comments:
In `@src/refresh_task/display_pipeline.py`:
- Around line 225-250: The return type annotation for _image_saved_callback is
overly permissive: change the return type from Callable[[Mapping[str, Any]],
None] | None to Callable[[Mapping[str, Any]], None] (or if None must remain for
compatibility, add a short comment explaining why) so that the signature matches
the implementation that always returns on_image_saved; update the function
signature for _image_saved_callback and ensure any callers or type stubs expect
Callable[[Mapping[str, Any]], None]; reference symbols: _image_saved_callback,
on_image_saved, ManualUpdateRequest, image_saved_metrics, image_saved.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2b08eee1-cfba-40ac-961e-b9b9a634a0f0

📥 Commits

Reviewing files that changed from the base of the PR and between a2033a9 and 7fbe64a.

📒 Files selected for processing (11)
  • src/blueprints/plugin.py
  • src/plugins/ai_image/ai_image.py
  • src/refresh_task/display_pipeline.py
  • src/refresh_task/executor.py
  • src/refresh_task/task.py
  • src/refresh_task/worker.py
  • src/static/scripts/plugin_form.js
  • src/utils/plugin_errors.py
  • tests/plugins/test_ai_image.py
  • tests/unit/test_refresh_task_display_pipeline.py
  • tests/unit/test_refresh_task_executor.py

Comment thread src/plugins/ai_image/ai_image.py Outdated
Comment thread src/refresh_task/task.py Outdated
Comment thread tests/plugins/test_ai_image.py
@sonarqubecloud
Copy link
Copy Markdown

@jtn0123 jtn0123 merged commit 3ca7668 into main Apr 29, 2026
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants