Skip to content

Commit 116cde0

Browse files
committed
fix: point v2 install at @next, list model params in refresh tool, fix fallback param
- README (#126): the opencode v2 install snippet used @latest, which is 0.9.0 (v1-only build, no { id, setup } default export) and reproduces "Plugin must export a default definition" on opencode 2.x. Point it at @next until 0.10.0 is promoted, with a note. - cursor_refresh_models (#119): append each model's param ids and accepted values, e.g. `grok-4.6 [effort=low|medium|high|xhigh, fast=false|true]`, so users can tell effort from reasoning_effort. Add a test. - fallback catalog: composer-2.5 exposes `fast`, not `thinking`; the stale entry produced a bogus `thinking` variant on the keyless path. - CHANGELOG: limit the connect-node note to verified facts (nothing in the repo imports it; @cursor/sdk nests its own 1.7.0) and record the above.
1 parent 2cabc4b commit 116cde0

5 files changed

Lines changed: 91 additions & 9 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,31 @@ All notable changes to this project will be documented in this file.
1212
tool-arg descriptions, were corrected to say so (wording only — no behavior
1313
change).
1414

15+
- **Fix: README v2 install snippet points at `@next` (#126).** The
16+
`plugins` example used `@latest`, which resolves to 0.9.0 — a v1-only
17+
build that opencode v2 rejects with "Plugin must export a default
18+
definition with an id and an effect or setup function". v2 support is
19+
published under the `next` dist-tag (0.10.0-next.1+) until promoted;
20+
the snippet and a note now say so.
21+
22+
- **Fix: fallback catalog's composer-2.5 param matches the live one.**
23+
The keyless/fallback entry advertised a `thinking` param (off/on) that
24+
the live catalog does not expose, yielding a bogus `thinking` variant;
25+
it now declares only `fast` (false/true), so the fallback path yields
26+
exactly the `fast` variant and `{ fast: "false" }` defaults.
27+
28+
- **Add: `cursor_refresh_models` lists param ids and values (#119).**
29+
Each model line now appends the model's advertised params as
30+
`[effort=low|medium|high|xhigh, fast=false|true]` (no suffix when the
31+
model has none), so accepted param ids and values are readable
32+
straight from the tool output.
33+
1534
- **Chore: dependency bumps (#122, #124, #125) and audit overrides.**
1635
Consolidates the three open Dependabot PRs: `@connectrpc/connect-node`
17-
2.1.2 → 2.2.0 (required at runtime by `@cursor/sdk`, not imported
18-
by the plugin itself; the 2.2.0 additions are server-side),
36+
2.1.2 → 2.2.0 (nothing in src/, scripts/, or test/ imports it, and
37+
`@cursor/sdk` declares `^1.6.1` and nests its own 1.7.0 copy, so the
38+
top-level entry — added in #31 when the SDK did not declare it — no
39+
longer looks needed; left in place as a removal candidate),
1940
`@cursor/sdk` 1.0.31 → 1.0.32, `@opencode-ai/plugin` and
2041
`@opencode-ai/sdk` 1.18.25/1.18.30 → 1.18.33 (kept on one version, one
2142
sdk copy in the tree), `@ai-sdk/provider` 3.0.15 → 3.0.18,

‎README.md‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,18 @@ opencode v2 uses a `plugins` key (plural) and loads the plugin's `setup()` entry
8181
```json
8282
{
8383
"$schema": "https://opencode.ai/config.json",
84-
"plugins": ["@stablekernel/opencode-cursor@latest"]
84+
"plugins": ["@stablekernel/opencode-cursor@next"]
8585
}
8686
```
8787

88-
Both generations load the same published package, so one install serves either.
88+
> v2 support is published under the `next` dist-tag (`0.10.0-next.1` or later)
89+
> until it is promoted to `latest`. `@latest` currently resolves to 0.9.0, a
90+
> v1-only build that opencode v2 rejects with "Plugin must export a default
91+
> definition with an id and an effect or setup function". Switch the spec back
92+
> to `@latest` once 0.10.0 is stable.
93+
94+
From 0.10.0 onward both generations load the same published package, so one
95+
install serves either (0.9.x `@latest` is v1-only).
8996
Minimum versions: v1 >= 1.18.29 (object plugin entrypoint), v2 tested against 2.0.19.
9097

9198
**Registry mirrors:** on v2 the provider package is installed at exactly the plugin's
@@ -309,8 +316,8 @@ needed.
309316
Param ids are **per model** — use the id the model actually advertises, or Cursor ignores the
310317
param and falls back to its own default (e.g. `high` effort). `grok-4.6` uses `effort`,
311318
`gpt-5.5` uses `reasoning`, `claude-opus-4-8` uses `effort` (plus a boolean `thinking`), and
312-
`composer-2.5` has only `fast`. Run `cursor_refresh_models` to list model ids, and check the
313-
model picker's variant names for the values a model accepts.
319+
`composer-2.5` has only `fast`. Run `cursor_refresh_models` to list each model id
320+
with its param ids and accepted values.
314321

315322
To set controls statically per model:
316323

‎src/fallback-models.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const FALLBACK_MODELS: ModelListItem[] = [
1313
displayName: "Composer 2.5",
1414
description: "Cursor's default agent model (fallback entry).",
1515
parameters: [
16-
{ id: "thinking", displayName: "Thinking", values: [{ value: "off" }, { value: "on" }] },
16+
{ id: "fast", displayName: "Fast", values: [{ value: "false" }, { value: "true" }] },
1717
],
1818
},
1919
{ id: "claude-opus-4-8", displayName: "Claude Opus 4.8 (via Cursor)" },

‎src/plugin/cursor-tools.ts‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { tool, type ToolContext, type ToolDefinition } from "@opencode-ai/plugin";
2+
import type { ModelListItem } from "@cursor/sdk";
23
import { rmSync } from "node:fs";
34
import semver from "semver";
45
import { runCloudAgent } from "../provider/cloud-agent.js";
@@ -262,6 +263,21 @@ export function buildCursorTools(deps: CursorToolDeps): Record<string, ToolDefin
262263
export const REFRESH_DESCRIPTION_BASE =
263264
"Refresh the live Cursor model catalog now (bypasses the cache) and report the available models. The catalog also auto-refreshes on every opencode startup; use this to pick up new models mid-session.";
264265

266+
/**
267+
* One `cursor_refresh_models` output line, with the model's advertised
268+
* params appended when it has any:
269+
* `- grok-4.6 — Grok 4.6 [effort=low|medium|high|xhigh, fast=false|true]`.
270+
* Models without parameters keep the plain `- id — name` line.
271+
*/
272+
function modelLine(m: Pick<ModelListItem, "id" | "displayName" | "parameters">): string {
273+
const params = m.parameters ?? [];
274+
if (params.length === 0) return `- ${m.id} — ${m.displayName}`;
275+
const joined = params
276+
.map((p) => `${p.id}=${(p.values ?? []).map((v) => v.value).join("|")}`)
277+
.join(", ");
278+
return `- ${m.id} — ${m.displayName} [${joined}]`;
279+
}
280+
265281
/**
266282
* Build the maintenance tools shared by v1 and v2:
267283
* - `cursor_refresh_models`: force-refresh the model catalog.
@@ -282,7 +298,7 @@ export function buildMaintenanceTools(
282298
execute: async () => {
283299
const apiKey = deps ? await deps.resolveApiKey() : undefined;
284300
const result = await discoverModels({ apiKey, forceRefresh: true });
285-
const lines = result.models.map((m) => `- ${m.id} — ${m.displayName}`);
301+
const lines = result.models.map(modelLine);
286302
const header =
287303
result.source === "live"
288304
? `Refreshed ${result.models.length} Cursor models (live):`

‎test/cursor-tools.test.ts‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import { afterEach, describe, expect, it, vi } from "vitest";
33
const runCloudAgent = vi.fn();
44
const runDelegate = vi.fn();
55
const linkDelegateSession = vi.fn();
6+
const discoverModels = vi.fn();
67

78
vi.mock("../src/provider/cloud-agent.js", () => ({ runCloudAgent }));
89
vi.mock("../src/provider/delegate.js", () => ({ runDelegate }));
910
vi.mock("../src/provider/subagent-bridge.js", () => ({ linkDelegateSession }));
11+
vi.mock("../src/model-discovery.js", () => ({ discoverModels }));
1012

11-
const { buildCursorTools } = await import("../src/plugin/cursor-tools.js");
13+
const { buildCursorTools, buildMaintenanceTools } = await import("../src/plugin/cursor-tools.js");
1214

1315
function ctx(ask: ReturnType<typeof vi.fn>) {
1416
return {
@@ -30,6 +32,7 @@ afterEach(() => {
3032
runCloudAgent.mockReset();
3133
runDelegate.mockReset();
3234
linkDelegateSession.mockReset();
35+
discoverModels.mockReset();
3336
});
3437

3538
describe("buildCursorTools", () => {
@@ -312,3 +315,38 @@ describe("buildCursorTools", () => {
312315
expect(String(out)).toContain("agent crashed");
313316
});
314317
});
318+
319+
describe("buildMaintenanceTools — cursor_refresh_models output", () => {
320+
const exec = async () => {
321+
const tools = buildMaintenanceTools();
322+
const out = (await tools.cursor_refresh_models!.execute({}, undefined as never)) as {
323+
output: string;
324+
};
325+
return out.output;
326+
};
327+
328+
it("appends [id=values] to a model with params; plain line without", async () => {
329+
discoverModels.mockResolvedValue({
330+
source: "live",
331+
models: [
332+
{
333+
id: "grok-4.6",
334+
displayName: "Grok 4.6",
335+
parameters: [
336+
{
337+
id: "effort",
338+
values: [{ value: "low" }, { value: "medium" }, { value: "high" }, { value: "xhigh" }],
339+
},
340+
{ id: "fast", values: [{ value: "false" }, { value: "true" }] },
341+
],
342+
},
343+
{ id: "plain-model", displayName: "Plain Model" },
344+
],
345+
});
346+
const output = await exec();
347+
expect(output).toContain(
348+
"- grok-4.6 — Grok 4.6 [effort=low|medium|high|xhigh, fast=false|true]",
349+
);
350+
expect(output.endsWith("- plain-model — Plain Model")).toBe(true);
351+
});
352+
});

0 commit comments

Comments
 (0)