Skip to content

Feature: Addition of Cursor Support#1772

Closed
arpan404 wants to merge 16 commits intopingdotgg:mainfrom
arpan404:arpan404-cursor
Closed

Feature: Addition of Cursor Support#1772
arpan404 wants to merge 16 commits intopingdotgg:mainfrom
arpan404:arpan404-cursor

Conversation

@arpan404
Copy link
Copy Markdown

@arpan404 arpan404 commented Apr 6, 2026

What Changed

  • Added Cursor as first-class provider.
  • Each and every models that cursor provides are integrated with proper model family and variant distinction
    • for eg: Cursor provides: GPT 5.4 High Fast. This is broken into:
      - Family: GPT 5.4,
      - Effort High
      - Fast: on.

Why

T3Code lacks the support for Cursor. During a recent live stream, Theo said that "Cursor ACP is not as reliable as Cursor Team claims and often provides outdated model lists." I had already implemented a solution addressing this limitation in another fork of the project, which I have been using in my daily workflow without issues. This PR ports that implementation into T3Code, enabling full Cursor support with proper handling of model families, variants, and performance modes.

UI Changes

N/A

Checklist

  • This PR is small and focused
  • [ x ] I explained what changed and why
  • [ ´] I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

This PR is large due to feature complexity, but all models have been tested in different modes and work as expected. If reviewing this PR is too cumbersome, maintainers can refer to my implementation and integrate Cursor directly. I am happy to assist with that process.

Note: Using the acpAdapter I introduced here, Gemini CLI can be easily be integrated.

One key limitation of this cursor integration is that Cursor does not provide any usage and context window details.

Following are some samples of Cursors in Action:
image
image
image
image


Note

High Risk
Large change adding a new provider integration that spawns external binaries (cursor-agent) and manages long-lived ACP sessions/events; errors or edge cases could impact orchestration session state, approvals, and runtime stability. Also tweaks attachment-path decoding and Claude thinking-token limits, which can affect request handling and model behavior.

Overview
Adds Cursor as a first-class provider across server runtime: a new CursorAdapter that speaks Cursor ACP (session start/resume, streaming deltas, tool approvals, user-input prompts, interrupt/cancel, rollback-by-restarting sessions with transcript bootstrapping) plus supporting metadata/error-parsing helpers and extensive tests.

Extends git-side text generation routing to support provider "cursor" via a new CursorTextGenerationLive implementation that runs cursor-agent --print with JSON schema validation/timeouts and maps failures to typed TextGenerationError.

Updates orchestration ingestion to treat turn.aborted as a terminal lifecycle event (status interrupted), finalize partial assistant output, and clear buffered plans, and tightens attachments serving by decoding URL paths before normalization. Separately adds Claude thinking budget options and applies max-thinking-token limits when enabled, and adjusts integration harness layers to provide ProjectionThreadMessageRepositoryLive.

Reviewed by Cursor Bugbot for commit 695b425. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add Cursor as a first-class provider with session management, model selection, and traits UI

  • Introduces the full Cursor provider stack: CursorProvider shells out to cursor-agent to list models and auth status; CursorAdapter manages ACP sessions over JSON-RPC stdin/stdout via acpClient.
  • Adds cursorModelSelector to group cursor models into families and resolve exact model slugs from traits (reasoningEffort, fastMode, thinking, maxMode); the provider model picker and traits picker use this to show family-level choices and emit concrete slugs.
  • Extends ProviderService to use a local transcript as the authoritative source for cursor sessions: sendTurn and startSession pass resumeCursor and replayTurns derived from persisted projection messages, falling back to rebuild-local-transcript when no resume cursor exists.
  • Adds CursorTextGeneration so title/commit generation spawns cursor-agent --print --output-format json with a resolved model slug.
  • Adds Claude thinking budget support (low/medium/high) to ClaudeAdapter and surfaces it as a selectable option in the traits picker.
  • Risk: Cursor sessions rely on local projection messages for replay; if those messages are missing or incomplete, the session starts with a partial or empty transcript.

