fix(cursor): Add native image support for Cursor - #1228
Conversation
📝 WalkthroughWalkthroughThe PR adds Cursor vision support for image resolution, selected-image protobuf encoding, tool-result promotion, native and sidecar model metadata, and request replay. It also updates Grok 4.5 effort-tier wire IDs and continuation handling. ChangesCursor model capabilities and Grok mapping
Cursor vision request flow
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant LiveTransport
participant CursorImages
participant ProtobufRequest
participant Cursor
Client->>LiveTransport: submit raw messages
LiveTransport->>CursorImages: prepare and resolve images
CursorImages-->>LiveTransport: prepared messages and selected images
LiveTransport->>ProtobufRequest: build Cursor request
ProtobufRequest->>Cursor: send selected context and MCP image content
Cursor-->>ProtobufRequest: return continuation or tool result
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
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. Comment |
⏳ DRAFT
What to do
Review readiness checklist
0/4 boxes ticked. This PR stays in draft until every box above is ticked. |
|
✅ Deterministic PR hygiene checks passed. |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/adapters/cursor/images.ts`:
- Around line 271-305: Update resolveCursorImages so an image whose prepared
data exceeds MAX_CURSOR_IMAGE_BYTES is omitted with continue instead of throwing
CursorImageError. Preserve the existing behavior for other validation failures
and continue processing subsequent images, aligning this path with
prepareCursorImageDataUrl’s omission policy.
- Around line 459-475: Update the JPEG marker scan in the dimension-detection
function to recognize standalone markers 0xD0–0xD9 and 0x01 before reading a
length, advancing past them without consuming length bytes. Preserve the
existing SOF0/SOF2 dimension extraction and malformed-length handling for
markers that carry a length.
- Around line 564-593: Restrict isTransparentCursorVisionSuffix to developer
messages containing the known Codex Desktop <multi_agent_mode> guidance block,
rather than any image-free developer message. Preserve intentional developer
instructions, while keeping stripTrailingTransparentDeveloperMessages and
cursorIsTrailingToolResultContinuation behavior unchanged for the recognized
guidance suffix.
In `@src/adapters/cursor/live-transport.ts`:
- Around line 573-579: Eliminate duplicate image preparation across both sites:
in src/adapters/cursor/live-transport.ts lines 573-579, make
resolveActiveCursorImages reuse the output of prepareCursorRawMessages and
enforce one deadline for the complete image phase; in
src/adapters/cursor/images.ts lines 664-709, update prepareCursorContentParts to
bypass parts already marked image/jpeg and within the soft cap, preventing
repeated processing of accumulated view_image history.
In `@src/adapters/cursor/protobuf-request.ts`:
- Around line 364-418: Export the existing decodeDataUrl helper from images.ts,
then replace the inline data-URL parsing and Buffer.from logic in
toolResultContentItems with a call to decodeDataUrl inside the existing try
block. Preserve the current omission behavior by returning no image item when
decoding fails or the shared size limits reject the payload.
- Around line 534-538: Update the flush function to skip pending external-model
MCP tool calls that have no result, while preserving image-path pairing and
existing handling for answered calls. Do not emit result-less external entries
through toolCallStep; only append valid completed calls to current.steps.
In `@tests/cursor-blob.test.ts`:
- Around line 838-1196: Add focused regression coverage for the live-transport
image phase, targeting the wiring around prepareCursorRawMessages,
resolveActiveCursorImages, and preparedRequest in the live transport flow.
Exercise the production path end to end so the resolver receives the prepared
rawMessages and produces selectedImages, rather than injecting selectedImages
directly through encodeCursorRunRequest; place the test with the existing cursor
image or live-transport tests.
In `@tests/cursor-request-builder.test.ts`:
- Around line 223-254: Add a focused regression test alongside the existing
createCursorRequest tests for a conversation containing an assistant message
whose content consists only of a toolCall part. Assert that this assistant
message converts to undefined and is omitted from request.messages, and verify
the resulting final message role remains the expected user role for
protobuf-request lastRole handling.
🪄 Autofix
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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: a4299e02-aacb-425b-bb01-2dfefc5c30fb
⛔ Files ignored due to path filters (1)
tests/helpers/cursor-grumpy-fixture.pngis excluded by!**/*.png
📒 Files selected for processing (22)
docs-site/src/content/docs/reference/adapters.mddocs-site/src/content/docs/reference/configuration/providers.mdsrc/adapters/cursor/discovery.tssrc/adapters/cursor/effort-map.tssrc/adapters/cursor/images.tssrc/adapters/cursor/live-transport.tssrc/adapters/cursor/protobuf-request.tssrc/adapters/cursor/request-builder.tssrc/adapters/cursor/types.tssrc/providers/registry.tssrc/types.tstests/catalog-vision-sidecar-modalities.test.tstests/cursor-blob.test.tstests/cursor-discovery.test.tstests/cursor-effort-suffix.test.tstests/cursor-images.test.tstests/cursor-request-builder.test.tstests/cursor-static-catalog.test.tstests/cursor-vision-wire-harness.test.tstests/model-in-list.test.tstests/oauth-provider-reconcile.test.tstests/provider-registry-parity.test.ts
|
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a76c091700
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // JPEG soft-cap rewrite for attach + view_image tool-result data URLs before encode. | ||
| const rawMessages = await prepareCursorRawMessages(request.rawMessages); | ||
| const selectedImages = await resolveActiveCursorImages(rawMessages, signal); | ||
| const preparedRequest = { ...request, rawMessages, selectedImages }; |
There was a problem hiding this comment.
Rebuild Cursor messages after image prep
When prepareCursorRawMessages() converts an active user/developer image part into text (for example an unsupported or corrupt image), only rawMessages is replaced here while request.messages still contains the pre-rewrite text that deliberately dropped image parts. activePromptText() then reads the stale message first, so the omission marker is lost; for an image-only turn this even falls through to resumeAction with no user text or SelectedImage. Recompute/sync the prepared messages from the rewritten raw messages before encoding.
AGENTS.md reference: src/AGENTS.md:L19-L19
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Already fixed on current head: live-transport rebuilds messages via cursorRequestMessagesFromRaw after prepare. Closing as stale.
| return { | ||
| parts: kept.map(entry => entry.part), | ||
| omittedOlder: Math.max(0, entries.length - kept.length), | ||
| promotedCallIds: new Set(kept.map(entry => entry.callId)), |
There was a problem hiding this comment.
Omit only promoted images from mixed tool-result calls
When a single trailing tool result contains more than MAX_CURSOR_IMAGES images, kept contains only the newest images but this set records just the call id. The protobuf builder later treats that call id as fully promoted and replaces every image in that tool result with the SelectedImage marker, so older overflow images are neither promoted nor kept on MCP as intended. Track the promoted image parts/indices instead of the whole call id.
AGENTS.md reference: src/AGENTS.md:L19-L19
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Already fixed on current head: promote omit uses per-part keys (callId#index); overflow in the same toolResult stays on MCP. Closing as stale.
| export function cursorIsTrailingToolResultContinuation( | ||
| messages: readonly OcxMessage[] | undefined, | ||
| ): boolean { | ||
| if (!messages?.length) return false; | ||
| return stripTrailingTransparentDeveloperMessages(messages).at(-1)?.role === "toolResult"; |
There was a problem hiding this comment.
Use the continuation helper for retry gating
For view_image continuations followed by Desktop's trailing multi-agent developer message, this helper now correctly reports a tool-result continuation after stripping that suffix, but createCursorAdapter still uses the raw last-message role for its invalid-argument retry guard (src/adapters/cursor.ts:114). In that Desktop shape the guard is false, so a pre-output Cursor invalid_argument can be replayed in a fresh conversation even though tool-result resumes are explicitly excluded from retry; use this helper there too.
AGENTS.md reference: src/AGENTS.md:L19-L19
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Already fixed on current head: retry gating uses cursorIsTrailingToolResultContinuation. Closing as stale.
| return [create(McpToolResultContentItemSchema, { | ||
| content: { | ||
| case: "image", | ||
| value: create(McpImageContentSchema, { | ||
| data, |
There was a problem hiding this comment.
Keep old tool-result images out of replay turns
When a later user turn follows an earlier view_image, that prior tool result is no longer the active image turn, but this branch still decodes the historical data URL into McpImageContent while building retained conversation turns. The root replay budget only accounts for text because contentToText drops images, so several retained screenshots can silently blow the Cursor request/hydration payload even though SelectedImage is active-turn-only; gate image MCP content to the trailing active tool-result block or budget these bytes.
AGENTS.md reference: src/AGENTS.md:L19-L19
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Already fixed on current head: historical MCP images outside the trailing promote window are omitted. Closing as stale.
| } | ||
| const resolved = url.toLowerCase().startsWith("data:") | ||
| ? decodeDataUrl(url) | ||
| : await fetchHttpsImageBytes(url, signal); |
There was a problem hiding this comment.
Omit stale HTTPS images instead of aborting
If the active Cursor turn contains an HTTPS image URL that fails DNS, times out, returns a non-image content type, or otherwise cannot be fetched, this await propagates CursorImageError through resolveActiveCursorImages() and the live transport aborts the whole request before Cursor sees the text. Data URLs in the same path are safely converted to omission text when they cannot be prepared, so remote image failures should degrade the same way rather than fail an otherwise valid turn.
AGENTS.md reference: src/AGENTS.md:L17-L17
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Already fixed on current head: HTTPS/decode failures soft-omit to omission text instead of aborting the turn. Closing as stale.
|
Addressed the high-signal automated review items:
Verification: focused unit suite (157 pass) + typecheck; collab-on headless attach + |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs-site/src/content/docs/reference/configuration/providers.md`:
- Around line 275-278: Update the Vision section in the provider configuration
documentation to explicitly include the curated GLM entries from noVisionModels
as vision-sidecar users, or clarify that auto and composer-* are examples rather
than an exhaustive list. Keep the documented model behavior aligned with the
actual noVisionModels configuration.
🪄 Autofix
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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 8e25a38a-1fb3-4959-80ba-33269f6ad9c0
📒 Files selected for processing (8)
docs-site/src/content/docs/reference/configuration/providers.mdsrc/adapters/cursor.tssrc/adapters/cursor/images.tssrc/adapters/cursor/live-transport.tssrc/adapters/cursor/request-builder.tstests/cursor-adapter.test.tstests/cursor-images.test.tstests/cursor-request-builder.test.ts
|
Also landed the low-risk optional review cleanups:
Verification: focused unit suite + typecheck green. |
6c43f20 to
869a4b0
Compare
Wibias
left a comment
There was a problem hiding this comment.
I would not merge this yet. The overall SelectedImage/SSRF approach looks reasonable, but there are several correctness/availability blockers in the current head.
Merge blockers
-
HTTPS image failures can abort or erase the active user turn.
prepareCursorRawMessages()only rewritesdata:images. Remote images are resolved later. DNS failures, timeouts, non-image responses, etc. currently propagate and abort the whole request. Worse, if an HTTPS image fetch succeeds butprepareCursorImageForWire()later omits it, an image-only user turn can end up withselectedImages=[]and empty text, causing protobuf construction to selectresumeAction. Cursor then receives no new user input. Remote failures should degrade consistently to an omission marker/text-only turn, never silently become resume. -
Historical
view_imagepixels are replayed after the active image turn.
conversationTurns()still serializes old image-bearing tool results asMcpImageContent. Those bytes are outside the normal root replay text budget, so accumulated screenshots can materially inflate the request/hydration payload and keep getting retransmitted to Cursor after they are no longer needed. Gate MCP image bytes to the active trailing tool-result block, or explicitly budget/omit historical image bytes. -
Result-less external-model MCP tool calls are emitted during replay.
External assistant tool calls go intopendingToolCalls, butflush()emits every remaining entry throughtoolCallStep()even without a result. This conflicts with the surrounding external-model handling that intentionally avoids native MCP tool-call replay because those structures can causeinvalid_argument. Unanswered/interrupted calls should be dropped for external models; completed image-call/result pairs still need to be preserved. -
Promotion overflow is tracked at call-ID granularity instead of image granularity.
extractTrailingToolResultImagePromotion()keeps the newestMAX_CURSOR_IMAGESentries but returns onlypromotedCallIds. If one tool result contains more than 12 images, that call ID is marked promoted and protobuf omits all images from that result. The older overflow images are therefore neither promoted nor retained on MCP, despite the stated behavior. Track the promoted image parts/indices rather than the entire call ID. -
Image preprocessing is duplicated and has no aggregate deadline.
The live path runsprepareCursorRawMessages()and thenresolveActiveCursorImages(), causing activedata:images to be decoded/re-encoded again. Historical data URLs are also prepared again on subsequent turns. HTTPS processing is serial with a per-image timeout, so 12 stalled images can delay stream creation for a very long time. Reuse prepared results and bound the entire image phase with one deadline/budget.
Security/resource hardening
- The SSRF design itself looks good: HTTPS-only, destination checks, public DNS resolution, and pinned connection/SNI behavior. I did not find an obvious auth bypass, path traversal, command injection, or network-pivot issue in this diff.
- The 16 MiB encoded-byte ceiling does not bound decoded pixel memory.
prepareCursorImageForWire()forces a fullBun.Imagedecode before resize/dimension limiting, so a highly compressed image with huge dimensions can still create a decompression/memory-DoS path. Add max dimensions / total-pixel limits before expensive processing where possible. Buffer.from(..., "base64")is lenient, so malformed base64 is not reliably rejected by the current try/catch. Also, accepting any undecodable <=64-byte payload for PNG/JPEG/GIF/WebP is a production validation bypass added for unit-test stubs. Tests should use real minimal images instead; production should stay fail-closed.
Required regression coverage
- image-only HTTPS unsupported/corrupt image must produce a user action with an omission marker, never
resumeAction - failed HTTPS image fetch plus valid text must continue text-only
- one tool result with >12 images must promote exactly 12 and preserve overflow correctly
- external assistant tool call without a result must not produce a result-less historical
mcpToolCall - prior
view_imagefollowed by a new user turn must not replay historicalMcpImageContent - malformed base64 must be rejected strictly; remove the <=64-byte production bypass
- add pixel/dimension limits and a bounded aggregate image-processing deadline
Some existing bot comments are already stale on this head (JPEG standalone-marker handling, shared capped data-URL decoder use, message rebuild after prep, retry continuation helper), so those should not be fixed twice.
Please address the blockers above and get the PR checks green before merge.
|
@yansigit Status check on this one, and what it needs to move. It is currently Deliberately not rebasing this for you. The other stale PRs I picked up in this pass were small, mechanical rebases where the author's intent was unambiguous. This one adds native image support across eight files of the Cursor adapter, including the protobuf request builder and live transport; resolving those conflicts means re-deciding your own design against a moved base, and I would be guessing at your intent rather than preserving it. To move it forward:
If you would rather not carry it, say so and I will close it as stale with the work preserved in the record — no judgement either way. If you do want to keep going, ping me after the rebase and I will review promptly rather than leaving it to age again. |
869a4b0 to
0f04a8c
Compare
|
@lidge-jun @Wibias — rebased onto current Wibias blockers
Security / docs
Intentionally not expanded
Please re-review when CI settles. (Linux |
Wibias
left a comment
There was a problem hiding this comment.
Updated full review on current head 0f04a8c487b73c352328050f777b248a9faed56a.
The prior blockers are substantially improved: HTTPS failures now soft-omit and rebuild the text channel; historical MCP image bytes are omitted in replay; external result-less MCP calls are dropped; promotion is tracked per image part; retry gating uses the continuation helper; base64 validation is strict; and PNG/JPEG/GIF dimension/pixel caps plus an aggregate image-phase signal were added. The HTTPS SSRF boundary also still looks sound: HTTPS-only, destination/DNS validation, pinned peer, original Host/SNI, and certificate verification. I did not find an obvious secret leak, auth bypass, path traversal, command injection, or SSRF pivot in this diff.
I still would not merge this head. Remaining blockers:
-
P1/P2 correctness + validation bypass: the small-JPEG fast path trusts only
image/jpeg+FF D8.
InprepareCursorImageForWire,alreadySmallJpegreturnsreadybefore the fullBun.Imagedecode when the declared MIME is JPEG, the payload is under the soft cap, and the first two bytes areFF D8. A truncated/corrupt payload such asFF D8 00 00therefore reachesSelectedImageeven though this path is documented fail-closed. There is a second mismatch case after the generic decode: a valid small PNG/WebP/GIF declared asimage/jpegis returned unchanged as JPEG, so bytes, MIME and attachment extension disagree. The existing fake-JPEG regression uses0xFFbytes and does not hit theFF D8shortcut. Validate the actual format before passthrough, or carry explicit provenance for an image already produced by the JPEG prep stage. Add regressions for truncatedFF D8...and valid PNG bytes mislabeled as JPEG. -
P1/P2 resource hardening: WebP still bypasses the pre-decode pixel/edge guard.
WebP is accepted byCURSOR_VISION_PASSTHROUGH_MIME, butsniffCursorImageDimensionsonly parses PNG/GIF/JPEG. For WebP,prepareCursorImageForWireenters a fullBun.Image(...).resize(...).jpeg(...)decode before dimensions are checked viametadata(). A small compressed image with huge decoded dimensions can therefore allocate/decode far beyond the intendedMAX_CURSOR_IMAGE_DECODE_EDGE/MAX_CURSOR_IMAGE_PIXELSlimits. Preflight WebP dimensions (and reject over-budget images) before the expensive decode. Add a regression with an oversized WebP header/container. -
P2 privacy/performance: historical image URLs are still fetched and historical pixels are reprocessed on every later turn.
createCursorRequeststores the completeparsed.context.messagesasrawMessages; live transport runsprepareCursorRawMessages(request.rawMessages, ...); andprepareCursorRawMessagesprepares images in every user/developer/toolResult message. But the protobuf replay layer now intentionally strips historical image pixels and keeps only active/trailing promotion bytes. So an old HTTPS image can cause a new DNS/HTTP request on each subsequent turn, leaking later turn timing/activity to that remote host and adding avoidable latency; historical data URLs are repeatedly decoded/re-encoded too. Scope byte preparation/fetching to the active user/developer image turn and the trailing tool-result promotion block. Historical replay should omit image parts without touching the source URL or pixels. -
P2 availability: the 30 s image-phase deadline is not an aggregate CPU deadline for local images.
createCursorImagePhaseSignalcan abort HTTPS I/O, but data-URL decode/JPEG work does not consult the signal, and the content loop does not stop starting local image work after the signal fires. The current deadline test only proves parent-abort propagation; it does not prove that local image preprocessing stops at the deadline. At minimum check the signal before/after each image and do not start further decode/encode jobs after expiry. Combined with blocker 3, old local images can currently consume CPU beyond the advertised phase bound.
Maintainer/review status
Several still-unresolved CodeRabbit/Codex threads are stale on this head and should be closed with a short explanation rather than fixed twice: shared capped data-URL decode, result-less external flush, per-part promotion, retry continuation handling, historical MCP serialization, JPEG standalone markers, and prepared-message rebuild are already addressed. The CodeRabbit performance concern remains substantively relevant because historical preprocessing/refetch is still happening.
Usefulness
The feature is useful and the overall architecture is much better than the first reviewed head: native SelectedImage for capable Cursor models plus sidecar fallback for curated blind models is a sensible split, and the view_image continuation handling solves a real failure mode. The remaining issues are boundary/safety problems, not a reason to abandon the feature.
Verdict: REQUEST CHANGES. Fix the four items above and add focused regressions before merge. CI is mostly green on this SHA; Cross-platform CI was still running test shard 3/4 when this review was submitted.
Resolve active-turn images before prepare/encode, attach selectedContext on the active UserMessage, omit image placeholders from text, and remove Cursor from noVisionModels so native vision replaces the sidecar path.
Add focused unit tests for images.ts caps, data URL validation, HTTPS/SSRF rejection, and active-turn selection. Extend cursor-blob coverage to assert history/root UserMessages omit selectedContext while the active turn keeps inline SelectedImage data.
Assert Cursor is absent from noVisionModels, advertises image through modelInputModalities, keeps catalog hints sidecar-free, and skips the vision sidecar when requests carry images.
Keep image-only user/developer turns in request-builder as empty-string active messages, and choose userMessageAction when selectedImages are present even if prompt text is empty. Add regression tests for first-turn and follow-up image-only encodes.
External Cursor models flattened tool results to text via contentToText(), which dropped image parts from view_image continuations. Route image-bearing tool results through the native mcpToolCall/McpImageContent path while keeping text-only external replay flattened. Adds grok-4.5 wire regression coverage and a reusable vision harness test.
Prep before the 1 MiB hard cap, promote consecutive view_image tool results with nudge text and MCP dedupe, fail-closed on undecodable MIME, and restore curated Auto/Composer sidecar noVisionModels. Co-authored-by: Cursor <cursoragent@cursor.com>
Treat trailing non-image developer injections as transparent for SelectedImage resolution and tool-continuation gates so Codex Desktop collab guidance no longer drops vision after view_image. Also enforce the JPEG soft-cap with edge shrink and map Grok none/minimal to medium with the cursor- wire prefix. Co-authored-by: Cursor <cursoragent@cursor.com>
Scope transparent developer suffixes to multi_agent_mode, reuse that continuation helper for invalid_argument retry gating, omit oversized prepared images instead of failing the turn, and rebuild text messages after JPEG prep so omission markers reach activePromptText. Co-authored-by: Cursor <cursoragent@cursor.com>
Skip standalone JPEG markers before SOF so SelectedImage dimensions stay correct, reuse decodeCursorImageDataUrl for MCP tool-result encode size limits, and cover the live-transport prepare→resolve→SelectedImage wiring. Co-authored-by: Cursor <cursoragent@cursor.com>
Soft-omit HTTPS/image failures onto userMessageAction text, strip historical McpImageContent outside the trailing promote window, drop result-less external mcpToolCalls, omit promote at image-part granularity, and add a shared image-phase deadline plus decode hardening with regression coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
Require JPEG SOF before soft-cap skip, sniff WebP dims before Bun decode, scope prepare/fetch to the active vision window, and honor the image-phase AbortSignal across local encode work. Co-authored-by: Cursor <cursoragent@cursor.com>
0f04a8c to
f44619a
Compare
|
@Wibias @lidge-jun — addressed the four remaining blockers from review on Round-2 blockers
Verification
Please re-review when CI settles. Readiness checklist remains 4/4 after local green. (Linux |
Summary
SelectedImage(blobIdWithData, JPEG soft-cap with edge shrink,view_image→ SelectedImage promotion + MCP image omit).developerinjections as transparent for vision/tool-continuation so Codex Desktop no longer drops SelectedImage afterview_imageand hallucinates.cursor/grok-4.5, map Codex effortnone/minimalto wiremediumand send live-discoverycursor-grok-4.5-{low,medium,high}ids (Fast still uses parameterizedgrok-4.5).Verification
bun test tests/cursor-images.test.ts tests/cursor-blob.test.ts tests/cursor-vision-wire-harness.test.ts tests/cursor-effort-suffix.test.tsbun run typecheckorigin/dev(f44619aa)HEAD=f44619aawithmulti_agent_v2ON (cursor/grok-4.5, efforthigh):I HATE WHEN/PEOPLE USE ME TO ILLUSTRATE A POINTview_image(collab on): same exact captions after tool resultbun run src/cli/index.ts ensurebefore smokeChecklist
Made with Cursor
Review readiness checklist
This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:
All CI tests are green on my local testing.
I pushed my PR to the latest dev commit.
I resolved all correct Codex and CodeRabbit findings.
My PR is ready for review.