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
2 changes: 2 additions & 0 deletions docs-site/src/content/docs/ja/reference/adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ filtered incomplete になります。実際のツール呼び出しを伴わな

- 通常の fetch/parse 経路の代わりに `runTurn` を使います。リクエスト、サーバーイベント、ツール引数、使用量 checkpoint、クライアントレスポンスは `cursor/gen/agent_pb.ts` の `@bufbuild/protobuf` スキーマでエンコードしたのち Connect メッセージとして framing します。
- content-addressed blob で対話状態を再生し、サーバーツール呼び出しを Codex に再マッピングします。protobuf の `GetUsableModels` RPC でリアルタイム Cursor モデルを探し、run リクエストが wire に commit される前だけリトライします。
- `cursor/grok-4.5-fast` は選択可能なモデルとして維持しつつ、Cursor には正規の `grok-4.5`
モデルを送信し、個別の `effort` および `fast=true` 値は `requested_model.parameters` に格納します。
- Cursor ネイティブのローカルファイルシステム/shell/network 実行はデフォルトで拒否します。明示的な `mcpServers` と `desktopExecutor` 統合はそれぞれ別の opt-in です。`unsafeAllowNativeLocalExec` はより広い組み込み executor を有効にし、Codex の承認/サンドボックスルールを迂回します。

## `azure-openai`(別名: `azure`)
Expand Down
2 changes: 2 additions & 0 deletions docs-site/src/content/docs/ko/reference/adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ commentary로 유지하고 비공개 완료 툴을 한 번 검증합니다.
- content-addressed blob으로 대화 상태를 재생하고 서버 툴 호출을 Codex에 다시 매핑합니다. protobuf
`GetUsableModels` RPC로 실시간 Cursor 모델을 찾으며, run 요청이 wire에 commit되기 전까지만
재시도합니다.
- `cursor/grok-4.5-fast`는 선택 가능한 모델로 유지하되, Cursor에는 정식 `grok-4.5` 모델을 보내고
별도의 `effort`, `fast=true` 값은 `requested_model.parameters`에 담습니다.
- Cursor 네이티브 로컬 파일시스템/shell/network 실행은 기본적으로 거부합니다. 명시적인
`mcpServers`와 `desktopExecutor` 통합은 각각 별도 opt-in입니다. `unsafeAllowNativeLocalExec`은
더 넓은 내장 executor를 켜며 Codex 승인/샌드박스 규칙을 우회합니다.
Expand Down
2 changes: 2 additions & 0 deletions docs-site/src/content/docs/reference/adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ 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.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- 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
Expand Down
2 changes: 2 additions & 0 deletions docs-site/src/content/docs/ru/reference/adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ authorization.
- Воспроизводит состояние диалога через content-addressed blob'ы, отображает серверные вызовы
инструментов обратно в Codex, обнаруживает актуальные модели Cursor через protobuf RPC
`GetUsableModels` и повторяет попытки только до того, как run-запрос зафиксирован на wire.
- Сохраняет `cursor/grok-4.5-fast` доступной для выбора, но отправляет Cursor каноническую модель
`grok-4.5`, помещая отдельные значения `effort` и `fast=true` в `requested_model.parameters`.
- Нативное для Cursor локальное выполнение операций с файловой системой/shell/сетью по умолчанию
запрещено. Явные интеграции `mcpServers` и `desktopExecutor` включаются отдельно;
`unsafeAllowNativeLocalExec` включает более широкий встроенный executor и обходит семантику
Expand Down
2 changes: 2 additions & 0 deletions docs-site/src/content/docs/zh-cn/reference/adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ Kiro 的 assistant 文本本身没有可靠的回合结束标记,但终止的
Connect message。
- 经 content-addressed blob 重放对话状态,把 server tool call 映射回 Codex,用 protobuf
`GetUsableModels` RPC 发现实时 Cursor 模型,并且只在 run request 尚未 commit 到 wire 前重试。
- 保留 `cursor/grok-4.5-fast` 作为可选模型,但向 Cursor 发送规范的 `grok-4.5` 模型,并将独立的
`effort` 和 `fast=true` 值放入 `requested_model.parameters`。
- Cursor 原生本地 filesystem/shell/network 执行默认被拒绝。显式 `mcpServers` 与
`desktopExecutor` 集成分别需要 opt-in;`unsafeAllowNativeLocalExec` 会启用更广泛的内置
executor,并绕过 Codex 审批和 sandbox 语义。
Expand Down
5 changes: 3 additions & 2 deletions src/adapters/cursor/effort-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,9 @@ export function cursorModelHasEffortTiers(baseModelId: string): boolean {
}

