feat: rebuild the Cursor adapter for OpenCode V2 - #35
Open
CasualDeveloper wants to merge 2 commits into
Open
CasualDeveloper wants to merge 2 commits into
CasualDeveloper wants to merge 2 commits into
Conversation
CasualDeveloper
marked this pull request as draft
September 10, 2026 13:03
CasualDeveloper
force-pushed
the
feat/opencode-v2-host-owned-cursor
branch
from
September 10, 2026 18:11
4faffe5 to
341c781
Compare
CasualDeveloper
marked this pull request as ready for review
September 10, 2026 18:17
CasualDeveloper
force-pushed
the
feat/opencode-v2-host-owned-cursor
branch
from
September 16, 2026 05:07
341c781 to
3670345
Compare
CasualDeveloper
force-pushed
the
feat/opencode-v2-host-owned-cursor
branch
11 times, most recently
from
September 23, 2026 00:34
9719566 to
b99cfe8
Compare
Reconstruct roles, tool outcomes, images, and reasoning from OpenCode's history. Preserve exact private-model selections while OpenCode owns tools, permissions, steering, persistence, and compaction. Remove the old proxy and compatibility implementation from the V2 channel. Use bounded per-Run HTTP/2 workers, validated Connect completion, and authoritative host tool handoffs. Preserve signed and opaque reasoning through restart. Report each host step's usage as Cursor's checkpoint occupancy, counted as uncached input, plus that step's streamed output tokens. Tool steps previously reported no usage, so a long session filled a 1M context without compacting, while single-step Runs reported Run totals larger than the window and compacted early. Keep exact Run counters in provider metadata. Answer every Cursor exec frame and interaction query. Complete native shell streams with shellResult and streamClose; without them Cursor kept the turn pending after every shell call, so each resumed Run stalled until the watchdog fired. Fail unserved frames in band, decline Cursor-hosted web, question, and mode-switch gates, and decline native StrReplace, which would write formatted host reads back into files. Offer Cursor only its MCP tool family, the allowlist the official SDK sends for tools: ["mcp"]. It keeps get_mcp_tools_tool_call, so Cursor starts normally, and models see OpenCode's tool names and schemas. Map native workspace requests that still arrive to host tools, route empty-pattern grep calls to glob, and unwrap tool result JSON envelopes. Wait up to 180 seconds for a resumed Run's first output, and open rebuilt Runs by saying the preceding tool results answer the model's own calls. Name host rules as the application's system prompt: in live checks, Composer, Grok 4.7, and Auto followed a project instruction over a conflicting user request in 18 of 18 trials, up from 6 of 9. Target released @opencode/plugin, @opencode/schema, and @opencode/cli 2.0.6. Replace the removed catalog API with provider source registration and reload. Align the host fixture with the 2.0.6 permission and form APIs, and accept npm 12 pack output in the package check. Match opaque checkpoint roots to visible text and tools even when Cursor also streamed thinking. Omit unmatched extra roots instead of failing an already-streamed answer. Treat Cursor reasoning-effort as a variant parameter, recognizing any effort-valued parameter including reasoning_effort and minimal. Keep Claude thinking on that same model: none is non-thinking, and thinking SKUs become low/medium/high/xhigh/max. Prefer default, then none, low, medium, high, xhigh, max. Preserve Connect error message details in the transport. Publish all non-duplicate models from AvailableModels so newly released and parameterized models (including Grok 4.7 and Grok 4.7 500K) appear in OpenCode's picker, sending exact usable wire IDs on each variant. Use a models.dev Cursor row for limits and price when OpenCode has one; do not copy lab caps. Verified with npm run verify against the pinned 2.0.6 host and live multi-step tool runs on WSL with Composer 2.5, Grok 4.7, and Auto. Refs otto-assistant#31
CasualDeveloper
force-pushed
the
feat/opencode-v2-host-owned-cursor
branch
from
September 23, 2026 08:40
b99cfe8 to
8ea167b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hey, @makeittech - was excited to try out Astra and I think it did a great job with our plugin!
Issue for this PR
Refs #31. Please keep it open through publication and release checks.
This PR supersedes #34 and includes its release-flow documentation and constants-test explanation. Please review and merge this PR directly.
Type of change
What does this PR do?
I rebuilt the Cursor adapter for OpenCode V2 and removed the old proxy and compatibility code. It keeps private models and exact variants while OpenCode manages history, tools, permissions, and compaction.
Each new Cursor Run is built from OpenCode's messages and tool results, including images and reasoning data. This lets the host recover after a lost connection or restart without a second conversation store.
Each step reports Cursor's checkpoint occupancy as its input and its streamed output tokens as its output, so OpenCode's context meter and compaction follow the real conversation size. Before, tool steps reported no usage, so one long session filled a 1M context without compacting, and single-step Runs reported Run totals larger than the window, so compaction ran too early. Exact Run totals, including cache reads and writes, stay in provider metadata. Cursor gives no per-call cache split, so OpenCode's cost estimate is an upper bound on long tool loops.
Cursor is offered only its MCP tool family: the same
x-cursor-agent-allowed-toolslist the official SDK sends fortools: ["mcp"]. Models see OpenCode's tool names and schemas instead of a second set of Cursor-native tools. The list includesget_mcp_tools_tool_call, so Cursor still starts normally; allowing onlymcp_tool_callbroke bootstrap. Native workspace requests that still arrive (shellStreamArgs,shellArgs,grepArgs,readArgs,writeArgs,lsArgs) map to OpenCode host tools, empty-pattern grep calls route toglob, and tool result JSON envelopes are unwrapped.Every Cursor exec frame and interaction query now gets a reply:
shellResultandstreamClose. Without them, Cursor kept the turn pending after every shell call, so each continuation stalled until the watchdog fired. This is why long sessions looked stuck.throwandstreamCloseinstead of leaving Cursor waiting.A resumed Run waits up to 180 seconds for first output, like a fresh one. A rebuilt Run tells the model that the preceding tool results answer its own calls, instead of an opener that read like an interruption. The host-contract rule names OpenCode's rules as the application's system prompt rather than the user's.
I updated the adapter after OpenCode removed
context.catalog. It now usescontext.provider.transform()andreload(), refreshes models oncredential.switched, and preserves failed-tool outcomes in structured compaction requests. Dependencies and the test host are pinned to released@opencode/*2.0.6. Older hosts with the catalog API are no longer supported.Grok opaque-reasoning checkpoints no longer fail a completed turn when a root has no unique assistant anchor. Cursor usable-model IDs group into families:
noneis the non-thinking SKU, and thinking SKUs become low/medium/high/xhigh/max.The OpenCode id stays the stable catalog name, such as
grok-4.6. Each variant still sends Cursor's exact wire id, such ascursor-grok-4.6-xhigh. All non-duplicate models fromAvailableModelsare published so newly released and parameterized models (such as Grok 4.7 and Grok 4.7 500K) appear in OpenCode's picker. If OpenCode has loaded a models.dev Cursor row, I copy that row's limits and price. I do not copy lab caps, and I do not replace the live catalog with a static list.I also fixed model grouping against Cursor's real parameter spellings: any effort parameter (including
reasoning_effortandminimal) groups into variants on one model, and Claude'sthinkingflag stays on that same model (noneis non-thinking, while effort variants keep thinking enabled) instead of creating separate models. Connect error message details are also preserved in the transport.How did you verify your code works?
The current head is
8ea167b. I amended it onto the previous head because #35 had no review activity.npm run verifypasses locally and in CI against the pinned2.0.6host. The host fixture now uses 2.0.6's permission and form APIs, which fixes the earlierMissing key ["decision"]failure.Live checks for this head ran on WSL through OpenCode
2.0.14with Composer 2.5, Grok 4.7, and Auto:AGENTS.mdrequiring one reply and the user asking for another, the models followed the project instruction in 18 of 18 trials, up from 6 of 9 with the old host-contract wording.Instruction priority on Opus and with images, opaque-reasoning replay, and continued work after automatic compaction still need live verification. The test report records the remaining limits.
Checklist