Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion src/adapters/cursor/discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,25 @@ export function cursorCodexToWireModelId(modelId: string): string {
return cursorWireModelSelection(modelId).modelId;
}

/**
* Synthetic ultra/big-context picker marker (devlog 260826 070). A `cursor/<base>-1m` row is a
* picker-only variant: the wire request keeps `<base>` (plus effort suffix) and turns on Cursor
* Max Mode instead. Only ids listed here are treated as synthetic — a real upstream wire id that
* happens to end in `-1m` never collides because it will not be in this set.
*/
export const CURSOR_ULTRA_1M_MODEL_IDS: ReadonlySet<string> = new Set([
"kimi-k3-1m",
]);

const CURSOR_ULTRA_1M_SUFFIX = "-1m";

/** Resolve a synthetic ultra marker id to its wire base, or undefined for ordinary ids. */
export function cursorUltraBaseModelId(modelId: string): string | undefined {
const normalized = modelId.startsWith("cursor/") ? modelId.slice("cursor/".length) : modelId;
if (!CURSOR_ULTRA_1M_MODEL_IDS.has(normalized)) return undefined;
return normalized.slice(0, -CURSOR_ULTRA_1M_SUFFIX.length);
}

/**
* Cursor-native wire models keep server-side conversation state reliably.
* External models (gpt/claude/gemini/grok families and similar) are more brittle on resumeAction.
Expand Down Expand Up @@ -215,7 +234,11 @@ export function filterCursorConfiguredModelsByLiveDiscovery<T extends { id: stri
): T[] {
return configured.filter(model =>
!CURSOR_KNOWN_UNCALLABLE_MODEL_IDS.has(model.id)
&& (isCursorRouterModelId(model.id) || isCursorModelAvailableForAccount(model.id, liveIds)),
&& (
isCursorRouterModelId(model.id)
// Synthetic ultra rows ride their base model's account availability.
|| isCursorModelAvailableForAccount(cursorUltraBaseModelId(model.id) ?? model.id, liveIds)
),
);
}

Expand Down Expand Up @@ -322,6 +345,10 @@ export const CURSOR_STATIC_MODELS: readonly CursorModelInfo[] = normalizeCursorM
// kimi-k3: cursor.com/docs/models/kimi-k3; account-verified via GetUsableModels (2026-07-28) —
// ships only as effort-suffixed kimi-k3-{low,high,max}, so the tier picker is exposed.
{ id: "kimi-k3", contextWindow: CONTEXT_262K, supportsReasoningEffort: true },
// kimi-k3-1m: synthetic ultra/Max-Mode picker variant (CURSOR_ULTRA_1M_MODEL_IDS) — wire sends
// kimi-k3-<effort> with maxMode=true; 1M context user-verified live on the Ultra plan
// (devlog 260826_cursor_responses_gap/025). inferCursorContextWindow maps "1m" ids to 1M.
{ id: "kimi-k3-1m", contextWindow: CONTEXT_1M, supportsReasoningEffort: true },
Comment on lines +348 to +351

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Document the new Cursor Max Mode model

Adding cursor/kimi-k3-1m changes the user-visible model catalog and introduces Max Mode behavior, but docs-site/src/content/docs/guides/providers.md:585-592 still describes only the 262K cursor/kimi-k3 row and its effort ladder; the translated provider guides likewise omit the new selection. Update the English guide and translations so users can distinguish the synthetic 1M row, its plan-gated behavior, and its wire mapping.

AGENTS.md reference: src/AGENTS.md:L28-L28

Useful? React with 👍 / 👎.


{ id: "grok-4.5", contextWindow: 500_000, supportsReasoningEffort: true },
{ id: "grok-4.5-fast", contextWindow: 500_000, supportsReasoningEffort: true },
Expand Down
3 changes: 3 additions & 0 deletions src/adapters/cursor/effort-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ const CURSOR_MODEL_EFFORT_TIERS: Record<string, readonly string[]> = {
// GetUsableModels (2026-07-28) lists kimi-k3 only as effort-suffixed kimi-k3-{low,high,max};
// the bare id returns not_found. Tiers mirror the native Kimi provider's K3 ladder.
"kimi-k3": ["low", "high", "max"],
// Synthetic ultra picker variant (devlog 260826 070): same tier ladder as kimi-k3; the -1m
// marker is stripped before wire-id composition, so these tiers never form a wire suffix.
"kimi-k3-1m": ["low", "high", "max"],
// Cursor renamed the Grok 4.5 slugs to cursor-grok-4.5-{low,medium,high} and
// cursor-grok-4.5-{low,medium,high}-fast. The bare Fast id returns not_found.
"grok-4.5": ["low", "medium", "high"],
Expand Down
9 changes: 7 additions & 2 deletions src/adapters/cursor/live-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export interface CursorUsableModelsOptions {
}

export type CursorUsableModelsResult =
| { ok: true; models: string[] }
| { ok: true; models: string[]; maxModeModels?: string[] }
| { ok: false; error: "auth" | "http" | "policy" | "transport" | "timeout" | "decode" | "empty" | "too_large"; detail?: string };

/** Test-only seam for management connectivity probes; production callers retain the HTTP/2 path. */
Expand Down Expand Up @@ -120,16 +120,21 @@ function decodeCursorUsableModels(bytes: Uint8Array): CursorUsableModelsResult {
// make stale configured ids such as `composer-2` look activated.
const ids: string[] = [];
const seenIds = new Set<string>();
const maxModeIds: string[] = [];
for (const model of response.models ?? []) {
const rawId = (model as { modelId?: string }).modelId;
if (typeof rawId !== "string") continue;
const id = rawId.trim();
if (!isValidModelDiscoveryModelId(id) || seenIds.has(id)) continue;
seenIds.add(id);
ids.push(id);
// Preserve Max-Mode capability for ultra/big-context auto-detection (devlog 260826 070).
if ((model as { maxMode?: boolean }).maxMode === true) maxModeIds.push(id);
if (ids.length >= CURSOR_MAX_DISCOVERED_MODELS) break;
}
return ids.length > 0 ? { ok: true, models: ids } : { ok: false, error: "empty" };
return ids.length > 0
? { ok: true, models: ids, ...(maxModeIds.length > 0 ? { maxModeModels: maxModeIds } : {}) }
: { ok: false, error: "empty" };
} catch {
return { ok: false, error: "decode", detail: "Invalid GetUsableModels protobuf response" };
}
Expand Down
7 changes: 5 additions & 2 deletions src/adapters/cursor/protobuf-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -967,12 +967,15 @@ function buildPreparedCursorRunRequest(
displayName: request.modelId,
displayNameShort: request.modelId,
aliases: [],
...(request.maxMode === true ? { maxMode: true } : {}),
}),
} : {}),
...(requestedModelParameters.length > 0 ? {
...(requestedModelParameters.length > 0 || request.maxMode === true ? {
requestedModel: create(RequestedModelSchema, {
modelId: request.modelId,
maxMode: false,
// Max Mode must be raised on BOTH RequestedModel and ModelDetails; missing either
// can invalid_argument upstream (devlog 260826 070).
maxMode: request.maxMode === true,
parameters: requestedModelParameters.map(parameter =>
create(RequestedModel_ModelParameterbytesSchema, parameter)),
}),
Expand Down
12 changes: 10 additions & 2 deletions src/adapters/cursor/request-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
import { isAllowedToolChoice, namespacedToolName, toolChoiceAliases, type OcxTool, type OcxToolChoice } from "../../types";
import type { CursorRequestMessage, CursorRequestedModelParameter, CursorRunRequest } from "./types";
import { cursorCheckpointModelAffinityId, cursorWireModelSelection, type CursorRoutingLevel } from "./discovery";
import { cursorUltraBaseModelId } from "./discovery";
import { decodeCursorCallId } from "./call-id";
import { cursorEffortSuffix, cursorRequestWireModelIdWithEffort } from "./effort-map";
import {
Expand Down Expand Up @@ -189,21 +190,27 @@ function normalizeCursorModelId(modelId: string, reasoning?: string): {
modelId: string;
requestedModelParameters?: readonly CursorRequestedModelParameter[];
routingLevel?: CursorRoutingLevel;
maxMode?: boolean;
} {
const selection = cursorWireModelSelection(modelId);
// Synthetic ultra (-1m) picker rows resolve to their wire base with Max Mode on
// (devlog 260826 070); the marker never reaches the wire.
const ultraBase = cursorUltraBaseModelId(modelId);
const selection = cursorWireModelSelection(ultraBase ?? modelId);
const maxMode = ultraBase !== undefined ? { maxMode: true } : {};
const id = selection.modelId;
const suffix = cursorEffortSuffix(id, reasoning);
if ((id === "grok-4.5-fast" || id === "grok-4.6-fast") && suffix) {
return {
...selection,
...maxMode,
modelId: id.slice(0, -"-fast".length),
requestedModelParameters: [
{ id: "effort", value: suffix },
{ id: "fast", value: "true" },
],
};
}
return { ...selection, modelId: suffix ? cursorRequestWireModelIdWithEffort(id, suffix) : id };
return { ...selection, ...maxMode, modelId: suffix ? cursorRequestWireModelIdWithEffort(id, suffix) : id };
}

function contentPartToText(part: OcxContentPart | OcxAssistantContentPart): string | undefined {
Expand Down Expand Up @@ -446,6 +453,7 @@ export function createCursorRequest(
modelId: model.modelId,
...(model.requestedModelParameters ? { requestedModelParameters: model.requestedModelParameters } : {}),
...(model.routingLevel ? { routingLevel: model.routingLevel } : {}),
...(model.maxMode ? { maxMode: true } : {}),
conversationId: resolveCursorConversationId(parsed, model.modelId, options),
system: [...(parsed.context.systemPrompt ?? []), ...(limitNote ? [limitNote] : [])],
messages,
Expand Down
6 changes: 6 additions & 0 deletions src/adapters/cursor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ export interface CursorRunRequest {
requestedModelParameters?: readonly CursorRequestedModelParameter[];
/** Cursor Router optimization parameter; valid only while modelId is the `default` wire model. */
routingLevel?: CursorRoutingLevel;
/**
* Cursor Max Mode (ultra/big-context). Set from a synthetic `-1m` picker variant; the wire
* keeps the original model id and raises RequestedModel.maxMode + ModelDetails.maxMode
* (both fields — missing either can invalid_argument upstream). Devlog 260826 070.
*/
maxMode?: boolean;
/**
* Bare API callers (no caller tools, no Codex thread identity) pay a ~10-15K input-token
* preamble because an absent AgentRunRequest.mcp_tools field makes Cursor inject its default
Expand Down
94 changes: 94 additions & 0 deletions tests/cursor-ultra-mode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { describe, expect, test } from "bun:test";
import {
CURSOR_STATIC_MODELS,
CURSOR_ULTRA_1M_MODEL_IDS,
cursorUltraBaseModelId,
filterCursorConfiguredModelsByLiveDiscovery,
} from "../src/adapters/cursor/discovery";
import { createCursorRequest } from "../src/adapters/cursor/request-builder";
import { encodeCursorRunRequest } from "../src/adapters/cursor/protobuf-request";
import { fromBinary } from "@bufbuild/protobuf";
import { AgentClientMessageSchema, type AgentRunRequest } from "../src/adapters/cursor/gen/agent_pb";
import type { OcxParsedRequest } from "../src/types/request";

function decodeRunRequest(bytes: Uint8Array): AgentRunRequest {
const msg = fromBinary(AgentClientMessageSchema, bytes);
if (msg.message.case !== "runRequest") throw new Error("expected runRequest");
return msg.message.value;
}

function parsedFor(modelId: string, reasoning?: string): OcxParsedRequest {
return {
modelId,
context: { systemPrompt: [], messages: [{ role: "user", content: "hi" }] },
options: reasoning ? { reasoning } : {},
} as OcxParsedRequest;
}

describe("cursor ultra (-1m / Max Mode) toggle (devlog 260826 070)", () => {
test("static catalog exposes the kimi-k3-1m picker row with 1M context", () => {
const row = CURSOR_STATIC_MODELS.find(model => model.id === "kimi-k3-1m");
expect(row).toBeDefined();
expect(row?.contextWindow).toBe(1_000_000);
expect(row?.supportsReasoningEffort).toBe(true);
});

test("ultra marker resolves to its wire base and never leaks", () => {
expect(cursorUltraBaseModelId("cursor/kimi-k3-1m")).toBe("kimi-k3");
expect(cursorUltraBaseModelId("kimi-k3-1m")).toBe("kimi-k3");
expect(cursorUltraBaseModelId("kimi-k3")).toBeUndefined();
expect(cursorUltraBaseModelId("claude-4-sonnet-1m")).toBeUndefined();
});

test("kimi-k3-1m + max resolves to wire kimi-k3-max with maxMode on the request", () => {
const request = createCursorRequest(parsedFor("cursor/kimi-k3-1m", "max"));
expect(request.modelId).toBe("kimi-k3-max");
expect(request.maxMode).toBe(true);
});

test("plain kimi-k3 stays maxMode-off", () => {
const request = createCursorRequest(parsedFor("cursor/kimi-k3", "max"));
expect(request.modelId).toBe("kimi-k3-max");
expect(request.maxMode).toBeUndefined();
});

test("wire raises maxMode on BOTH RequestedModel and ModelDetails", () => {
const bytes = encodeCursorRunRequest({
modelId: "kimi-k3-max",
maxMode: true,
conversationId: "c1",
system: [],
messages: [{ role: "user", content: "hi" }],
});
const decoded = decodeRunRequest(bytes);
expect(decoded.requestedModel?.maxMode).toBe(true);
expect(decoded.requestedModel?.modelId).toBe("kimi-k3-max");
expect(decoded.modelDetails?.maxMode).toBe(true);
});

test("non-ultra requests keep maxMode=false wire behavior", () => {
const bytes = encodeCursorRunRequest({
modelId: "kimi-k3-max",
conversationId: "c1",
system: [],
messages: [{ role: "user", content: "hi" }],
});
const decoded = decodeRunRequest(bytes);
expect(decoded.requestedModel).toBeUndefined();
// ModelDetails.maxMode is proto-optional; absent (undefined) means off.
expect(decoded.modelDetails?.maxMode ?? false).toBe(false);
});

test("account filter admits the synthetic row through its base availability", () => {
const configured = [{ id: "kimi-k3-1m" }, { id: "kimi-k3" }];
const live = ["kimi-k3-high", "kimi-k3-max"];
const filtered = filterCursorConfiguredModelsByLiveDiscovery(configured, live);
expect(filtered.map(model => model.id)).toEqual(["kimi-k3-1m", "kimi-k3"]);
});

test("ultra id set stays narrow and every entry has a static row", () => {
for (const id of CURSOR_ULTRA_1M_MODEL_IDS) {
expect(CURSOR_STATIC_MODELS.some(model => model.id === id)).toBe(true);
}
});
});
Loading