Skip to content

feat(review): route visual-vision to a local self-host provider (#4335) - #4353

Merged
JSONbored merged 2 commits into
mainfrom
claude/selfhost-visual-vision-local-provider
Jul 9, 2026
Merged

feat(review): route visual-vision to a local self-host provider (#4335)#4353
JSONbored merged 2 commits into
mainfrom
claude/selfhost-visual-vision-local-provider

Conversation

@JSONbored

@JSONbored JSONbored commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • The visual-vision advisory (feat(review): advisory-only AI-vision analysis of before/after visual captures #4111) required a maintainer BYOK (anthropic/openai) key to run at all — Workers AI is retired and the subscription CLIs (claude-code/codex) can't consume inline image bytes, so BYOK was the only option, even for self-host operators with no interest in paying for a cloud vision API.
  • Adds a dedicated AI_VISION binding (mirrors AI_EMBED's existing separation from the review chain — same pattern, same reasoning) so self-host operators can point visual-vision at a local Ollama vision-language model instead. BYOK is still preferred when both are configured (bills the maintainer's own account, same convention as every other dual-path AI call site in this codebase).
  • evaluateVisualVisionGate's provider check is broadened additivelyproviderKey OR selfHostVisionAvailable — so behavior is byte-identical for any deployment that doesn't set AI_VISION (existing BYOK-only deployments are unaffected).
  • Investigated as part of Stretch: evaluate spare GPU headroom for a local vision-language model in visual-review #4335 (stretch/evaluation issue) — found the existing "only BYOK can see screenshots" comment was stale: the self-host ollama/openai-compatible HTTP provider path already forwards images correctly (toOpenAiMessageContent in src/selfhost/ai.ts), it just wasn't wired as an option for this specific call site.

Closes #4335. Note on methodology: #4335 asked for a side-by-side quality comparison against the current cloud-based analysis on real past PR screenshots before adopting. That comparison turned out to be blocked — no BYOK vision key was configured for any repo in this deployment, so there was no real cloud-path baseline to compare against (the feature had never actually fired in production). Rather than leave this stalled, the maintainer made a direct decision to adopt local Ollama vision given the hardware is provisioned for it; this PR is that decision implemented, not the outcome of the originally-planned comparison. Closing #4335 on that basis rather than leaving an evaluation issue open for a question that's already been answered.

Scope

Validation

  • git diff --check
  • npm run actionlint (via npm run test:ci)
  • npm run typecheck (via npm run test:ci)
  • npm run test:coverage locally — visual-findings.ts gate change and the new runSelfHostVisualVision helper/call-site branch in processors.ts both fully covered (both BYOK-preferred and self-host-only paths, plus fail-safe: no binding, no .run, thrown error, empty response)
  • npm run test:workers (via npm run test:ci)
  • npm run build:mcp / npm run test:mcp-pack (via npm run test:ci)
  • npm run ui:openapi:check / npm run ui:lint / npm run ui:typecheck / npm run ui:build (via npm run test:ci) — no UI/API surface touched
  • npm audit --audit-level=moderate — 0 vulnerabilities
  • New behavior has unit tests for every new branch and fallback path (see test/unit/visual-findings.test.ts and test/unit/visual-vision-wiring.test.ts)

src/server.ts is excluded from Codecov (codecov.yml) — validated by the self-host integration workflow, not unit-coverable without booting a real server; the new AI_VISION wiring there mirrors the existing (also-excluded) AI_EMBED block exactly.

Ran npm run test:ci (full unsharded suite) end-to-end — green.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth/cookie/CORS/GitHub App/Cloudflare/session changes include negative-path tests. — N/A, no such surface touched.
  • API/OpenAPI/MCP behavior is updated and tested where needed. — N/A, no API/OpenAPI/MCP surface touched.
  • UI changes use live API data or real empty/error/loading states. — N/A, backend-only change.
  • Visible UI changes include a UI Evidence section. — N/A, no UI change.
  • Public docs updated where needed (.env.example documents every new var; selfhost-env-reference.ts regenerated via npm run selfhost:env-reference).

Notes

  • No migration or cf-typegen regeneration needed. npm run selfhost:env-reference was run and its output committed (server.ts is a scanned source root for that generator).

The visual-vision advisory (#4111) required a maintainer BYOK
(anthropic/openai) key -- Workers AI is retired and the subscription
CLIs (claude-code/codex) can't consume inline image bytes, so BYOK
was the only option. Adds a dedicated AI_VISION binding (mirrors
AI_EMBED's separation from the review chain) so self-host operators
can point visual-vision at a local Ollama vision-language model
instead, with BYOK still preferred when both are configured.

evaluateVisualVisionGate's provider check is broadened additively
(providerKey OR selfHostVisionAvailable) -- byte-identical behavior
for any deployment that doesn't set AI_VISION.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
gittensory-ui 6685eea Commit Preview URL

Branch Preview URL
Jul 09 2026, 03:13 AM

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.95%. Comparing base (7ec1d53) to head (8f064b6).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4353      +/-   ##
==========================================
- Coverage   93.95%   93.95%   -0.01%     
==========================================
  Files         399      399              
  Lines       36786    36796      +10     
  Branches    13440    13443       +3     
==========================================
+ Hits        34561    34570       +9     
  Misses       1569     1569              
- Partials      656      657       +1     
Files with missing lines Coverage Δ
src/queue/processors.ts 95.39% <100.00%> (+0.01%) ⬆️
src/review/visual/visual-findings.ts 100.00% <100.00%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The BYOK-vs-self-host dispatch was a multi-line ternary assigning
into visionText, with a separate `if (!visionText) return` below it.
Both paths were exercised by tests (confirmed locally), but CI's
coverage merge reported one branch of the `if` as never taken --
restructured into a plain if/else with the SAME behavior, which
resolves the ambiguity cleanly (verified: both branches now hit in a
full local coverage run).
@JSONbored JSONbored self-assigned this Jul 9, 2026
@JSONbored
JSONbored merged commit 83b94d1 into main Jul 9, 2026
9 checks passed
@JSONbored
JSONbored deleted the claude/selfhost-visual-vision-local-provider branch July 9, 2026 03:42
JSONbored added a commit that referenced this pull request Jul 10, 2026
…path rewrite

#4335/#4353 rewrote runVisualVisionForAdvisory into a BYOK-vs-self-host dual
path 42 minutes before this PR opened, so GitHub couldn't auto-merge the
original diff. Reapplies the same BYOK daily-cap check + recordVisualVisionUsage
accounting, scoped to only the BYOK branch (self-host consumes the operator's
own resources and was never part of this spend surface). Moves the cap check
back before the shot-fetching loop, matching this PR's own test name/intent
("...before fetching screenshots") -- my first pass had it after the loop.

Also updates two tests for unrelated main drift since this PR opened: the
"declines when no route crossed..." test now needs stubMinerCheckOnly() for
#4513's install-wide reputation check, and both new tests need `mode: "live"`
for the #token-bleed-spend-gate paused-mode field.
JSONbored added a commit that referenced this pull request Jul 10, 2026
* fix(review): meter visual vision BYOK calls

* fix(review): reapply BYOK vision metering against the self-host dual-path rewrite

#4335/#4353 rewrote runVisualVisionForAdvisory into a BYOK-vs-self-host dual
path 42 minutes before this PR opened, so GitHub couldn't auto-merge the
original diff. Reapplies the same BYOK daily-cap check + recordVisualVisionUsage
accounting, scoped to only the BYOK branch (self-host consumes the operator's
own resources and was never part of this spend surface). Moves the cap check
back before the shot-fetching loop, matching this PR's own test name/intent
("...before fetching screenshots") -- my first pass had it after the loop.

Also updates two tests for unrelated main drift since this PR opened: the
"declines when no route crossed..." test now needs stubMinerCheckOnly() for
#4513's install-wide reputation check, and both new tests need `mode: "live"`
for the #token-bleed-spend-gate paused-mode field.

* fix(review): distinguish a real provider failure from success in visual-vision's usage status

recordVisualVisionUsage hardcoded status: "ok" even when visionResponse.failure
was set (a genuine timeout/http_error/exception) -- the detail string already
distinguished this from a completed-but-empty response, but that distinction
was discarded at the status level. Now uses "error" for a genuine failure,
matching runAgentSummary's existing convention (services/ai-summaries.ts) for
the same shape of problem.

Since countByokAiEventsForRepoSince/sumByokAiUsageForRepoSince gate on
status = "ok", introducing "error" required deciding whether it should still
count toward the daily BYOK cap. It must: excluding failed attempts would
turn a flaky or misconfigured provider into a way to bypass the cap entirely
via forced failures, defeating the whole point of #4363's own fix. Switched
both functions from an exact "ok" match to an explicit
BYOK_SPEND_ATTEMPT_STATUSES allowlist (["ok", "error"]).

Caught during review: an exclusion-based filter (!= "quota_exceeded") is NOT
safe here, because ai_usage_events is also reused for BYOK key-lifecycle
audit rows (recordAiKeyChange's "set"/"replace"/"delete") whose model is also
byok:<provider>-prefixed -- an exclusion would have silently started counting
those as spend. Caught by a new regression test before it shipped.
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.

Stretch: evaluate spare GPU headroom for a local vision-language model in visual-review

1 participant