/**
* Compose a Cursor wire id from a Codex-facing base id and effort tier.
* Fast variants put the mode after the effort; other models use the ordinary `{base}-{effort}` form.
* Compose Cursor's flattened model id from a Codex-facing base id and effort tier. Discovery uses
* this for the ids returned by GetUsableModels. Parameterized Grok Fast requests bypass the flat id
* and send the base model plus requested_model parameters instead.
*/
export function cursorWireModelIdWithEffort(baseModelId: string, effortSuffix: string): string {
if (baseModelId.endsWith("-fast")) {
Expand Down
35 changes: 20 additions & 15 deletions src/adapters/cursor/protobuf-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,11 @@ function buildPreparedCursorRunRequest(
tools: request.tools?.length ?? 0,
});

const requestedModelParameters = [
...(request.requestedModelParameters ?? []),
...(request.routingLevel ? [{ id: CURSOR_ROUTING_LEVEL_PARAMETER_ID, value: request.routingLevel }] : []),
];
const hasExplicitModelParameters = (request.requestedModelParameters?.length ?? 0) > 0;
const runRequest = create(AgentRunRequestSchema, {
conversationId: request.conversationId,
conversationState: create(ConversationStateStructureSchema, {
Expand All @@ -637,24 +642,24 @@ function buildPreparedCursorRunRequest(
readPaths: [],
}),
action,
modelDetails: create(ModelDetailsSchema, {
modelId: request.modelId,
displayModelId: request.modelId,
displayName: request.modelId,
displayNameShort: request.modelId,
aliases: [],
}),
// requested_model is currently a Cursor Router-only surface. External model clients still
// send model_details alone; sending both makes external workers reach stepCompleted and then
// reject the turn with invalid_argument.
...(request.routingLevel ? {
// Explicit model-picker parameters follow current Cursor clients and use requested_model alone.
// Keep legacy model_details for flat model ids and the already-live Router path; sending both for
// a parameterized external model can resolve conflicting selections and end in invalid_argument.
...(!hasExplicitModelParameters ? {
modelDetails: create(ModelDetailsSchema, {
modelId: request.modelId,
displayModelId: request.modelId,
displayName: request.modelId,
displayNameShort: request.modelId,
aliases: [],
}),
} : {}),
...(requestedModelParameters.length > 0 ? {
requestedModel: create(RequestedModelSchema, {
modelId: request.modelId,
maxMode: false,
parameters: [create(RequestedModel_ModelParameterbytesSchema, {
id: CURSOR_ROUTING_LEVEL_PARAMETER_ID,
value: request.routingLevel,
})],
parameters: requestedModelParameters.map(parameter =>
create(RequestedModel_ModelParameterbytesSchema, parameter)),
}),
} : {}),
// Mirror the client (Responses) tool definitions into the top-level AgentRunRequest.mcp_tools
Expand Down
28 changes: 21 additions & 7 deletions src/adapters/cursor/request-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
OcxToolResultMessage,
} from "../../types";
import { isAllowedToolChoice, namespacedToolName, toolChoiceAliases, type OcxTool, type OcxToolChoice } from "../../types";
import type { CursorRequestMessage, CursorRunRequest } from "./types";
import type { CursorRequestMessage, CursorRequestedModelParameter, CursorRunRequest } from "./types";
import { cursorWireModelSelection, type CursorRoutingLevel } from "./discovery";
import { cursorEffortSuffix, cursorWireModelIdWithEffort } from "./effort-map";
import {
Expand Down Expand Up @@ -117,16 +117,29 @@ function catalogLimitNote(kept: readonly OcxTool[], omitted: readonly OcxTool[])
}

/**
* Resolve a `cursor/<model>` selection + Codex reasoning effort to the actual Cursor model id. Cursor
* encodes the effort as a per-model suffix (`claude-4.6-opus-high`); `cursorEffortSuffix` picks the
* right tier for that specific model (literal pass-through, with rank clamp fallback) or
* `undefined` for non-reasoning models like `composer-2.5`. A fully-qualified id (one that isn't a
* known effort base) passes through unchanged.
* Resolve a `cursor/<model>` selection + Codex reasoning effort to Cursor's requested model shape.
* Most models encode effort in a flat id (`claude-4.6-opus-high`). Grok 4.5 Fast is parameterized
* instead: current Cursor clients send the `grok-4.5` base id plus `effort` and `fast` parameters.
* A fully-qualified id (one that is not a known effort base) passes through unchanged.
*/
function normalizeCursorModelId(modelId: string, reasoning?: string): { modelId: string; routingLevel?: CursorRoutingLevel } {
function normalizeCursorModelId(modelId: string, reasoning?: string): {
modelId: string;
requestedModelParameters?: readonly CursorRequestedModelParameter[];
routingLevel?: CursorRoutingLevel;
} {
const selection = cursorWireModelSelection(modelId);
const id = selection.modelId;
const suffix = cursorEffortSuffix(id, reasoning);
if (id === "grok-4.5-fast" && suffix) {
return {
...selection,
modelId: "grok-4.5",
requestedModelParameters: [
{ id: "effort", value: suffix },
{ id: "fast", value: "true" },
],
};
}
return { ...selection, modelId: suffix ? cursorWireModelIdWithEffort(id, suffix) : id };
}

Expand Down Expand Up @@ -241,6 +254,7 @@ export function createCursorRequest(
const model = normalizeCursorModelId(parsed.modelId, parsed.options.reasoning);
return {
modelId: model.modelId,
...(model.requestedModelParameters ? { requestedModelParameters: model.requestedModelParameters } : {}),
...(model.routingLevel ? { routingLevel: model.routingLevel } : {}),
conversationId: resolveCursorConversationId(parsed, model.modelId, options),
system: [...(parsed.context.systemPrompt ?? []), ...(limitNote ? [limitNote] : [])],
Expand Down
7 changes: 7 additions & 0 deletions src/adapters/cursor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@ import type { OcxUsage } from "../../types";
import type { OcxMessage, OcxRequestOptions, OcxTool } from "../../types";
import type { CursorRoutingLevel } from "./discovery";

export interface CursorRequestedModelParameter {
id: string;
value: string;
}

export interface CursorRunRequest {
modelId: string;
/** Cursor model-picker parameters encoded through AgentRunRequest.requested_model. */
requestedModelParameters?: readonly CursorRequestedModelParameter[];
/** Cursor Router optimization parameter; valid only while modelId is the `default` wire model. */
routingLevel?: CursorRoutingLevel;
conversationId: string;
Expand Down
8 changes: 7 additions & 1 deletion structure/04_transports-and-sidecars.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ replays are explicit and receive the same repair.
These compatibility guards are covered by focused tests and should stay close to the adapters that
need them.

## Cursor Router optimization levels
## Cursor parameterized models

Cursor Router's parameterized `default` model is represented in Codex by four catalog rows:
`cursor/auto` preserves Cursor's team/account default, while `cursor/auto-cost`,
Expand All @@ -277,6 +277,12 @@ All four route to the `default` Cursor wire model. Explicit variants additionall
parameterized-model channel used by current Cursor clients. Router rows are static capabilities and
must survive a live `GetUsableModels` response that omits `default`.

`cursor/grok-4.5-fast` is also a stable Codex-facing row, but current Cursor clients do not request
it as a flat model slug. OpenCodex sends `grok-4.5` through `requested_model` with separate `effort`
and `fast=true` parameters, leaving legacy `model_details` unset for that parameterized external
selection. Live discovery still recognizes Cursor's flattened `cursor-grok-4.5-{effort}-fast`
variants, plus the older `grok-4.5-fast-{effort}` ordering, as availability evidence only.

## Cursor active-context usage

Cursor's `conversationCheckpointUpdate.tokenDetails.usedTokens` is treated as the authoritative
Expand Down
25 changes: 25 additions & 0 deletions tests/cursor-blob.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,31 @@ describe("Cursor blob handshake", () => {
expect(run?.requestedModel).toBeUndefined();
});

test("encodes Grok Fast through requested_model parameters without legacy model_details", () => {
const bytes = encodeCursorRunRequest({
modelId: "grok-4.5",
requestedModelParameters: [
{ id: "effort", value: "high" },
{ id: "fast", value: "true" },
],
conversationId: "c1",
system: [],
messages: [{ role: "user", content: "hi" }],
});
const msg = fromBinary(AgentClientMessageSchema, bytes);
const run = msg.message.case === "runRequest" ? msg.message.value : undefined;

expect(run?.modelDetails).toBeUndefined();
expect(run?.requestedModel).toMatchObject({
modelId: "grok-4.5",
maxMode: false,
parameters: [
{ id: "effort", value: "high" },
{ id: "fast", value: "true" },
],
});
});

test("adds Cursor exact-tool guidance to system prompt blobs when tools are advertised", () => {
const bytes = encodeCursorRunRequest({
modelId: "claude-4.6-sonnet",
Expand Down
42 changes: 36 additions & 6 deletions tests/cursor-effort-suffix.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ function modelIdFor(modelId: string, reasoning?: string): string {
return createCursorRequest(parsed).modelId;
}

function selectionFor(modelId: string, reasoning?: string) {
const parsed: OcxParsedRequest = {
modelId,
context: { messages: [{ role: "user", content: "hi", timestamp: 1 }] },
stream: false,
options: reasoning ? { reasoning } : {},
};
const request = createCursorRequest(parsed);
return { modelId: request.modelId, parameters: request.requestedModelParameters };
}

describe("Cursor per-model reasoning-effort suffix", () => {
test("literal requested efforts pass through when the model supports that tier", () => {
expect(modelIdFor("cursor/claude-4.6-opus", "high")).toBe("claude-4.6-opus-high");
Expand Down Expand Up @@ -60,18 +71,37 @@ describe("Cursor per-model reasoning-effort suffix", () => {
expect(modelIdFor("cursor/glm-5.2", "max")).toBe("glm-5.2-max");
});

test("grok-4.5 uses current low/medium/high tiers and trailing Fast wire ids", () => {
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-fast", "low")).toBe("grok-4.5-low-fast");
expect(modelIdFor("cursor/grok-4.5-fast", "medium")).toBe("grok-4.5-medium-fast");
expect(modelIdFor("cursor/grok-4.5-fast", "high")).toBe("grok-4.5-high-fast");
expect(selectionFor("cursor/grok-4.5", "high")).toEqual({
modelId: "grok-4.5-high",
parameters: undefined,
});
expect(selectionFor("cursor/grok-4.5-fast", "low")).toEqual({
modelId: "grok-4.5",
parameters: [{ id: "effort", value: "low" }, { id: "fast", value: "true" }],
});
expect(selectionFor("cursor/grok-4.5-fast", "medium")).toEqual({
modelId: "grok-4.5",
parameters: [{ id: "effort", value: "medium" }, { id: "fast", value: "true" }],
});
expect(selectionFor("cursor/grok-4.5-fast", "high")).toEqual({
modelId: "grok-4.5",
parameters: [{ id: "effort", value: "high" }, { id: "fast", value: "true" }],
});
// Codex-only upper tiers and an omitted effort clamp to Cursor's current top tier.
expect(modelIdFor("cursor/grok-4.5-fast", "xhigh")).toBe("grok-4.5-high-fast");
expect(modelIdFor("cursor/grok-4.5-fast")).toBe("grok-4.5-high-fast");
expect(selectionFor("cursor/grok-4.5-fast", "xhigh")).toEqual({
modelId: "grok-4.5",
parameters: [{ id: "effort", value: "high" }, { id: "fast", value: "true" }],
});
expect(selectionFor("cursor/grok-4.5-fast")).toEqual({
modelId: "grok-4.5",
parameters: [{ id: "effort", value: "high" }, { id: "fast", value: "true" }],
});
expect(cursorModelEffortLadder("grok-4.5")).toEqual(["low", "medium", "high"]);
expect(cursorModelEffortLadder("grok-4.5-fast")).toEqual(["low", "medium", "high"]);
});
Expand Down
Loading