Skip to content

feat(cua-driver): add screenshot_out_file param; CLI no longer dumps base64 by default - #1414

Merged
f-trycua merged 3 commits into
mainfrom
feat/cua-driver-screenshot-out-file
May 1, 2026
Merged

feat(cua-driver): add screenshot_out_file param; CLI no longer dumps base64 by default#1414
f-trycua merged 3 commits into
mainfrom
feat/cua-driver-screenshot-out-file

Conversation

@f-trycua

@f-trycua f-trycua commented May 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds screenshot_out_file optional param to get_window_state: when set, screenshot bytes are written to that path, the MCP image content block is omitted, and screenshot_file_path is returned in structuredContent instead of inline base64.
  • CLI breaking change: get_window_state stdout no longer includes screenshot_png_b64 by default. The mergeImageContentIntoJSON splice has been removed. Use --image-out <path> (existing CLI flag) or the new screenshot_out_file param to get the image on disk.
  • AppStateSnapshot gains a screenshotFilePath: String? field (encoded as screenshot_file_path).
  • Docs: integrations.mdx OpenCode section now warns about the CLI-subprocess base64 saturation problem and includes the modalities config fix for local Ollama vision models. SKILL.md removes the legacy jq .screenshot_png_b64 path and documents screenshot_out_file.

Motivation

Agents calling get_window_state via CLI subprocess (e.g. OpenCode with a local Ollama model) were receiving ~31 KB of raw base64 in stdout on every call, saturating the context window in 1–2 turns. The screenshot_out_file param and the CLI default change together fix this: the model gets the AX tree + a file path, and reads the image from disk when it needs to reason over the screenshot.

Test plan

  • swift build clean ✅
  • cua-driver get_window_state '{"pid":N,"window_id":W}' — stdout has no screenshot_png_b64 field
  • cua-driver get_window_state '{"pid":N,"window_id":W,"screenshot_out_file":"/tmp/shot.jpg"}' — file written, response has screenshot_file_path, no base64 in stdout
  • cua-driver get_window_state '{"pid":N,"window_id":W}' --image-out /tmp/shot.jpg — file written via existing flag, no base64 in stdout
  • MCP path unchanged: screenshot still arrives as native image content block when screenshot_out_file is not set
  • Docs render correctly in fumadocs preview

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Expanded OpenCode integration guide with global and project-level configuration options.
    • Added prominent warning about proper MCP server setup to prevent context overflow.
    • Introduced new Ollama/vision section with model capability configuration requirements.
  • New Features

    • Added file-based screenshot output option as an alternative to inline embedding.

…base64 by default

get_window_state now accepts screenshot_out_file (optional absolute path).
When set, screenshot bytes are written to that file, the MCP image content
block is omitted, and screenshot_file_path is returned in structuredContent
instead — callers get the AX tree without paying the ~31 KB base64-in-context
token cost.

CLI change: removed mergeImageContentIntoJSON — stdout no longer splices
screenshot_png_b64 into the JSON by default. Use --image-out <path> or the
new screenshot_out_file param to get the image on disk. This fixes context
saturation in agents that call get_window_state via CLI subprocess (OpenCode
with local Ollama models being the motivating case).

Docs: integrations.mdx OpenCode section now warns about the CLI fallback
problem and shows the modalities config fix for local vision models. SKILL.md
updated to remove the legacy jq .screenshot_png_b64 path and document
screenshot_out_file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment May 1, 2026 4:42am

Request Review

@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bc876248-6e0b-42ad-ad40-ccb00fd0ad8f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The changes introduce file-based screenshot handling to CUA Driver, allowing screenshots to be written to disk instead of embedded as base64 in responses. This includes updating documentation, adding optional screenshotFilePath fields to the snapshot structure, implementing screenshot_out_file parameter handling, and removing the previous inline image-merging logic from CLI output.

Changes