Macroscope summarized 695b425.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 6, 2026

Important

Review skipped

Auto 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2f45027e-20e7-47fe-9d10-99b46aa6c60d

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@github-actions github-actions bot added size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Apr 6, 2026
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp bot commented Apr 6, 2026

Approvability

Verdict: Needs human review

Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

You can customize Macroscope's approvability policy. Learn more.

await context.client.close();
}),
).then(() => undefined),
);
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.

stopAll deletes sessions before close, skipping turn settlement

Medium Severity

In stopAll, sessions.delete(threadId) runs before context.client.close(), but in stopSession the order is reversed. The close handler (set during startSession) checks sessions.get(input.threadId) and returns early if the session is gone. This means stopAll silently skips settling any active turn (no turn.completed/turn.aborted event emitted) and never emits session.exited. Downstream consumers like ProviderRuntimeIngestion won't learn that turns ended or sessions closed, potentially leaving orchestration threads stuck in a stale "running" state.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4cf2302. Configure here.

return matched;
}
}
return preferredKinds.every((kind) => kind.startsWith("allow_")) ? options[0] : undefined;
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.

🟢 Low Layers/CursorAdapterToolHelpers.ts:532

selectCursorPermissionOption returns options[0] when all preferredKinds start with "allow_" but no match exists. This returns a reject option (like "reject_once") when the caller explicitly requested an allow option. Consider returning undefined instead so the caller can handle the mismatch rather than receiving an unintended option.

+  return preferredKinds.every((kind) => kind.startsWith("allow_")) ? undefined : options[0];
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/CursorAdapterToolHelpers.ts around line 532:

`selectCursorPermissionOption` returns `options[0]` when all `preferredKinds` start with `"allow_"` but no match exists. This returns a reject option (like `"reject_once"`) when the caller explicitly requested an allow option. Consider returning `undefined` instead so the caller can handle the mismatch rather than receiving an unintended option.

Evidence trail:
apps/server/src/provider/Layers/CursorAdapterToolHelpers.ts lines 522-534 (function definition and fallback logic at line 532); apps/server/src/provider/Layers/CursorAdapterToolHelpers.test.ts lines 119-125 (test showing undefined is returned when reject preferred but only allow options exist - reverse case is not tested)

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 695b425. Configure here.

settleTurn(activeContext, activeContext.activeTurn.id, {
type: "completed",
errorMessage: `Cursor ACP exited unexpectedly (code=${code ?? "null"}, signal=${signal ?? "null"}).`,
});
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.

Close handler reports "unexpectedly" for graceful stops

Medium Severity

The close handler always settles an active turn with errorMessage: "Cursor ACP exited unexpectedly..." regardless of whether context.stopping is true. The stopping flag is correctly checked for the session.exited event's reason and exitKind, but the settleTurn call ignores it. During a graceful stopSession, this causes the turn to emit a turn.completed with state: "failed" and a misleading error message, which the orchestration layer interprets as an error condition before the subsequent session.exited event arrives.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 695b425. Configure here.

: status === "ready"
: isTerminalTurnLifecycleEvent(event)
? lastErrorFromTerminalTurnEvent(event, thread.session?.lastError ?? null)
: status === "ready" || status === "interrupted"
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.

Last error clearing now affects all providers' interrupted state

Medium Severity

Adding status === "interrupted" to the lastError clearing condition at line 997 changes behavior for all providers, not just Cursor. When a session.state.changed event transitions to "interrupted" state, lastError is now unconditionally cleared to null. Previously, the stale lastError was preserved. This can discard a meaningful error message if a provider session transitions to interrupted state after an error — the error context is lost before it can be surfaced.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 695b425. Configure here.

@juliusmarminge
Copy link
Copy Markdown
Member

thanks but the cursor team has promised they'll fix the acp for us so i wanna use that (see #1355 )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants