diff --git a/docs-site/src/content/docs/reference/adapters.md b/docs-site/src/content/docs/reference/adapters.md index 970557956..dd6224d6c 100644 --- a/docs-site/src/content/docs/reference/adapters.md +++ b/docs-site/src/content/docs/reference/adapters.md @@ -181,8 +181,9 @@ advertised effort control on those models as proof of upstream-native reasoning - Exposes Cursor Router as `cursor/auto` plus explicit `cursor/auto-cost`, `cursor/auto-balance`, and `cursor/auto-intelligence` entries. Explicit levels are encoded in `requested_model.parameters` while the legacy `cursor/auto` entry retains the account/team default. -- Keeps `cursor/grok-4.5-fast` as a selectable model while sending Cursor's canonical `grok-4.5` - model with separate `effort` and `fast=true` parameters. +- Sends regular `cursor/grok-4.5` tiers with Cursor's exact live-discovery wire ids + (`cursor-grok-4.5-low`, `-medium`, or `-high`). Keeps `cursor/grok-4.5-fast` selectable while + sending the canonical `grok-4.5` model with separate `effort` and `fast=true` parameters. - Cursor-native local filesystem/shell/network execution is denied by default. Explicit `mcpServers` and `desktopExecutor` integrations have separate opt-ins; `nativeLocalExec: "on"` enables the broader built-in executor and bypasses Codex approval/sandbox semantics, and legacy diff --git a/src/adapters/cursor/effort-map.ts b/src/adapters/cursor/effort-map.ts index a7c0b76e4..1e937b310 100644 --- a/src/adapters/cursor/effort-map.ts +++ b/src/adapters/cursor/effort-map.ts @@ -126,3 +126,14 @@ export function cursorWireModelIdWithEffort(baseModelId: string, effortSuffix: s } return `${baseModelId}-${effortSuffix}`; } + +/** + * Compose the exact flattened id sent by AgentService/Run. Discovery normalizes Cursor's optional + * `cursor-` prefix only for catalog matching, but regular Grok 4.5 requests require that prefix on + * the wire. Keep this separate from {@link cursorWireModelIdWithEffort} so discovery can continue + * comparing canonical, prefix-free ids. Grok Fast uses requested_model parameters instead. + */ +export function cursorRequestWireModelIdWithEffort(baseModelId: string, effortSuffix: string): string { + const flattened = cursorWireModelIdWithEffort(baseModelId, effortSuffix); + return baseModelId === "grok-4.5" ? `cursor-${flattened}` : flattened; +} diff --git a/src/adapters/cursor/request-builder.ts b/src/adapters/cursor/request-builder.ts index e550c6910..9c7e91e64 100644 --- a/src/adapters/cursor/request-builder.ts +++ b/src/adapters/cursor/request-builder.ts @@ -10,7 +10,7 @@ import type { import { isAllowedToolChoice, namespacedToolName, toolChoiceAliases, type OcxTool, type OcxToolChoice } from "../../types"; import type { CursorRequestMessage, CursorRequestedModelParameter, CursorRunRequest } from "./types"; import { cursorWireModelSelection, type CursorRoutingLevel } from "./discovery"; -import { cursorEffortSuffix, cursorWireModelIdWithEffort } from "./effort-map"; +import { cursorEffortSuffix, cursorRequestWireModelIdWithEffort } from "./effort-map"; import { cursorMcpToolEncodedSize, cursorMcpToolsEncodedSize, @@ -140,7 +140,7 @@ function normalizeCursorModelId(modelId: string, reasoning?: string): { ], }; } - return { ...selection, modelId: suffix ? cursorWireModelIdWithEffort(id, suffix) : id }; + return { ...selection, modelId: suffix ? cursorRequestWireModelIdWithEffort(id, suffix) : id }; } function contentPartToText(part: OcxContentPart | OcxAssistantContentPart): string | undefined { diff --git a/tests/cursor-effort-suffix.test.ts b/tests/cursor-effort-suffix.test.ts index 218a06eef..2d4c627f0 100644 --- a/tests/cursor-effort-suffix.test.ts +++ b/tests/cursor-effort-suffix.test.ts @@ -3,6 +3,15 @@ import { createCursorRequest } from "../src/adapters/cursor/request-builder"; import { cursorEffortSuffix, cursorModelEffortLadder } from "../src/adapters/cursor/effort-map"; import type { OcxParsedRequest } from "../src/types"; +// Static fixture recorded from Cursor GetUsableModels on 2026-08-06. This pins the +// exact wire ids observed during the incident; live availability normalization is +// covered separately in cursor-discovery.test.ts. +const RECORDED_CURSOR_GROK_45_DISCOVERY_IDS = [ + "cursor-grok-4.5-low", + "cursor-grok-4.5-medium", + "cursor-grok-4.5-high", +] as const; + function modelIdFor(modelId: string, reasoning?: string): string { const parsed: OcxParsedRequest = { modelId, @@ -84,13 +93,13 @@ describe("Cursor per-model reasoning-effort suffix", () => { }); test("grok-4.5 uses current tiers and sends Fast as a separate model parameter", () => { - expect(modelIdFor("cursor/grok-4.5", "low")).toBe("grok-4.5-low"); - expect(modelIdFor("cursor/grok-4.5", "medium")).toBe("grok-4.5-medium"); - expect(modelIdFor("cursor/grok-4.5", "high")).toBe("grok-4.5-high"); - expect(modelIdFor("cursor/grok-4.5", "xhigh")).toBe("grok-4.5-high"); - expect(modelIdFor("cursor/grok-4.5")).toBe("grok-4.5-high"); + expect(modelIdFor("cursor/grok-4.5", "low")).toBe("cursor-grok-4.5-low"); + expect(modelIdFor("cursor/grok-4.5", "medium")).toBe("cursor-grok-4.5-medium"); + expect(modelIdFor("cursor/grok-4.5", "high")).toBe("cursor-grok-4.5-high"); + expect(modelIdFor("cursor/grok-4.5", "xhigh")).toBe("cursor-grok-4.5-high"); + expect(modelIdFor("cursor/grok-4.5")).toBe("cursor-grok-4.5-high"); expect(selectionFor("cursor/grok-4.5", "high")).toEqual({ - modelId: "grok-4.5-high", + modelId: "cursor-grok-4.5-high", parameters: undefined, }); expect(selectionFor("cursor/grok-4.5-fast", "low")).toEqual({ @@ -118,6 +127,14 @@ describe("Cursor per-model reasoning-effort suffix", () => { expect(cursorModelEffortLadder("grok-4.5-fast")).toEqual(["low", "medium", "high"]); }); + test("regular grok-4.5 request ids match the recorded discovery fixture", () => { + for (const effort of ["low", "medium", "high"] as const) { + const requestModelId = modelIdFor("cursor/grok-4.5", effort); + expect(requestModelId).toBe(`cursor-grok-4.5-${effort}`); + expect(RECORDED_CURSOR_GROK_45_DISCOVERY_IDS).toContain(requestModelId); + } + }); + test("kimi-k3 maps to its live effort-suffixed variants", () => { expect(modelIdFor("cursor/kimi-k3", "low")).toBe("kimi-k3-low"); expect(modelIdFor("cursor/kimi-k3", "medium")).toBe("kimi-k3-high");