Skip to content

fix(pi): skip screenshot when the active model has no vision - #2927

Open
QuentinDanblon wants to merge 1 commit into
browserbase:mainfrom
QuentinDanblon:pi-screenshot-no-vision
Open

QuentinDanblon wants to merge 1 commit into
browserbase:mainfrom
QuentinDanblon:pi-screenshot-no-vision

Conversation

@QuentinDanblon

@QuentinDanblon QuentinDanblon commented Sep 12, 2026

Copy link
Copy Markdown

What

screenshot now answers with an explicit text result instead of an image when the active model cannot consume images.

Why

ScreenshotInputSchema gives the model a tool that returns real image content, but nothing checks whether the active model accepts images. When it does not — a text-only model such as Qwen3-235B-A22B-Instruct-2507 or qwen3.7-max — pi reports no error and the turn is spent for nothing:

  • the tool returns content: [{ type: "image", ... }] with isError: false,
  • the model answers as if it had inspected the page, so the caller cannot tell the capture was discarded.

That is worse than a refusal, because the agent loop continues on a false premise.

Change

  • extensions/model-capabilities.ts (new): modelAcceptsImages(model) reads the model's declared input list, the same field pi --list-models renders as the images column. It returns true, false, or undefined when the capability is unknown.
  • screenshot checks the active model through the tool's ExtensionContext before capturing. On a text-only model it returns a text result naming the model — screenshot is unavailable: opencode-go/qwen3.7-max does not accept image input... — and points at snapshot/run as the alternatives, without launching the browser.
  • The unknown case fails open: a model whose capabilities cannot be read is never blocked.
  • The tool snippet now advertises the requirement: screenshot: capture the rendered page as an image (needs a vision-capable model).

Verification

  • vitest run in packages/integrations/pi: 8 tests pass, including modelAcceptsImages cases and an extension test that calls the registered tool's execute with a text-only model and asserts the refusal.
  • tsc --noEmit, oxlint, and oxfmt --check are clean for the touched files.
  • Manual, end to end:
    • text-only model (opencode-go/qwen3.7-max): the tool returns the refusal text, the model reports it and stops, no browser is launched;
    • vision model: the image arrives and is described correctly ("A minimal white page with the heading "Example Domain," ...").

I ran the TypeScript checks scoped to packages/integrations/pi rather than just check / just test, because just, Go 1.26, and uv are not installed in my environment. No changeset: packages/integrations/pi is private and this is not a public SDK, extension, or protocol change.

Note on ordering

This is independent of the snapshot compaction change. Both touch the import block of the same file, so whichever lands second needs a trivial rebase there.


Developed and verified with an AI coding agent in a local checkout; the test results above come from that checkout.


Summary by cubic

Makes the screenshot tool refuse to run when the active model can't consume images. Previously it returned a real image even for text-only models, so those models answered as if they'd inspected the page and the capture was silently discarded; now it returns a text result naming the model and pointing at snapshot/run as alternatives, without launching the browser.

  • Reads the model's declared input list; unknown capability shapes fail open and never block a model.
  • The tool's prompt snippet now advertises the vision requirement.

Written for commit d894d3e. Summary will update on new commits.

Review in cubic

`screenshot` returns real image content. When the active model does not accept
images, pi reports no error: the capture is dropped or ignored and the model
answers as if the page had been inspected, spending a full turn for nothing.

The tool now reads the active model's declared `input` capabilities and answers
with an explicit text result instead of an image, naming the model and pointing
at `snapshot`/`run` as the alternatives. Unknown capability shapes return
undefined and fail open, so a model we cannot classify is never blocked, and
the tool snippet advertises the vision requirement.

Verified end to end: a text-only model gets the refusal and stops, a vision
model receives the image and describes it.
@changeset-bot

changeset-bot Bot commented Sep 12, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: d894d3e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

This PR is from an external contributor and must be approved by a stagehand team member with write access before CI can run.
Approving the latest commit mirrors it into an internal PR owned by the approver.
If new commits are pushed later, the internal PR stays open but is marked stale until someone approves the latest external commit and refreshes it.

@github-actions github-actions Bot added external-contributor Tracks PRs mirrored from external contributor forks. external-contributor:awaiting-approval Waiting for a stagehand team member to approve the latest external commit. labels Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external-contributor:awaiting-approval Waiting for a stagehand team member to approve the latest external commit. external-contributor Tracks PRs mirrored from external contributor forks.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant