diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index 4615c0726..e14f0d6e0 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -274,9 +274,11 @@ Gateway** needs your account + gateway ids filled into the URL. Cursor is tracked separately as an experimental adapter. `adapter: "cursor"` appears in `ocx init` and the dashboard Add Provider picker as an experimental local config entry with Cursor's static fallback model catalog metadata. When a Cursor access token is configured, opencodex uses Cursor's -live HTTP/2 transport. Its v2.7.1 fallback seed includes `gpt-5.6-sol` / `terra` / `luna` (1M context) -plus `grok-4.5` / `grok-4.5-fast` (500K); live discovery decides which remain visible for the -account. Cursor server-driven native read/write/delete/ls/grep/shell/fetch execution +live HTTP/2 transport. Its v2.7.1 fallback seed includes `gpt-5.6-sol` / `terra` / `luna` (1M context), +`grok-4.5` / `grok-4.5-fast` (500K), and `kimi-k3` (262K); live discovery decides which remain +visible for the account. Cursor serves Kimi K3 only as effort-suffixed wire ids, so +`cursor/kimi-k3` exposes a `low` / `high` / `max` ladder and defaults to `max`, matching the +model's documented API default. Cursor server-driven native read/write/delete/ls/grep/shell/fetch execution is disabled by default because it bypasses Codex's approval and sandbox path; set `unsafeAllowNativeLocalExec: true` on the `providers.cursor` object in `~/.opencodex/config.json` only for trusted local experiments (or via **Providers → Cursor → Edit JSON** in the dashboard). diff --git a/docs-site/src/content/docs/ja/guides/providers.md b/docs-site/src/content/docs/ja/guides/providers.md index ef2dd1d54..67548d50e 100644 --- a/docs-site/src/content/docs/ja/guides/providers.md +++ b/docs-site/src/content/docs/ja/guides/providers.md @@ -204,9 +204,11 @@ Gateway** は URL にアカウント + ゲートウェイ ID を埋める必要 Cursor は別の実験的アダプターとして追跡します。`adapter: "cursor"` は `ocx init` とダッシュボード Add Provider ピッカーに実験的 local config 項目として表示され、Cursor の静的フォールバックモデルカタログ メタデータを保存します。Cursor アクセストークンを設定すると opencodex は Cursor ライブ HTTP/2 トランスポートを -使います。v2.7.1 フォールバックリストには 1M コンテキストの `gpt-5.6-sol` / `terra` / `luna` と 500K コンテキストの -`grok-4.5` / `grok-4.5-fast` が含まれ、ライブ探索結果に基づき現在のアカウントに表示するモデルを -決定します。Cursor サーバーが直接送るネイティブ read/write/delete/ls/grep/shell/fetch 実行は Codex +使います。v2.7.1 フォールバックリストには 1M コンテキストの `gpt-5.6-sol` / `terra` / `luna`、500K コンテキストの +`grok-4.5` / `grok-4.5-fast`、262K コンテキストの `kimi-k3` が含まれ、ライブ探索結果に基づき現在の +アカウントに表示するモデルを決定します。Cursor は Kimi K3 を effort サフィックス付きの wire id +としてのみ提供するため、`cursor/kimi-k3` は `low` / `high` / `max` のラダーを公開し、既定値はモデル +ドキュメントの API 既定値と同じ `max` です。Cursor サーバーが直接送るネイティブ read/write/delete/ls/grep/shell/fetch 実行は Codex 承認とサンドボックス経路をバイパスするためデフォルトで無効です。信頼できるローカル実験でのみ `~/.opencodex/config.json` の `providers.cursor` に `unsafeAllowNativeLocalExec: true` を設定してください。 ダッシュボードからは **Providers → Cursor → Edit JSON** で設定できます。完全な例は diff --git a/docs-site/src/content/docs/ko/guides/providers.md b/docs-site/src/content/docs/ko/guides/providers.md index 208839d81..8cdae100a 100644 --- a/docs-site/src/content/docs/ko/guides/providers.md +++ b/docs-site/src/content/docs/ko/guides/providers.md @@ -205,9 +205,11 @@ Gateway**는 URL에 계정 + 게이트웨이 id를 채워야 합니다. Cursor는 별도의 실험적 어댑터로 추적합니다. `adapter: "cursor"`는 `ocx init`과 dashboard Add Provider picker에 실험적 local config 항목으로 표시되며, Cursor의 static fallback model catalog metadata를 저장합니다. Cursor access token이 설정되면 opencodex는 Cursor live HTTP/2 transport를 -사용합니다. v2.7.1 폴백 목록에는 1M 컨텍스트의 `gpt-5.6-sol` / `terra` / `luna`와 500K 컨텍스트의 -`grok-4.5` / `grok-4.5-fast`가 들어 있으며, 실시간 탐색 결과에 따라 현재 계정에 표시할 모델을 -결정합니다. Cursor 서버가 직접 보내는 native read/write/delete/ls/grep/shell/fetch 실행은 Codex +사용합니다. v2.7.1 폴백 목록에는 1M 컨텍스트의 `gpt-5.6-sol` / `terra` / `luna`, 500K 컨텍스트의 +`grok-4.5` / `grok-4.5-fast`, 262K 컨텍스트의 `kimi-k3`가 들어 있으며, 실시간 탐색 결과에 따라 +현재 계정에 표시할 모델을 결정합니다. Cursor는 Kimi K3를 effort 접미사가 붙은 wire id로만 +제공하므로 `cursor/kimi-k3`는 `low` / `high` / `max` 래더를 노출하고 기본값은 모델 문서의 API +기본값과 같은 `max`입니다. Cursor 서버가 직접 보내는 native read/write/delete/ls/grep/shell/fetch 실행은 Codex 승인 및 sandbox 경로를 우회하므로 기본적으로 비활성화되어 있습니다. 신뢰한 로컬 실험에서만 `~/.opencodex/config.json`의 `providers.cursor`에 `unsafeAllowNativeLocalExec: true`를 설정하세요. 대시보드에서는 **Providers → Cursor → Edit JSON**에서 설정할 수 있습니다. 전체 예시는 diff --git a/docs-site/src/content/docs/ru/guides/providers.md b/docs-site/src/content/docs/ru/guides/providers.md index d6df82506..f23e818d8 100644 --- a/docs-site/src/content/docs/ru/guides/providers.md +++ b/docs-site/src/content/docs/ru/guides/providers.md @@ -219,8 +219,11 @@ Cursor отслеживается отдельно как эксперимент `ocx init` и в селекторе Add Provider дашборда как экспериментальная запись локальной конфигурации с метаданными статического резервного каталога моделей Cursor. Когда настроен токен доступа Cursor, opencodex использует живой транспорт HTTP/2 Cursor. Его резервный список версии v2.7.1 включает -`gpt-5.6-sol` / `terra` / `luna` (контекст 1M) плюс `grok-4.5` / `grok-4.5-fast` (500K); живое -обнаружение решает, какие из них останутся видимыми для аккаунта. Управляемое сервером Cursor +`gpt-5.6-sol` / `terra` / `luna` (контекст 1M), `grok-4.5` / `grok-4.5-fast` (500K) и `kimi-k3` +(262K); живое обнаружение решает, какие из них останутся видимыми для аккаунта. Cursor отдаёт +Kimi K3 только через wire id с суффиксом усилия, поэтому `cursor/kimi-k3` предоставляет лестницу +`low` / `high` / `max` и по умолчанию использует `max` — как и задокументированное значение по +умолчанию в API модели. Управляемое сервером Cursor нативное выполнение read/write/delete/ls/grep/shell/fetch по умолчанию отключено, поскольку оно обходит путь одобрений и песочницу Codex; устанавливайте `unsafeAllowNativeLocalExec: true` в объекте `providers.cursor` файла `~/.opencodex/config.json` только для доверенных локальных diff --git a/docs-site/src/content/docs/zh-cn/guides/providers.md b/docs-site/src/content/docs/zh-cn/guides/providers.md index 357ddbcac..b749d3837 100644 --- a/docs-site/src/content/docs/zh-cn/guides/providers.md +++ b/docs-site/src/content/docs/zh-cn/guides/providers.md @@ -191,8 +191,10 @@ GPT-5.6 Sol/Terra/Luna 会预置在提供商的回退列表中,因此即使实 Cursor 作为单独的实验性 adapter 进行跟踪。`adapter: "cursor"` 会作为实验性本地配置出现在 `ocx init` 和 dashboard Add Provider picker 中,并保存 Cursor 的静态回退模型目录 metadata。配置 Cursor access token 后,opencodex 会使用 Cursor live HTTP/2 transport。v2.7.1 回退列表包含上下文为 -1M 的 `gpt-5.6-sol` / `terra` / `luna`,以及上下文为 500K 的 -`grok-4.5` / `grok-4.5-fast`;最终显示哪些模型由账号的实时发现结果决定。Cursor 服务器直接发起的 +1M 的 `gpt-5.6-sol` / `terra` / `luna`、上下文为 500K 的 `grok-4.5` / `grok-4.5-fast`,以及上下文为 +262K 的 `kimi-k3`;最终显示哪些模型由账号的实时发现结果决定。Cursor 只以带 effort 后缀的 wire id +提供 Kimi K3,因此 `cursor/kimi-k3` 暴露 `low` / `high` / `max` 阶梯,默认值为 `max`,与该模型 +文档中的 API 默认值一致。Cursor 服务器直接发起的 native read/write/delete/ls/grep/shell/fetch 执行默认禁用,因为它会绕过 Codex 的 approval 和 sandbox 路径;只有在可信本地实验中,才应在 `~/.opencodex/config.json` 的 `providers.cursor` 对象上设置 `unsafeAllowNativeLocalExec: true`,也可以在仪表盘的 **Providers → Cursor → Edit JSON** diff --git a/src/adapters/cursor/discovery.ts b/src/adapters/cursor/discovery.ts index c8ab41a7c..6e95b2821 100644 --- a/src/adapters/cursor/discovery.ts +++ b/src/adapters/cursor/discovery.ts @@ -167,7 +167,7 @@ export const CURSOR_STATIC_MODELS: readonly CursorModelInfo[] = normalizeCursorM // the request builder appends the per-model suffix (see effort-map.ts) and reasoning models // advertise effort so Codex exposes the tier picker. `supportsReasoningEffort` tracks whether the // model has *selectable effort tiers* (CURSOR_MODEL_EFFORT_TIERS), NOT merely whether it reasons: - // gemini/grok/kimi/gpt-5-mini are reasoning models in the SOT but are sent bare (no tier picker). + // gemini/grok/kimi-k2.7/gpt-5-mini are reasoning models in the SOT but are sent bare (no tier picker). ...CURSOR_ROUTER_MODEL_IDS.map(id => ({ id, contextWindow: CONTEXT_200K, supportsReasoningEffort: false })), { id: "claude-sonnet-5", contextWindow: CONTEXT_200K, supportsReasoningEffort: true }, @@ -224,6 +224,9 @@ export const CURSOR_STATIC_MODELS: readonly CursorModelInfo[] = normalizeCursorM // take PR #73's supportsReasoningEffort for glm-5.2 (its effort-map tiers landed with the PR). { id: "glm-5.2", contextWindow: CONTEXT_1M, supportsReasoningEffort: true }, { id: "kimi-k2.7-code", contextWindow: CONTEXT_262K }, + // 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 }, { id: "grok-4.5", contextWindow: 500_000, supportsReasoningEffort: true }, { id: "grok-4.5-fast", contextWindow: 500_000, supportsReasoningEffort: true }, diff --git a/src/adapters/cursor/effort-map.ts b/src/adapters/cursor/effort-map.ts index ea4edbe5e..ddb613114 100644 --- a/src/adapters/cursor/effort-map.ts +++ b/src/adapters/cursor/effort-map.ts @@ -27,6 +27,9 @@ const CURSOR_MODEL_EFFORT_TIERS: Record = { "claude-opus-5": ["low", "medium", "high", "xhigh", "max"], "claude-sonnet-5": ["low", "medium", "high", "xhigh", "max"], "glm-5.2": ["high", "max"], + // 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"], // GetUsableModels (2026-07-09) lists grok-4.5-{medium,high,xhigh} and grok-4.5-fast-{medium,high,xhigh}; // the bare "grok-4.5-fast" id was removed upstream and now returns not_found. "grok-4.5": ["medium", "high", "xhigh"], diff --git a/src/providers/registry.ts b/src/providers/registry.ts index 61c9eb058..663e8c8ec 100644 --- a/src/providers/registry.ts +++ b/src/providers/registry.ts @@ -457,6 +457,12 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [ modelContextWindows: cursorModelContextWindows(CURSOR_STATIC_MODELS), modelInputModalities: cursorModelInputModalities(CURSOR_STATIC_MODELS), modelReasoningEfforts: cursorModelReasoningEfforts(CURSOR_STATIC_MODELS), + // Kimi K3 documents `max` as its API default, and its Cursor ladder has no `medium` + // rung — so applyReasoningLevels' medium->high->first fallback would settle the catalog + // default on `high`, the picker would send `high` explicitly, and the request builder's + // no-effort fallback to `kimi-k3-max` would never be reached. Mirrors the other K3 + // routes (kimi, kimi-code, opencode-go). + modelDefaultReasoningEfforts: { "kimi-k3": "max" }, // Cursor's wire protocol never forwards image parts (request-builder emits an unsupported- // content marker), so the vision sidecar covers ALL cursor models regardless of what the // upstream model could natively do. Live-discovered models outside the static list fall back diff --git a/tests/codex-catalog.test.ts b/tests/codex-catalog.test.ts index 7cc4f1ccc..7ff52a606 100644 --- a/tests/codex-catalog.test.ts +++ b/tests/codex-catalog.test.ts @@ -696,6 +696,34 @@ describe("Google Gemini catalog metadata", () => { }); }); +describe("Cursor Kimi K3 catalog default effort", () => { + // Regression for the effort-tier ladder added with cursor/kimi-k3: the Cursor ladder is + // low/high/max with NO medium rung, so applyReasoningLevels' medium -> high -> first + // preference would settle default_reasoning_level on "high" unless the registry supplies a + // default. Kimi documents `max` as K3's API default, and a "high" default makes the picker + // send high explicitly so the request builder never falls back to kimi-k3-max. + test("keeps max as the catalog default instead of falling back to high", async () => { + const cursor = { + adapter: "cursor", + authMode: "oauth" as const, + liveModels: false, + }; + enrichProviderFromRegistry("cursor", cursor); + const models = await gatherRoutedModels({ + port: 0, + defaultProvider: "cursor", + providers: { cursor }, + }); + const entry = buildCatalogEntries(nativeTemplate(), [], models) + .find(row => row.slug === "cursor/kimi-k3"); + + expect(entry).toBeDefined(); + expect((entry?.supported_reasoning_levels as Array<{ effort: string }>).map(level => level.effort)) + .toEqual(["low", "high", "max", "ultra"]); + expect(entry?.default_reasoning_level).toBe("max"); + }); +}); + describe("configured CatalogModel displayName -> catalog display_name", () => { test("a routed CatalogModel displayName becomes the catalog display_name", () => { const model = { provider: "deepseek", id: "deepseek-v4", displayName: "DeepSeek V4", owned_by: "deepseek" }; diff --git a/tests/cursor-discovery.test.ts b/tests/cursor-discovery.test.ts index 9c6a28f92..19f88a285 100644 --- a/tests/cursor-discovery.test.ts +++ b/tests/cursor-discovery.test.ts @@ -36,6 +36,7 @@ describe("Cursor discovery metadata", () => { expect(ids).toContain("gpt-5.5"); expect(ids).toContain("glm-5.2"); expect(ids).toContain("kimi-k2.7-code"); + expect(ids).toContain("kimi-k3"); expect(ids).toContain("claude-opus-4-7-fast"); // 260709 refresh: stale ids dropped from the static seed (cursor.com docs); gpt-5.5-extra // stays — it survives the live GetUsableModels filter (004_live_snapshot.md). diff --git a/tests/cursor-effort-suffix.test.ts b/tests/cursor-effort-suffix.test.ts index b5839d0c2..423cd9919 100644 --- a/tests/cursor-effort-suffix.test.ts +++ b/tests/cursor-effort-suffix.test.ts @@ -66,6 +66,16 @@ describe("Cursor per-model reasoning-effort suffix", () => { expect(modelIdFor("cursor/grok-4.5-fast", "xhigh")).toBe("grok-4.5-fast-xhigh"); }); + 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"); + expect(modelIdFor("cursor/kimi-k3", "high")).toBe("kimi-k3-high"); + expect(modelIdFor("cursor/kimi-k3", "max")).toBe("kimi-k3-max"); + // Bare id (no requested effort) resolves to the model's top tier; upstream rejects the bare id. + expect(modelIdFor("cursor/kimi-k3")).toBe("kimi-k3-max"); + expect(cursorModelEffortLadder("kimi-k3")).toEqual(["low", "high", "max"]); + }); + test("model ladders are deduped and sorted in canonical Codex order", () => { expect(cursorModelEffortLadder("claude-opus-4-8")).toEqual(["low", "medium", "high", "xhigh", "max"]); expect(cursorModelEffortLadder("glm-5.2")).toEqual(["high", "max"]);