Cohort / File(s) Summary
Documentation Updates
docs/content/docs/cua-driver/guide/getting-started/integrations.mdx, libs/cua-driver/Skills/cua-driver/SKILL.md
Updated OpenCode integration guide with config location options, prominent MCP server wiring warnings, and new Ollama/vision section. Updated get_window_state response documentation to emphasize file-path based screenshots via screenshot_file_path instead of inline base64.
Data Structure Extension
libs/cua-driver/Sources/CuaDriverCore/AppState/AppState.swift
Added optional screenshotFilePath: String? field to AppStateSnapshot with corresponding initializer parameter and Codable support.
Tool and CLI Implementation
libs/cua-driver/Sources/CuaDriverServer/Tools/GetWindowStateTool.swift, libs/cua-driver/Sources/CuaDriverCLI/CallCommand.swift
Implemented screenshot_out_file parameter in GetWindowStateTool to write base64-decoded screenshots to disk and return file paths; removed mergeImageContentIntoJSON helper from CLI to stop merging image content blocks into structured JSON output.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 No more bloated base64 strings in sight,
Screenshots hop to disk, keeping context light!
File paths whisper where images dwell,
A rabbit's refactor—working quite well! 📸✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a screenshot_out_file parameter and removing default base64 dumps from CLI output, which aligns with the primary objectives of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 feat/cua-driver-screenshot-out-file

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.

… consistency

Aligns the CLI flag name with the tool param name (screenshot_out_file)
so both interfaces use the same terminology.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/content/docs/cua-driver/guide/getting-started/integrations.mdx`:
- Around line 95-97: Update the warning in the Callout: change the message to
reflect the new CLI behavior after CallCommand was changed — remove the claim
that raw base64 screenshot floods stdout and instead warn that image blocks are
dropped by the shell fallback unless callers explicitly use the CLI image output
options; reference get_window_state and advise using --image-out or the
screenshot_out_file option (and prefer running cua-driver as an MCP server) so
image data is preserved.

In `@libs/cua-driver/Skills/cua-driver/SKILL.md`:
- Around line 439-440: The documented response shape incorrectly lists
has_screenshot: bool; remove that entry from the SKILL.md doc for
get_window_state and instead document only the fields actually present on
AppStateSnapshot (screenshot_file_path and the screenshot_* metadata). Update
any nearby examples or JSON schemas in the same file to eliminate references to
has_screenshot so consumers only rely on screenshot_file_path and the
screenshot_* fields emitted by get_window_state.
- Around line 444-450: The fenced code block showing the example CLI usage for
"cua-driver get_window_state" is missing a language, causing markdownlint
warnings; update the fence to include a language (e.g., add "bash" after the
opening ```), so the block becomes a bash code fence and the two example lines
"cua-driver get_window_state
'{\"pid\":N,\"window_id\":W,\"screenshot_out_file\":\"/tmp/shot.jpg\"}'" and
"cua-driver get_window_state '{\"pid\":N,\"window_id\":W}' --image-out
/tmp/shot.jpg" are correctly highlighted.

In `@libs/cua-driver/Sources/CuaDriverCore/AppState/AppState.swift`:
- Around line 57-59: The doc for the public property screenshotFilePath (in
AppState.swift) is incorrect: GetWindowStateTool currently captures JPEG bytes
(captureWindow(format: .jpeg)) and advertises image/jpeg, so update either the
capture to write PNGs or, more simply, change the comment for screenshotFilePath
to reflect the actual image format produced (JPEG) or state that the file
extension/format matches what GetWindowStateTool writes (currently
JPEG/image/jpeg); reference screenshotFilePath, GetWindowStateTool and
captureWindow(format: .jpeg) when making the change so the public contract and
implementation stay consistent.

In `@libs/cua-driver/Sources/CuaDriverServer/Tools/GetWindowStateTool.swift`:
- Around line 277-283: The code currently writes screenshot bytes with try? and
unconditionally sets resolvedScreenshotFilePath, so I need you to perform the
write with error handling and only set resolvedScreenshotFilePath when the write
actually succeeds; locate the block that checks screenshotOutFile and
snapshot.screenshotPngBase64 (and the similar block around lines 287–290),
replace the silent try? bytes.write(to:) with a do/catch or a conditional write
that verifies success, and in the catch/failure path do not set
resolvedScreenshotFilePath (and instead allow the inline image handling to
remain); reference the variables/methods screenshotOutFile,
snapshot.screenshotPngBase64, Data(base64Encoded:), bytes.write(to:), and
resolvedScreenshotFilePath when making the change.
🪄 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: CHILL

Plan: Pro

Run ID: 3ac9182a-0096-4130-9a82-8a3e1a7fc179

📥 Commits

Reviewing files that changed from the base of the PR and between 8a4c513 and bd34d5a.

