From e20f5f34ea3e65645803908869a153830c597e89 Mon Sep 17 00:00:00 2001 From: Dominatorrr <276074461+Pllutonyy@users.noreply.github.com> Date: Fri, 18 Sep 2026 17:29:31 +0300 Subject: [PATCH] fix(cursor): preserve native Claude effort model IDs before executor dispatch (#12838) --- docs/providers/CURSOR-API-KEY-AND-CLI.md | 16 ++++++- .../handlers/chatCore/claudeEffortVariant.ts | 11 +++++ .../chatcore-claude-effort-variant.test.ts | 42 +++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/docs/providers/CURSOR-API-KEY-AND-CLI.md b/docs/providers/CURSOR-API-KEY-AND-CLI.md index 53c0a85dfd..8449882947 100644 --- a/docs/providers/CURSOR-API-KEY-AND-CLI.md +++ b/docs/providers/CURSOR-API-KEY-AND-CLI.md @@ -1,7 +1,7 @@ --- title: "Cursor API provider and the Cursor CLI passthrough" -version: 3.8.50 -lastUpdated: 2026-08-19 +version: 3.8.51 +lastUpdated: 2026-09-05 --- # Cursor API provider and the Cursor CLI passthrough @@ -66,6 +66,18 @@ Notes: - `POST /api/providers/{id}/refresh-cursor` is for the `cursor` IDE provider only; `cursor-api` connections have no IDE session to renew. +## Native model IDs and effort + +For `cursor` / `cu` and `cursor-api` / `cua`, the shared Claude-effort normalizer +leaves the requested model ID intact. Cursor can advertise a suffix such as +`-low` as part of a real model ID, rather than as an OmniRoute effort alias. +The Cursor executor preserves an exact live-catalog match; when there is no +match, its existing model resolver owns suffix-to-parameter fallback. + +This does not change effort normalization for direct Claude, Claude-compatible, +or Vertex routes. Availability still depends on the selected Cursor account's +catalog and entitlement. + ## Cursor CLI passthrough Route: `src/app/api/cursor-cli/[...path]/route.ts` → diff --git a/open-sse/handlers/chatCore/claudeEffortVariant.ts b/open-sse/handlers/chatCore/claudeEffortVariant.ts index dac50233ad..2da837a4f6 100644 --- a/open-sse/handlers/chatCore/claudeEffortVariant.ts +++ b/open-sse/handlers/chatCore/claudeEffortVariant.ts @@ -38,6 +38,17 @@ export function applyClaudeEffortVariant(opts: { sourceFormat: string; }): { effectiveModel: string; log: string | null } { const { provider, body, sourceFormat } = opts; + // Cursor advertises native effort-suffixed ids. Its executor resolves exact + // live-catalog ids before applying its own suffix fallback; stripping here + // destroys that information before the executor can see it. + if ( + provider === "cursor" || + provider === "cu" || + provider === "cursor-api" || + provider === "cua" + ) { + return { effectiveModel: opts.effectiveModel, log: null }; + } let effectiveModel = opts.effectiveModel; let log: string | null = null; diff --git a/tests/unit/chatcore-claude-effort-variant.test.ts b/tests/unit/chatcore-claude-effort-variant.test.ts index 0630a3c7bd..4cda1b2a47 100644 --- a/tests/unit/chatcore-claude-effort-variant.test.ts +++ b/tests/unit/chatcore-claude-effort-variant.test.ts @@ -10,6 +10,48 @@ import { test } from "node:test"; import assert from "node:assert/strict"; import { applyClaudeEffortVariant } from "../../open-sse/handlers/chatCore/claudeEffortVariant.ts"; import { FORMATS } from "../../open-sse/translator/formats.ts"; +import { resolveRequestedModel } from "../../open-sse/utils/cursorAgentProtobuf.ts"; + +for (const provider of ["cursor", "cu", "cursor-api", "cua"]) { + for (const sourceFormat of [FORMATS.OPENAI, FORMATS.CLAUDE, FORMATS.OPENAI_RESPONSES]) { + test(`${provider}/${sourceFormat}: preserves native Cursor Claude model ids before executor resolution`, () => { + for (const model of [ + "claude-fable-5-1-low", + "claude-fable-5-1-thinking-low", + "claude-opus-5-low", + ]) { + const body = { model, messages: [] }; + const result = applyClaudeEffortVariant({ + provider, + effectiveModel: model, + body, + sourceFormat, + }); + assert.deepEqual(result, { effectiveModel: model, log: null }); + assert.deepEqual(body, { model, messages: [] }); + assert.deepEqual( + resolveRequestedModel(result.effectiveModel, { liveCatalogIds: new Set([model]) }), + { modelId: model, parameters: [] } + ); + } + }); + } +} + +test("Cursor preserves explicit client effort while its encoder owns non-catalog suffix fallback", () => { + const body = { model: "claude-opus-5-low", reasoning_effort: "none", messages: [] }; + const result = applyClaudeEffortVariant({ + provider: "cursor", + effectiveModel: body.model, + body, + sourceFormat: FORMATS.OPENAI, + }); + assert.deepEqual(body, { model: "claude-opus-5-low", reasoning_effort: "none", messages: [] }); + assert.deepEqual(resolveRequestedModel(result.effectiveModel, { liveCatalogIds: new Set() }), { + modelId: "claude-opus-5", + parameters: [{ id: "effort", value: "low" }], + }); +}); test("claude provider + effort suffix → strips to base, mutates body model + reasoning_effort, returns log", () => { const body: Record = { model: "claude-sonnet-4-high", messages: [] };