`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.
What
screenshotnow answers with an explicit text result instead of an image when the active model cannot consume images.Why
ScreenshotInputSchemagives 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 asQwen3-235B-A22B-Instruct-2507orqwen3.7-max— pi reports no error and the turn is spent for nothing:content: [{ type: "image", ... }]withisError: false,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 declaredinputlist, the same fieldpi --list-modelsrenders as theimagescolumn. It returnstrue,false, orundefinedwhen the capability is unknown.screenshotchecks the active model through the tool'sExtensionContextbefore 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 atsnapshot/runas the alternatives, without launching the browser.screenshot: capture the rendered page as an image (needs a vision-capable model).Verification
vitest runinpackages/integrations/pi: 8 tests pass, includingmodelAcceptsImagescases and an extension test that calls the registered tool'sexecutewith a text-only model and asserts the refusal.tsc --noEmit,oxlint, andoxfmt --checkare clean for the touched files.opencode-go/qwen3.7-max): the tool returns the refusal text, the model reports it and stops, no browser is launched;I ran the TypeScript checks scoped to
packages/integrations/pirather thanjust check/just test, becausejust, Go 1.26, anduvare not installed in my environment. No changeset:packages/integrations/piis private and this is not a public SDK, extension, or protocol change.Note on ordering
This is independent of the
snapshotcompaction 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
screenshottool 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 atsnapshot/runas alternatives, without launching the browser.inputlist; unknown capability shapes fail open and never block a model.Written for commit d894d3e. Summary will update on new commits.