📒 Files selected for processing (5)
  • docs/content/docs/cua-driver/guide/getting-started/integrations.mdx
  • libs/cua-driver/Skills/cua-driver/SKILL.md
  • libs/cua-driver/Sources/CuaDriverCLI/CallCommand.swift
  • libs/cua-driver/Sources/CuaDriverCore/AppState/AppState.swift
  • libs/cua-driver/Sources/CuaDriverServer/Tools/GetWindowStateTool.swift

Comment on lines +95 to +97
<Callout type="warn">
**Always configure cua-driver as an MCP server — never rely on the CLI fallback.** If MCP is not wired up, OpenCode will call `cua-driver` as a shell subprocess and the full `get_window_state` response (including a ~31 KB base64 screenshot) lands as raw text in the model context, consuming thousands of tokens per call.
</Callout>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update this warning to match the new CLI behavior.

This still describes the old failure mode. After the CallCommand change, the default CLI path no longer splices screenshot base64 into stdout, so the problem with shell fallback is now "image blocks get lost unless you handle --image-out/screenshot_out_file," not "raw base64 floods the context."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/content/docs/cua-driver/guide/getting-started/integrations.mdx` around
lines 95 - 97, Update the warning in the Callout: change the message to reflect
the new CLI behavior after CallCommand was changed — remove the claim that raw
base64 screenshot floods stdout and instead warn that image blocks are dropped
by the shell fallback unless callers explicitly use the CLI image output
options; reference get_window_state and advise using --image-out or the
screenshot_out_file option (and prefer running cua-driver as an MCP server) so
image data is preserved.

Comment on lines +439 to +440
- `has_screenshot: bool` — `true` when a screenshot was captured this turn
(either inline via MCP image block or written to `screenshot_file_path`).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove has_screenshot from the documented response shape.

get_window_state does not emit a has_screenshot boolean here. AppStateSnapshot only carries screenshot_file_path and the screenshot_* metadata, so documenting has_screenshot will send consumers down a branch that never exists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/cua-driver/Skills/cua-driver/SKILL.md` around lines 439 - 440, The
documented response shape incorrectly lists has_screenshot: bool; remove that
entry from the SKILL.md doc for get_window_state and instead document only the
fields actually present on AppStateSnapshot (screenshot_file_path and the
screenshot_* metadata). Update any nearby examples or JSON schemas in the same
file to eliminate references to has_screenshot so consumers only rely on
screenshot_file_path and the screenshot_* fields emitted by get_window_state.

Comment on lines 444 to 450
```
# canonical, works in every capture mode — writes the image bytes
# wherever you point, stdout stays readable (tree in som, summary
# in vision). stderr warns (exit 0) if the response had no image.
cua-driver get_window_state '{"pid":N,"window_id":W}' --image-out /tmp/shot.png

# som-only legacy path: pull the spliced base64 out of structuredContent.
# Prefer --image-out above — it's one flag vs a probe + pipe.
if [ "$(cua-driver get_window_state '{"pid":N,"window_id":W}' | jq -r '.has_screenshot')" = "true" ]; then
cua-driver get_window_state '{"pid":N,"window_id":W}' | jq -r '.screenshot_png_b64' | base64 -d > shot.png
fi
# write to file — stdout stays readable (AX tree / summary only, no base64)
cua-driver get_window_state '{"pid":N,"window_id":W,"screenshot_out_file":"/tmp/shot.jpg"}'

# CLI --image-out flag is equivalent and works for all capture modes
cua-driver get_window_state '{"pid":N,"window_id":W}' --image-out /tmp/shot.jpg
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language to this fenced example.

This block is missing a fence language, which is why markdownlint is flagging it.

Suggested fix
-```
+```bash
 # write to file — stdout stays readable (AX tree / summary only, no base64)
 cua-driver get_window_state '{"pid":N,"window_id":W,"screenshot_out_file":"/tmp/shot.jpg"}'
 
 # CLI --image-out flag is equivalent and works for all capture modes
 cua-driver get_window_state '{"pid":N,"window_id":W}' --image-out /tmp/shot.jpg
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 444-444: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/cua-driver/Skills/cua-driver/SKILL.md` around lines 444 - 450, The
fenced code block showing the example CLI usage for "cua-driver
get_window_state" is missing a language, causing markdownlint warnings; update
the fence to include a language (e.g., add "bash" after the opening ```), so the
block becomes a bash code fence and the two example lines "cua-driver
get_window_state
'{\"pid\":N,\"window_id\":W,\"screenshot_out_file\":\"/tmp/shot.jpg\"}'" and
"cua-driver get_window_state '{\"pid\":N,\"window_id\":W}' --image-out
/tmp/shot.jpg" are correctly highlighted.

Comment on lines +57 to +59
/// File-system path to the saved screenshot PNG, when the caller
/// requested a file-backed screenshot. Absent when no path was written.
public let screenshotFilePath: String?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document the actual screenshot format.

The new comment says screenshot_file_path points to a saved PNG, but GetWindowStateTool currently captures JPEG bytes (captureWindow(format: .jpeg)) and advertises image/jpeg. That makes the public contract misleading for consumers choosing an extension or decoder based on this field's docs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/cua-driver/Sources/CuaDriverCore/AppState/AppState.swift` around lines
57 - 59, The doc for the public property screenshotFilePath (in AppState.swift)
is incorrect: GetWindowStateTool currently captures JPEG bytes
(captureWindow(format: .jpeg)) and advertises image/jpeg, so update either the
capture to write PNGs or, more simply, change the comment for screenshotFilePath
to reflect the actual image format produced (JPEG) or state that the file
extension/format matches what GetWindowStateTool writes (currently
JPEG/image/jpeg); reference screenshotFilePath, GetWindowStateTool and
captureWindow(format: .jpeg) when making the change so the public contract and
implementation stay consistent.

Comment on lines +277 to +283
if let outPath = screenshotOutFile, let b64 = snapshot.screenshotPngBase64 {
let expandedPath = (outPath as NSString).expandingTildeInPath
if let bytes = Data(base64Encoded: b64) {
let url = URL(fileURLWithPath: expandedPath)
try? bytes.write(to: url)
resolvedScreenshotFilePath = expandedPath
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't suppress the inline image unless the file write actually succeeded.

try? bytes.write(to:) drops I/O errors, but resolvedScreenshotFilePath is still set immediately afterward. On a permission or missing-directory failure, the response will advertise screenshot_file_path and also skip the inline image block, so the caller gets neither a real file nor image content.

Suggested fix
                 if let outPath = screenshotOutFile, let b64 = snapshot.screenshotPngBase64 {
                     let expandedPath = (outPath as NSString).expandingTildeInPath
                     if let bytes = Data(base64Encoded: b64) {
                         let url = URL(fileURLWithPath: expandedPath)
-                        try? bytes.write(to: url)
-                        resolvedScreenshotFilePath = expandedPath
+                        do {
+                            try bytes.write(to: url)
+                            resolvedScreenshotFilePath = expandedPath
+                        } catch {
+                            textContent += "\n\n⚠️ Failed to write screenshot_out_file to \(expandedPath): \(error.localizedDescription)"
+                        }
                     }
                 }

Also applies to: 287-290

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/cua-driver/Sources/CuaDriverServer/Tools/GetWindowStateTool.swift`
around lines 277 - 283, The code currently writes screenshot bytes with try? and
unconditionally sets resolvedScreenshotFilePath, so I need you to perform the
write with error handling and only set resolvedScreenshotFilePath when the write
actually succeeds; locate the block that checks screenshotOutFile and
snapshot.screenshotPngBase64 (and the similar block around lines 287–290),
replace the silent try? bytes.write(to:) with a do/catch or a conditional write
that verifies success, and in the catch/failure path do not set
resolvedScreenshotFilePath (and instead allow the inline image handling to
remain); reference the variables/methods screenshotOutFile,
snapshot.screenshotPngBase64, Data(base64Encoded:), bytes.write(to:), and
resolvedScreenshotFilePath when making the change.

…_file

- GetWindowStateTool: only set resolvedScreenshotFilePath when write
  actually succeeds; fall through to inline image block on I/O error
- AppState: fix doc comment — saved file is JPEG not PNG
- SKILL.md: remove stale has_screenshot entry; add bash fence language
- integrations.mdx: update callout to reflect actual CLI behavior after
  mergeImageContentIntoJSON removal

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@f-trycua
f-trycua merged commit 9d623c0 into main May 1, 2026
5 of 9 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.

1 participant