@stablekernel/opencode-cursor 0.1.0-rc.1 → 0.1.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/{chunk-YYO6O43T.js → chunk-D4YQ7ZEM.js} +135 -18
- package/dist/chunk-D4YQ7ZEM.js.map +1 -0
- package/dist/plugin/index.js +34 -17
- package/dist/plugin/index.js.map +1 -1
- package/dist/provider/index.js +12 -4
- package/dist/provider/index.js.map +1 -1
- package/package.json +5 -1
- package/dist/chunk-YYO6O43T.js.map +0 -1
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ opencode loads two things from this one package:
|
|
|
52
52
|
|
|
53
53
|
| opencode concept | What it loads | Export |
|
|
54
54
|
| --- | --- | --- |
|
|
55
|
-
| Plugin (`plugin` config) | auth + provider registration + dynamic model listing + a refresh tool | `@stablekernel/opencode-cursor
|
|
55
|
+
| Plugin (`plugin` config) | auth + provider registration + dynamic model listing + a refresh tool | `@stablekernel/opencode-cursor` (resolved via the package's `./server` export) |
|
|
56
56
|
| Provider (`provider.cursor.npm`) | a Vercel AI SDK `LanguageModelV3` that drives a local Cursor agent | `@stablekernel/opencode-cursor` (`createCursor`) |
|
|
57
57
|
|
|
58
58
|
The plugin's `config` hook registers `provider.cursor` (pointing `npm` at this package) and seeds
|
|
@@ -71,7 +71,7 @@ Add the plugin to your `opencode.json` (project or global):
|
|
|
71
71
|
```json
|
|
72
72
|
{
|
|
73
73
|
"$schema": "https://opencode.ai/config.json",
|
|
74
|
-
"plugin": ["@stablekernel/opencode-cursor
|
|
74
|
+
"plugin": ["@stablekernel/opencode-cursor"]
|
|
75
75
|
}
|
|
76
76
|
```
|
|
77
77
|
|
|
@@ -157,28 +157,139 @@ function resolveControls(modelId, staticControls, providerOptions) {
|
|
|
157
157
|
return { mode, modelSelection: buildModelSelection(modelId, params) };
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
// src/
|
|
160
|
+
// src/native-binding.ts
|
|
161
|
+
import { execSync, spawn } from "child_process";
|
|
162
|
+
import { existsSync, readdirSync, statSync } from "fs";
|
|
163
|
+
import { createRequire } from "module";
|
|
164
|
+
import { dirname, join } from "path";
|
|
165
|
+
var BINDING_ROOTS = ["build", "lib/binding", "compiled"];
|
|
166
|
+
function hasNodeFile(dir, depth) {
|
|
167
|
+
if (depth < 0) return false;
|
|
168
|
+
let entries;
|
|
169
|
+
try {
|
|
170
|
+
entries = readdirSync(dir);
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
for (const entry of entries) {
|
|
175
|
+
const path = join(dir, entry);
|
|
176
|
+
if (entry.endsWith(".node")) {
|
|
177
|
+
try {
|
|
178
|
+
if (statSync(path).isFile()) return true;
|
|
179
|
+
} catch {
|
|
180
|
+
}
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
if (statSync(path).isDirectory() && hasNodeFile(path, depth - 1)) return true;
|
|
185
|
+
} catch {
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
function hasSqliteBinding(sqliteDir) {
|
|
191
|
+
return BINDING_ROOTS.some((root) => hasNodeFile(join(sqliteDir, root), 3));
|
|
192
|
+
}
|
|
193
|
+
function resolveSqliteDir() {
|
|
194
|
+
const req = createRequire(import.meta.url);
|
|
195
|
+
try {
|
|
196
|
+
const sdkPkg = req.resolve("@cursor/sdk/package.json");
|
|
197
|
+
return dirname(createRequire(sdkPkg).resolve("sqlite3/package.json"));
|
|
198
|
+
} catch {
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
return dirname(req.resolve("sqlite3/package.json"));
|
|
202
|
+
} catch {
|
|
203
|
+
return void 0;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function detectNodeExecutable() {
|
|
207
|
+
const isBun = typeof globalThis.Bun !== "undefined";
|
|
208
|
+
if (!isBun) return process.execPath;
|
|
209
|
+
try {
|
|
210
|
+
const out = execSync(process.platform === "win32" ? "where node" : "command -v node", {
|
|
211
|
+
encoding: "utf8",
|
|
212
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
213
|
+
}).trim();
|
|
214
|
+
return out.split("\n")[0] || process.execPath;
|
|
215
|
+
} catch {
|
|
216
|
+
return process.execPath;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async function runPrebuildInstall(sqliteDir) {
|
|
220
|
+
let bin;
|
|
221
|
+
try {
|
|
222
|
+
const req = createRequire(join(sqliteDir, "package.json"));
|
|
223
|
+
const pkgPath = req.resolve("prebuild-install/package.json");
|
|
224
|
+
const pkg = await import(pkgPath, { with: { type: "json" } });
|
|
225
|
+
const binField = pkg.default.bin;
|
|
226
|
+
const rel = typeof binField === "string" ? binField : binField?.["prebuild-install"];
|
|
227
|
+
if (!rel) return false;
|
|
228
|
+
bin = join(dirname(pkgPath), rel);
|
|
229
|
+
} catch {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
if (!existsSync(bin)) return false;
|
|
233
|
+
return new Promise((resolve) => {
|
|
234
|
+
const child = spawn(detectNodeExecutable(), [bin, "-r", "napi"], {
|
|
235
|
+
cwd: sqliteDir,
|
|
236
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
237
|
+
});
|
|
238
|
+
let stderr = "";
|
|
239
|
+
child.stderr?.on("data", (chunk) => {
|
|
240
|
+
stderr += chunk.toString();
|
|
241
|
+
});
|
|
242
|
+
child.on("error", () => resolve(false));
|
|
243
|
+
child.on("exit", (code) => {
|
|
244
|
+
if (code !== 0 && stderr && process.env["OPENCODE_CURSOR_DEBUG"]) {
|
|
245
|
+
console.error(`[opencode-cursor] prebuild-install stderr: ${stderr.trim()}`);
|
|
246
|
+
}
|
|
247
|
+
resolve(code === 0);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
161
251
|
var cached;
|
|
252
|
+
function ensureSqliteBinding(options = {}) {
|
|
253
|
+
cached ??= (async () => {
|
|
254
|
+
const log = options.log ?? ((message) => console.error(message));
|
|
255
|
+
const sqliteDir = options.sqliteDir ?? resolveSqliteDir();
|
|
256
|
+
if (!sqliteDir || !existsSync(join(sqliteDir, "package.json"))) {
|
|
257
|
+
return "not-found";
|
|
258
|
+
}
|
|
259
|
+
if (hasSqliteBinding(sqliteDir)) return "present";
|
|
260
|
+
const run = options.run ?? runPrebuildInstall;
|
|
261
|
+
const ok = await run(sqliteDir).catch(() => false);
|
|
262
|
+
if (ok && hasSqliteBinding(sqliteDir)) return "repaired";
|
|
263
|
+
log(
|
|
264
|
+
`[opencode-cursor] sqlite3 native binding is missing in ${sqliteDir} and automatic repair failed. @cursor/sdk will not load. Fix manually with: cd ${sqliteDir} && npx prebuild-install -r napi (or: npm rebuild sqlite3)`
|
|
265
|
+
);
|
|
266
|
+
return "failed";
|
|
267
|
+
})();
|
|
268
|
+
return cached;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/cursor-runtime.ts
|
|
272
|
+
var cached2;
|
|
162
273
|
async function loadCursorSdk() {
|
|
163
|
-
if (!
|
|
164
|
-
|
|
165
|
-
|
|
274
|
+
if (!cached2) {
|
|
275
|
+
cached2 = ensureSqliteBinding().then(() => import("@cursor/sdk")).catch((err) => {
|
|
276
|
+
cached2 = void 0;
|
|
166
277
|
const detail = err instanceof Error ? err.message : String(err);
|
|
167
278
|
throw new Error(
|
|
168
279
|
`[opencode-cursor] Failed to load "@cursor/sdk". Make sure it is installed (\`npm install @cursor/sdk\`). Original error: ${detail}`
|
|
169
280
|
);
|
|
170
281
|
});
|
|
171
282
|
}
|
|
172
|
-
return
|
|
283
|
+
return cached2;
|
|
173
284
|
}
|
|
174
285
|
|
|
175
286
|
// src/provider/agent-backend.ts
|
|
176
|
-
import { execSync } from "child_process";
|
|
177
|
-
import { existsSync } from "fs";
|
|
287
|
+
import { execSync as execSync2 } from "child_process";
|
|
288
|
+
import { existsSync as existsSync2 } from "fs";
|
|
178
289
|
import { fileURLToPath } from "url";
|
|
179
290
|
|
|
180
291
|
// src/provider/sidecar-client.ts
|
|
181
|
-
import { spawn } from "child_process";
|
|
292
|
+
import { spawn as spawn2 } from "child_process";
|
|
182
293
|
import { createInterface } from "readline";
|
|
183
294
|
function reviveError(error) {
|
|
184
295
|
const e = error ?? {};
|
|
@@ -200,7 +311,7 @@ var SidecarClient = class {
|
|
|
200
311
|
ensureChild() {
|
|
201
312
|
if (this.disposed) throw new Error("cursor sidecar client disposed");
|
|
202
313
|
if (this.child) return this.child;
|
|
203
|
-
const child =
|
|
314
|
+
const child = spawn2(this.options.nodePath ?? "node", [this.options.scriptPath], {
|
|
204
315
|
stdio: ["pipe", "pipe", "pipe"],
|
|
205
316
|
env: { ...process.env, ...this.options.env }
|
|
206
317
|
});
|
|
@@ -377,7 +488,7 @@ function resolveBackendKind(env) {
|
|
|
377
488
|
}
|
|
378
489
|
function detectNode() {
|
|
379
490
|
try {
|
|
380
|
-
const out =
|
|
491
|
+
const out = execSync2(process.platform === "win32" ? "where node" : "command -v node", {
|
|
381
492
|
encoding: "utf8",
|
|
382
493
|
stdio: ["ignore", "pipe", "ignore"]
|
|
383
494
|
}).trim();
|
|
@@ -415,7 +526,7 @@ function resolveSidecarScript() {
|
|
|
415
526
|
];
|
|
416
527
|
for (const candidate of candidates) {
|
|
417
528
|
const path = fileURLToPath(new URL(candidate, import.meta.url));
|
|
418
|
-
if (
|
|
529
|
+
if (existsSync2(path)) return path;
|
|
419
530
|
}
|
|
420
531
|
return void 0;
|
|
421
532
|
}
|
|
@@ -423,13 +534,19 @@ function sidecarBackend(nodePath, scriptPath) {
|
|
|
423
534
|
const client = new SidecarClient({ scriptPath, nodePath });
|
|
424
535
|
return {
|
|
425
536
|
kind: "sidecar",
|
|
426
|
-
createAgent: (options) =>
|
|
427
|
-
|
|
537
|
+
createAgent: async (options) => {
|
|
538
|
+
await ensureSqliteBinding();
|
|
539
|
+
return client.createAgent(options);
|
|
540
|
+
},
|
|
541
|
+
resumeAgent: async (agentId, options) => {
|
|
542
|
+
await ensureSqliteBinding();
|
|
543
|
+
return client.resumeAgent(agentId, options);
|
|
544
|
+
}
|
|
428
545
|
};
|
|
429
546
|
}
|
|
430
|
-
var
|
|
547
|
+
var cached3;
|
|
431
548
|
function loadAgentBackend() {
|
|
432
|
-
if (!
|
|
549
|
+
if (!cached3) {
|
|
433
550
|
const env = detectEnvironment();
|
|
434
551
|
const kind = resolveBackendKind(env);
|
|
435
552
|
const scriptPath = kind === "sidecar" ? resolveSidecarScript() : void 0;
|
|
@@ -440,9 +557,9 @@ function loadAgentBackend() {
|
|
|
440
557
|
`[opencode-cursor] Running under Bun without a usable Node sidecar (node: ${env.nodePath ?? "not found"}, script: ${scriptPath ?? "not found"}): Cursor native tool calls may fail (Bun node:http2 incompatibility). Install Node.js to enable the sidecar, or set OPENCODE_CURSOR_SIDECAR=0 to silence this warning.`
|
|
441
558
|
);
|
|
442
559
|
}
|
|
443
|
-
|
|
560
|
+
cached3 = kind === "sidecar" && env.nodePath && scriptPath ? sidecarBackend(env.nodePath, scriptPath) : inProcessBackend();
|
|
444
561
|
}
|
|
445
|
-
return
|
|
562
|
+
return cached3;
|
|
446
563
|
}
|
|
447
564
|
|
|
448
565
|
// src/provider/session-pool.ts
|
|
@@ -499,4 +616,4 @@ export {
|
|
|
499
616
|
loadCursorSdk,
|
|
500
617
|
acquireAgent
|
|
501
618
|
};
|
|
502
|
-
//# sourceMappingURL=chunk-
|
|
619
|
+
//# sourceMappingURL=chunk-D4YQ7ZEM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api-key.ts","../src/provider/agent-events.ts","../src/provider/controls.ts","../src/native-binding.ts","../src/cursor-runtime.ts","../src/provider/agent-backend.ts","../src/provider/sidecar-client.ts","../src/provider/session-pool.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\n\n/** Environment variable the Cursor SDK itself reads as a fallback. */\nexport const CURSOR_API_KEY_ENV_VAR = \"CURSOR_API_KEY\";\n\n/**\n * Values that are *not* real keys but rather instructions to read the key from\n * the environment. opencode config commonly stores literal `{env:...}` style\n * placeholders, and users sometimes paste the variable name itself.\n */\nconst PLACEHOLDERS = new Set<string>([\n CURSOR_API_KEY_ENV_VAR,\n `$${CURSOR_API_KEY_ENV_VAR}`,\n `\\${${CURSOR_API_KEY_ENV_VAR}}`,\n]);\n\n/**\n * Resolve a usable Cursor API key.\n *\n * Resolution order: an explicit, non-placeholder candidate (e.g. from opencode\n * auth storage or provider options) wins; otherwise fall back to the\n * `CURSOR_API_KEY` environment variable. Returns `undefined` when no key is\n * available so callers can present a clear \"needs auth\" path.\n *\n * The key is never logged or persisted by this module.\n */\nexport function resolveCursorApiKey(candidate?: string | null): string | undefined {\n const trimmed = candidate?.trim();\n if (trimmed && !PLACEHOLDERS.has(trimmed)) return trimmed;\n const fromEnv = process.env[CURSOR_API_KEY_ENV_VAR]?.trim();\n return fromEnv ? fromEnv : undefined;\n}\n\n/**\n * Produce a short, non-reversible fingerprint of an API key. Used purely to key\n * the on-disk model cache so the cache invalidates when the key changes. The\n * raw key is never written to disk.\n */\nexport function fingerprintApiKey(apiKey: string): string {\n return createHash(\"sha256\").update(apiKey).digest(\"hex\").slice(0, 16);\n}\n","import type { AgentModeOption, SDKUserMessage } from \"@cursor/sdk\";\nimport type { AgentLike, AgentRunLike } from \"./agent-backend.js\";\n\n/** Token usage as reported by Cursor's `turn-ended` update. */\nexport interface CursorUsage {\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n}\n\n/** Normalized events bridged from the Cursor SDK's push callbacks. */\nexport type CursorEvent =\n | { type: \"text-delta\"; text: string }\n | { type: \"reasoning-delta\"; text: string }\n | { type: \"tool-call\"; id: string; name: string; input: unknown }\n | { type: \"tool-result\"; id: string; name: string; result: unknown; isError: boolean }\n | { type: \"usage\"; usage: CursorUsage }\n | { type: \"finish\"; text?: string };\n\nexport interface StreamAgentTurnOptions {\n mode: AgentModeOption;\n abortSignal?: AbortSignal;\n}\n\n/**\n * Human-readable name for a Cursor tool call. Most Cursor tools carry their\n * name in `toolCall.type` (shell/read/edit/…), but an MCP tool call has\n * `type: \"mcp\"` with the real tool in `args.toolName` (and server in\n * `args.providerIdentifier`) — surface that instead of the literal \"mcp\".\n */\nfunction toolDisplayName(toolCall: ({ type?: string } & Record<string, any>) | undefined): string {\n if (!toolCall) return \"tool\";\n if (toolCall.type === \"mcp\") {\n const name = toolCall.args?.toolName;\n const server = toolCall.args?.providerIdentifier;\n if (name) return server ? `${server}/${name}` : String(name);\n return \"mcp\";\n }\n return toolCall.type ?? \"tool\";\n}\n\n/**\n * Stream a single turn on an already-acquired Cursor agent and yield normalized\n * events. The agent's lifecycle (create/resume/close) is owned by the caller\n * (see session-pool.ts) so it can be reused across turns. The SDK streams via\n * `onDelta` callbacks; we bridge those into a pull-based async generator so both\n * `doStream` and `doGenerate` can consume them.\n */\nexport async function* streamAgentTurn(\n agent: AgentLike,\n message: SDKUserMessage,\n options: StreamAgentTurnOptions,\n): AsyncGenerator<CursorEvent> {\n const queue: CursorEvent[] = [];\n let wake: (() => void) | undefined;\n let finished = false;\n let failure: unknown;\n\n // Opt-in stderr tracing of what the live agent emits (set OPENCODE_CURSOR_DEBUG=1).\n const debug = process.env.OPENCODE_CURSOR_DEBUG === \"1\";\n const counts: Record<string, number> = {};\n\n const push = (event: CursorEvent) => {\n queue.push(event);\n wake?.();\n wake = undefined;\n };\n\n const onDelta = ({ update }: { update: { type: string } & Record<string, any> }) => {\n if (debug) counts[update.type] = (counts[update.type] ?? 0) + 1;\n switch (update.type) {\n case \"text-delta\":\n push({ type: \"text-delta\", text: update.text });\n break;\n case \"thinking-delta\":\n push({ type: \"reasoning-delta\", text: update.text });\n break;\n case \"tool-call-started\":\n push({\n type: \"tool-call\",\n id: String(update.callId),\n name: toolDisplayName(update.toolCall),\n input: update.toolCall?.args ?? {},\n });\n break;\n case \"tool-call-completed\": {\n const tool = update.toolCall ?? {};\n const result = tool.result;\n // MCP failures often arrive as {status:\"success\", value:{isError:true}}\n // (the MCP-protocol error flag), not as a top-level status error.\n const mcpError = tool.type === \"mcp\" && result?.value?.isError === true;\n push({\n type: \"tool-result\",\n id: String(update.callId),\n name: toolDisplayName(tool),\n result: result ?? null,\n isError: result?.status === \"error\" || mcpError,\n });\n break;\n }\n case \"turn-ended\":\n if (update.usage) push({ type: \"usage\", usage: update.usage as CursorUsage });\n break;\n }\n };\n\n const runHolder: { run?: AgentRunLike } = {};\n const onAbort = () => {\n void Promise.resolve(runHolder.run?.cancel()).catch(() => {});\n };\n options.abortSignal?.addEventListener(\"abort\", onAbort);\n\n // A previous opencode/CLI crash (or a second instance racing on the same\n // agent store) can leave a persisted run wedged; the SDK then rejects new\n // sends with AgentBusyError. Retry once with the SDK's documented recovery\n // path (local.force expires the wedged run) instead of failing the turn.\n const sendTurn = async (): Promise<AgentRunLike> => {\n try {\n return await agent.send(message, { mode: options.mode, onDelta });\n } catch (err) {\n if (err instanceof Error && err.name === \"AgentBusyError\") {\n if (debug) console.error(\"[cursor:debug] agent busy; retrying send with local.force\");\n return agent.send(message, { mode: options.mode, onDelta, local: { force: true } });\n }\n throw err;\n }\n };\n\n // Kick off the turn. Resolve text from run.wait() for models that don't emit\n // incremental text deltas.\n void sendTurn()\n .then(async (run) => {\n runHolder.run = run;\n const result = await run.wait();\n if (debug) {\n console.error(\n `[cursor:debug] updates=${JSON.stringify(counts)} status=${result.status} resultLen=${(result.result ?? \"\").length}`,\n );\n }\n if (result.status === \"error\") {\n // Surface the failure instead of finishing silently — a silent stop\n // leaves opencode showing dangling tool calls with no explanation.\n throw new Error(\n `Cursor run ended with status \"error\"${result.result ? `: ${result.result}` : \"\"}`,\n );\n }\n // A cancelled run finishes without fabricating final text.\n push({ type: \"finish\", ...(result.status === \"cancelled\" ? {} : { text: result.result }) });\n })\n .catch((err) => {\n failure = err;\n if (debug) console.error(`[cursor:debug] send failed: ${err instanceof Error ? err.message : String(err)}`);\n })\n .finally(() => {\n finished = true;\n wake?.();\n wake = undefined;\n });\n\n try {\n while (true) {\n if (queue.length > 0) {\n yield queue.shift()!;\n continue;\n }\n if (finished) break;\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n }\n // Drain anything queued right before completion.\n while (queue.length > 0) yield queue.shift()!;\n if (failure) throw failure;\n } finally {\n options.abortSignal?.removeEventListener(\"abort\", onAbort);\n }\n}\n","import type { AgentModeOption, ModelSelection } from \"@cursor/sdk\";\n\n/** Per-model static control defaults (from provider/model config options). */\nexport interface StaticControls {\n mode: AgentModeOption;\n /** Default Cursor model params (id -> value), e.g. { thinking: \"high\" }. */\n params?: Record<string, string>;\n}\n\nexport interface ResolvedControls {\n mode: AgentModeOption;\n modelSelection: ModelSelection;\n}\n\n/**\n * Build a Cursor `ModelSelection` from a model id and an optional map of model\n * params (e.g. `{ thinking: \"high\" }`). Shared by the provider control\n * resolution and the cloud/delegate tools so param handling stays consistent.\n */\nexport function buildModelSelection(\n modelId: string,\n params?: Record<string, string>,\n): ModelSelection {\n const paramList = Object.entries(params ?? {}).map(([id, value]) => ({ id, value }));\n return paramList.length > 0 ? { id: modelId, params: paramList } : { id: modelId };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isMode(value: unknown): value is AgentModeOption {\n return value === \"agent\" || value === \"plan\";\n}\n\n/**\n * Resolve the per-turn Cursor controls from static config plus opencode's\n * per-request `providerOptions.cursor` (which carries merged model `options` and\n * the selected model `variant`). Per-request values win over static defaults.\n *\n * Recognized keys in `providerOptions.cursor`:\n * - `mode`: \"agent\" | \"plan\"\n * - `params`: Record<string,string> of Cursor model params (e.g. { thinking: \"high\" })\n * - `thinking`: string convenience, mapped to the `thinking` param if not already set\n */\nexport function resolveControls(\n modelId: string,\n staticControls: StaticControls,\n providerOptions: Record<string, unknown> | undefined,\n): ResolvedControls {\n const po = providerOptions ?? {};\n\n const mode: AgentModeOption = isMode(po[\"mode\"]) ? po[\"mode\"] : staticControls.mode;\n\n const params: Record<string, string> = { ...(staticControls.params ?? {}) };\n if (isRecord(po[\"params\"])) {\n for (const [key, value] of Object.entries(po[\"params\"])) {\n if (value != null) params[key] = String(value);\n }\n }\n if (typeof po[\"thinking\"] === \"string\" && params[\"thinking\"] === undefined) {\n params[\"thinking\"] = po[\"thinking\"];\n }\n\n return { mode, modelSelection: buildModelSelection(modelId, params) };\n}\n","/**\n * Self-heal for sqlite3's native binding.\n *\n * `@cursor/sdk` depends on `sqlite3` (a native addon). opencode installs\n * plugin packages with Bun, which does not run sqlite3's `install` lifecycle\n * script (`prebuild-install -r napi || node-gyp rebuild`), so the installed\n * tree has **no** `node_sqlite3.node` binary and the SDK crashes at import\n * with \"Could not locate the bindings file\".\n *\n * Before loading the SDK (in-process or via the Node sidecar) we check for a\n * binding and, when it is missing, run sqlite3's own `prebuild-install -r napi`\n * to fetch the prebuilt NAPI binary (ABI-portable across Node versions, also\n * loadable by Bun). Failures degrade to a clear warning; the SDK import then\n * surfaces its own error.\n */\nimport { execSync, spawn } from \"node:child_process\";\nimport { existsSync, readdirSync, statSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\n\nexport type EnsureResult = \"present\" | \"repaired\" | \"failed\" | \"not-found\";\n\nexport interface EnsureOptions {\n /** Override the sqlite3 package directory (tests). */\n sqliteDir?: string;\n /** Override the repair runner (tests). Returns true when the command succeeded. */\n run?: (sqliteDir: string) => Promise<boolean>;\n /** Override the warning sink (tests). */\n log?: (message: string) => void;\n}\n\n/** Directories (relative to the sqlite3 package root) that may hold the binding. */\nconst BINDING_ROOTS = [\"build\", \"lib/binding\", \"compiled\"];\n\nfunction hasNodeFile(dir: string, depth: number): boolean {\n if (depth < 0) return false;\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return false;\n }\n for (const entry of entries) {\n const path = join(dir, entry);\n if (entry.endsWith(\".node\")) {\n try {\n if (statSync(path).isFile()) return true;\n } catch {\n // ignore unreadable entries\n }\n continue;\n }\n try {\n if (statSync(path).isDirectory() && hasNodeFile(path, depth - 1)) return true;\n } catch {\n // ignore unreadable entries\n }\n }\n return false;\n}\n\n/** True when the sqlite3 package dir contains a compiled `.node` binding. */\nexport function hasSqliteBinding(sqliteDir: string): boolean {\n return BINDING_ROOTS.some((root) => hasNodeFile(join(sqliteDir, root), 3));\n}\n\n/**\n * Locate the sqlite3 package directory that `@cursor/sdk` will load, walking\n * the same resolution chain (our module -> @cursor/sdk -> sqlite3).\n */\nexport function resolveSqliteDir(): string | undefined {\n const req = createRequire(import.meta.url);\n try {\n const sdkPkg = req.resolve(\"@cursor/sdk/package.json\");\n return dirname(createRequire(sdkPkg).resolve(\"sqlite3/package.json\"));\n } catch {\n // fall through: try resolving sqlite3 directly (hoisted installs)\n }\n try {\n return dirname(req.resolve(\"sqlite3/package.json\"));\n } catch {\n return undefined;\n }\n}\n\nfunction detectNodeExecutable(): string {\n const isBun = typeof (globalThis as { Bun?: unknown }).Bun !== \"undefined\";\n if (!isBun) return process.execPath;\n // Under Bun prefer a real Node (matches the sidecar runtime); prebuild-install\n // itself is plain JS, so Bun works as a last resort.\n try {\n const out = execSync(process.platform === \"win32\" ? \"where node\" : \"command -v node\", {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n return out.split(\"\\n\")[0] || process.execPath;\n } catch {\n return process.execPath;\n }\n}\n\n/** Default repair: run sqlite3's own `prebuild-install -r napi` in its package dir. */\nasync function runPrebuildInstall(sqliteDir: string): Promise<boolean> {\n let bin: string;\n try {\n const req = createRequire(join(sqliteDir, \"package.json\"));\n const pkgPath = req.resolve(\"prebuild-install/package.json\");\n const pkg = (await import(pkgPath, { with: { type: \"json\" } })) as {\n default: { bin?: string | Record<string, string> };\n };\n const binField = pkg.default.bin;\n const rel = typeof binField === \"string\" ? binField : binField?.[\"prebuild-install\"];\n if (!rel) return false;\n bin = join(dirname(pkgPath), rel);\n } catch {\n return false;\n }\n if (!existsSync(bin)) return false;\n\n return new Promise<boolean>((resolve) => {\n const child = spawn(detectNodeExecutable(), [bin, \"-r\", \"napi\"], {\n cwd: sqliteDir,\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n });\n let stderr = \"\";\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n child.on(\"error\", () => resolve(false));\n child.on(\"exit\", (code) => {\n if (code !== 0 && stderr && process.env[\"OPENCODE_CURSOR_DEBUG\"]) {\n console.error(`[opencode-cursor] prebuild-install stderr: ${stderr.trim()}`);\n }\n resolve(code === 0);\n });\n });\n}\n\nlet cached: Promise<EnsureResult> | undefined;\n\n/**\n * Ensure the sqlite3 native binding exists, repairing it once per process if\n * needed. Never throws; \"failed\"/\"not-found\" outcomes warn and let the SDK\n * import surface its own error.\n */\nexport function ensureSqliteBinding(options: EnsureOptions = {}): Promise<EnsureResult> {\n cached ??= (async () => {\n const log = options.log ?? ((message: string) => console.error(message));\n const sqliteDir = options.sqliteDir ?? resolveSqliteDir();\n if (!sqliteDir || !existsSync(join(sqliteDir, \"package.json\"))) {\n return \"not-found\";\n }\n if (hasSqliteBinding(sqliteDir)) return \"present\";\n\n const run = options.run ?? runPrebuildInstall;\n const ok = await run(sqliteDir).catch(() => false);\n if (ok && hasSqliteBinding(sqliteDir)) return \"repaired\";\n\n log(\n `[opencode-cursor] sqlite3 native binding is missing in ${sqliteDir} and automatic ` +\n `repair failed. @cursor/sdk will not load. Fix manually with: ` +\n `cd ${sqliteDir} && npx prebuild-install -r napi (or: npm rebuild sqlite3)`,\n );\n return \"failed\";\n })();\n return cached;\n}\n\n/** Test hook. */\nexport function resetNativeBinding(): void {\n cached = undefined;\n}\n","/**\n * Lazy loader for the official Cursor SDK (`@cursor/sdk`).\n *\n * The SDK is heavy and only needed once a Cursor model is actually used or\n * models are discovered, so it is imported on demand. A failed import (e.g. the\n * dependency is missing) degrades gracefully into a clear error instead of\n * crashing opencode at startup.\n */\nimport { ensureSqliteBinding } from \"./native-binding.js\";\n\nexport type CursorSdkModule = typeof import(\"@cursor/sdk\");\n\nlet cached: Promise<CursorSdkModule> | undefined;\n\nexport async function loadCursorSdk(): Promise<CursorSdkModule> {\n if (!cached) {\n // @cursor/sdk eagerly requires sqlite3 (native addon); opencode's Bun\n // install skips its build script, so repair the binding first if missing.\n cached = ensureSqliteBinding()\n .then(() => import(\"@cursor/sdk\"))\n .catch((err: unknown) => {\n // Allow a later retry if the failure was transient.\n cached = undefined;\n const detail = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[opencode-cursor] Failed to load \"@cursor/sdk\". Make sure it is installed ` +\n `(\\`npm install @cursor/sdk\\`). Original error: ${detail}`,\n );\n });\n }\n return cached;\n}\n","/**\n * Selects where Cursor agents run:\n *\n * - \"in-process\": straight through `@cursor/sdk` in this process (Node — the\n * normal path for tests, scripts, and any non-Bun host).\n * - \"sidecar\": a spawned Node child hosting the SDK (Bun — opencode's runtime —\n * has a `node:http2` bug that kills Cursor's streaming RPC with\n * NGHTTP2_FRAME_SIZE_ERROR, losing tool-completion updates; see\n * src/sidecar/agent-host.mjs).\n *\n * Override with OPENCODE_CURSOR_SIDECAR=1/0 (force on/off).\n */\nimport { execSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { loadCursorSdk } from \"../cursor-runtime.js\";\nimport { ensureSqliteBinding } from \"../native-binding.js\";\nimport { SidecarClient, type AgentLike } from \"./sidecar-client.js\";\n\nexport type { AgentLike, AgentRunLike, AgentSendOptions } from \"./sidecar-client.js\";\n\nexport type BackendKind = \"in-process\" | \"sidecar\";\n\nexport interface AgentBackend {\n kind: BackendKind;\n createAgent(options: unknown): Promise<AgentLike>;\n resumeAgent(agentId: string, options: unknown): Promise<AgentLike>;\n}\n\nexport interface BackendEnvironment {\n isBun: boolean;\n /** Resolved node executable, or undefined when not on PATH. */\n nodePath: string | undefined;\n}\n\n/** Pure selection logic (unit-testable without spawning anything). */\nexport function resolveBackendKind(env: BackendEnvironment): BackendKind {\n const override = process.env[\"OPENCODE_CURSOR_SIDECAR\"];\n if (override === \"0\" || override === \"false\") return \"in-process\";\n if (override === \"1\" || override === \"true\") return env.nodePath ? \"sidecar\" : \"in-process\";\n return env.isBun && env.nodePath ? \"sidecar\" : \"in-process\";\n}\n\nfunction detectNode(): string | undefined {\n try {\n const out = execSync(process.platform === \"win32\" ? \"where node\" : \"command -v node\", {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n return out.split(\"\\n\")[0] || undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction detectEnvironment(): BackendEnvironment {\n const isBun = typeof (globalThis as { Bun?: unknown }).Bun !== \"undefined\";\n // Only pay the PATH lookup when the answer can matter.\n const needsNode = isBun || process.env[\"OPENCODE_CURSOR_SIDECAR\"] === \"1\";\n return { isBun, nodePath: needsNode ? detectNode() : process.execPath };\n}\n\nfunction inProcessBackend(): AgentBackend {\n return {\n kind: \"in-process\",\n createAgent: async (options) => {\n const { Agent } = await loadCursorSdk();\n return (await Agent.create(options as never)) as unknown as AgentLike;\n },\n resumeAgent: async (agentId, options) => {\n const { Agent } = await loadCursorSdk();\n return (await Agent.resume(agentId, options as never)) as unknown as AgentLike;\n },\n };\n}\n\n/**\n * Locate the sidecar script across layouts: tsup may place this module in\n * dist/provider/index.js or hoist it into a root-level dist/chunk-*.js, and in\n * dev/tests it runs straight from src/. Try each known relative position.\n */\nexport function resolveSidecarScript(): string | undefined {\n const candidates = [\n \"./sidecar/agent-host.js\", // importer is a chunk at dist root\n \"../sidecar/agent-host.js\", // importer is dist/provider/index.js\n \"../sidecar/agent-host.mjs\", // importer is src/provider/*.ts (dev/tests)\n ];\n for (const candidate of candidates) {\n const path = fileURLToPath(new URL(candidate, import.meta.url));\n if (existsSync(path)) return path;\n }\n return undefined;\n}\n\nfunction sidecarBackend(nodePath: string, scriptPath: string): AgentBackend {\n const client = new SidecarClient({ scriptPath, nodePath });\n // The sidecar imports @cursor/sdk (which eagerly requires sqlite3's native\n // binding) in the child process; repair the binding before first use.\n return {\n kind: \"sidecar\",\n createAgent: async (options) => {\n await ensureSqliteBinding();\n return client.createAgent(options);\n },\n resumeAgent: async (agentId, options) => {\n await ensureSqliteBinding();\n return client.resumeAgent(agentId, options);\n },\n };\n}\n\nlet cached: AgentBackend | undefined;\n\n/** Resolve (and cache) the agent backend for this process. */\nexport function loadAgentBackend(): AgentBackend {\n if (!cached) {\n const env = detectEnvironment();\n const kind = resolveBackendKind(env);\n const scriptPath = kind === \"sidecar\" ? resolveSidecarScript() : undefined;\n // A user who explicitly opted out (OPENCODE_CURSOR_SIDECAR=0/false) has\n // accepted the in-process behavior and should not be warned.\n const override = process.env[\"OPENCODE_CURSOR_SIDECAR\"];\n const optedOut = override === \"0\" || override === \"false\";\n if (env.isBun && !optedOut && (kind === \"in-process\" || !scriptPath)) {\n console.error(\n \"[opencode-cursor] Running under Bun without a usable Node sidecar \" +\n `(node: ${env.nodePath ?? \"not found\"}, script: ${scriptPath ?? \"not found\"}): ` +\n \"Cursor native tool calls may fail (Bun node:http2 incompatibility). \" +\n \"Install Node.js to enable the sidecar, or set OPENCODE_CURSOR_SIDECAR=0 \" +\n \"to silence this warning.\",\n );\n }\n cached =\n kind === \"sidecar\" && env.nodePath && scriptPath\n ? sidecarBackend(env.nodePath, scriptPath)\n : inProcessBackend();\n }\n return cached;\n}\n\n/** Test hook. */\nexport function resetAgentBackend(): void {\n cached = undefined;\n}\n","/**\n * Client half of the Node sidecar (see src/sidecar/agent-host.mjs for the\n * protocol and the why). Spawns one Node child per client and multiplexes\n * agent create/resume/send/cancel/close requests over JSON-lines stdio,\n * exposing agents through the same minimal surface the provider already\n * consumes ({@link AgentLike}), so session-pool/agent-events need no\n * sidecar-specific logic.\n */\nimport { spawn, type ChildProcessByStdio } from \"node:child_process\";\nimport { createInterface, type Interface } from \"node:readline\";\nimport type { Readable, Writable } from \"node:stream\";\n\n/** Minimal run surface the provider consumes (subset of the SDK's Run). */\nexport interface AgentRunLike {\n wait(): Promise<{ status: string; result?: string }>;\n cancel(): void | Promise<void>;\n}\n\nexport interface AgentSendOptions {\n mode?: string;\n onDelta?: (input: { update: Record<string, unknown> & { type: string } }) => void;\n local?: { force?: boolean };\n}\n\n/** Minimal agent surface the provider consumes (subset of the SDK's SDKAgent). */\nexport interface AgentLike {\n agentId: string;\n send(message: unknown, options?: AgentSendOptions): Promise<AgentRunLike>;\n close(): void;\n}\n\nexport interface SidecarClientOptions {\n /** Path to the agent-host script. */\n scriptPath: string;\n /** Node executable; default \"node\" from PATH. */\n nodePath?: string;\n /** Extra environment for the child (merged over process.env). */\n env?: Record<string, string>;\n /** Mirror child stderr to this process (debug aid). */\n debug?: boolean;\n}\n\ninterface Pending {\n resolve: (msg: Record<string, unknown>) => void;\n reject: (err: Error) => void;\n /** Streaming hooks for \"send\" requests. */\n onUpdate?: (update: Record<string, unknown> & { type: string }) => void;\n onResult?: (result: { status: string; result?: string }) => void;\n onStreamError?: (err: Error) => void;\n}\n\nfunction reviveError(error: unknown): Error {\n const e = (error ?? {}) as { name?: string; message?: string };\n const err = new Error(e.message ?? \"sidecar error\");\n if (e.name) err.name = e.name;\n return err;\n}\n\nexport class SidecarClient {\n private readonly options: SidecarClientOptions;\n private child: ChildProcessByStdio<Writable, Readable, Readable> | undefined;\n private reader: Interface | undefined;\n private readonly pending = new Map<number, Pending>();\n private nextId = 1;\n private disposed = false;\n\n constructor(options: SidecarClientOptions) {\n this.options = options;\n }\n\n /** Spawn (or reuse) the child process. */\n private ensureChild(): ChildProcessByStdio<Writable, Readable, Readable> {\n if (this.disposed) throw new Error(\"cursor sidecar client disposed\");\n if (this.child) return this.child;\n\n const child = spawn(this.options.nodePath ?? \"node\", [this.options.scriptPath], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: { ...process.env, ...this.options.env },\n });\n this.child = child;\n\n this.reader = createInterface({ input: child.stdout });\n this.reader.on(\"line\", (line) => this.handleLine(line));\n child.stderr.on(\"data\", (chunk: Buffer) => {\n if (this.options.debug || process.env[\"OPENCODE_CURSOR_DEBUG\"]) {\n process.stderr.write(`[cursor:sidecar] ${chunk}`);\n }\n });\n child.on(\"exit\", (code) => {\n this.failAll(new Error(`cursor sidecar exited (code ${code ?? \"unknown\"})`));\n this.child = undefined;\n this.reader?.close();\n this.reader = undefined;\n });\n child.on(\"error\", (err) => {\n this.failAll(new Error(`cursor sidecar failed to start: ${err.message}`));\n this.child = undefined;\n });\n this.updateRefs();\n return child;\n }\n\n /**\n * Keep the child (and its pipes) from holding the parent's event loop open\n * while idle, but ref it whenever a reply is outstanding so the loop can't\n * exit mid-request. Without this, any process that uses the provider and\n * never dispose()s — scripts, tests, opencode itself on shutdown — hangs.\n */\n private updateRefs(): void {\n const child = this.child;\n if (!child) return;\n const refable = [child, child.stdin, child.stdout, child.stderr] as Array<{\n ref?: () => void;\n unref?: () => void;\n }>;\n if (this.pending.size > 0) {\n for (const target of refable) target.ref?.();\n } else {\n for (const target of refable) target.unref?.();\n }\n }\n\n private failAll(err: Error): void {\n for (const pending of this.pending.values()) {\n pending.onStreamError?.(err);\n pending.reject(err);\n }\n this.pending.clear();\n this.updateRefs();\n }\n\n private handleLine(line: string): void {\n if (!line.trim()) return;\n let msg: Record<string, unknown>;\n try {\n msg = JSON.parse(line) as Record<string, unknown>;\n } catch {\n return; // ignore non-protocol noise on stdout\n }\n const id = msg[\"id\"];\n if (typeof id !== \"number\") return;\n const pending = this.pending.get(id);\n if (!pending) return;\n\n const ev = msg[\"ev\"];\n if (ev === \"update\") {\n pending.onUpdate?.(msg[\"update\"] as Record<string, unknown> & { type: string });\n return;\n }\n if (ev === \"result\") {\n this.pending.delete(id);\n this.updateRefs();\n pending.onResult?.(msg[\"result\"] as { status: string; result?: string });\n return;\n }\n if (ev === \"error\") {\n this.pending.delete(id);\n this.updateRefs();\n pending.onStreamError?.(reviveError(msg[\"error\"]));\n return;\n }\n\n if (msg[\"ok\"] === true) {\n // \"send\" acks stay pending for their streaming terminal event.\n if (!pending.onResult) {\n this.pending.delete(id);\n this.updateRefs();\n }\n pending.resolve(msg);\n } else {\n this.pending.delete(id);\n this.updateRefs();\n pending.reject(reviveError(msg[\"error\"]));\n }\n }\n\n private request(\n payload: Record<string, unknown>,\n hooks?: Pick<Pending, \"onUpdate\" | \"onResult\" | \"onStreamError\">,\n ): Promise<Record<string, unknown>> {\n const child = this.ensureChild();\n const id = this.nextId++;\n return new Promise<Record<string, unknown>>((resolve, reject) => {\n this.pending.set(id, { resolve, reject, ...hooks });\n this.updateRefs();\n child.stdin.write(`${JSON.stringify({ id, ...payload })}\\n`, (err) => {\n if (err) {\n this.pending.delete(id);\n this.updateRefs();\n reject(err);\n }\n });\n });\n }\n\n async createAgent(options: unknown): Promise<AgentLike> {\n const res = await this.request({ op: \"create\", options });\n return this.wrapAgent(String(res[\"agentId\"]));\n }\n\n async resumeAgent(agentId: string, options: unknown): Promise<AgentLike> {\n const res = await this.request({ op: \"resume\", agentId, options });\n return this.wrapAgent(String(res[\"agentId\"]));\n }\n\n private wrapAgent(agentId: string): AgentLike {\n return {\n agentId,\n send: (message, options) => this.sendTurn(agentId, message, options),\n close: () => {\n void this.request({ op: \"close\", agentId }).catch(() => {\n // best effort, mirrors SDKAgent.close()\n });\n },\n };\n }\n\n private async sendTurn(\n agentId: string,\n message: unknown,\n options?: AgentSendOptions,\n ): Promise<AgentRunLike> {\n let settle!: {\n resolve: (r: { status: string; result?: string }) => void;\n reject: (e: Error) => void;\n };\n const waited = new Promise<{ status: string; result?: string }>((resolve, reject) => {\n settle = { resolve, reject };\n });\n // Avoid unhandled-rejection noise when the consumer never calls wait().\n waited.catch(() => {});\n\n let sendId: number | undefined;\n const ack = this.request(\n {\n op: \"send\",\n agentId,\n message,\n ...(options?.mode ? { mode: options.mode } : {}),\n ...(options?.local?.force ? { force: true } : {}),\n },\n {\n onUpdate: (update) => options?.onDelta?.({ update }),\n onResult: (result) => settle.resolve(result),\n onStreamError: (err) => settle.reject(err),\n },\n );\n // The request id is allocated synchronously inside request(); capture it\n // for cancel by reading the id we just used.\n sendId = this.nextId - 1;\n\n await ack;\n return {\n wait: () => waited,\n cancel: async () => {\n if (sendId === undefined) return;\n await this.request({ op: \"cancel\", sendId }).catch(() => {});\n },\n };\n }\n\n /** Kill the child and reject anything in flight. */\n dispose(): void {\n this.disposed = true;\n this.failAll(new Error(\"cursor sidecar client disposed\"));\n this.reader?.close();\n this.reader = undefined;\n this.child?.kill();\n this.child = undefined;\n }\n}\n","import type {\n AgentDefinition,\n AgentModeOption,\n McpServerConfig,\n ModelSelection,\n SettingSource,\n} from \"@cursor/sdk\";\nimport { loadAgentBackend, type AgentLike } from \"./agent-backend.js\";\n\n/** sessionID -> Cursor agentId, so a session reuses one Cursor agent across turns. */\nconst pool = new Map<string, string>();\n\n/** Test/diagnostic helpers. */\nexport function getPooledAgentId(sessionID: string): string | undefined {\n return pool.get(sessionID);\n}\nexport function clearAgentPool(): void {\n pool.clear();\n}\n\nexport interface AcquireAgentParams {\n apiKey: string;\n modelSelection: ModelSelection;\n mode: AgentModeOption;\n cwd: string;\n settingSources?: SettingSource[];\n sandbox?: boolean;\n mcpServers?: Record<string, McpServerConfig>;\n agents?: Record<string, AgentDefinition>;\n name?: string;\n /** opencode session id; required for pooling. */\n sessionID?: string;\n /** When true (and sessionID present) reuse/resume one agent per session. */\n session: boolean;\n /**\n * Resume a specific Cursor agent by id. Takes precedence over session\n * pooling; lets power users continue an explicit agent (e.g. one returned by\n * a prior tool call) rather than the session's auto-managed one.\n */\n agentId?: string;\n}\n\nexport interface AcquiredAgent {\n agent: AgentLike;\n /** True when an existing pooled agent was resumed (send only the new turn). */\n resumed: boolean;\n /** Close the agent unless it's pooled (pooled agents persist for the next turn). */\n release: () => void;\n}\n\n/**\n * Get an agent to run a turn: resume the session's pooled agent when possible,\n * otherwise create a fresh one. Resume failures fall back to creation, so a\n * stale/expired pool entry degrades to a correct fresh turn rather than an error.\n */\nexport async function acquireAgent(params: AcquireAgentParams): Promise<AcquiredAgent> {\n const backend = loadAgentBackend();\n\n const createOptions = {\n apiKey: params.apiKey,\n model: params.modelSelection,\n mode: params.mode,\n local: {\n cwd: params.cwd,\n ...(params.settingSources ? { settingSources: params.settingSources } : {}),\n ...(params.sandbox !== undefined ? { sandboxOptions: { enabled: params.sandbox } } : {}),\n },\n ...(params.mcpServers ? { mcpServers: params.mcpServers } : {}),\n ...(params.agents ? { agents: params.agents } : {}),\n ...(params.name ? { name: params.name } : {}),\n };\n\n const pooling = params.session && Boolean(params.sessionID);\n const pooledId = pooling ? pool.get(params.sessionID!) : undefined;\n // An explicit agentId wins over the session's pooled agent.\n const resumeId = params.agentId ?? pooledId;\n\n let agent: AgentLike | undefined;\n let resumed = false;\n if (resumeId) {\n try {\n agent = await backend.resumeAgent(resumeId, createOptions);\n resumed = true;\n } catch {\n // A stale/expired id degrades to a fresh agent; drop a matching pool entry.\n if (pooledId && resumeId === pooledId) pool.delete(params.sessionID!);\n }\n }\n if (!agent) {\n agent = await backend.createAgent(createOptions);\n }\n\n if (pooling) pool.set(params.sessionID!, agent.agentId);\n\n const release = () => {\n if (!pooling) {\n try {\n agent!.close();\n } catch {\n // best effort\n }\n }\n };\n\n return { agent, resumed, release };\n}\n"],"mappings":"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","names":["cached","execSync","existsSync","spawn","execSync","existsSync","cached"]}
|
package/dist/plugin/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
resolveControls,
|
|
7
7
|
resolveCursorApiKey,
|
|
8
8
|
streamAgentTurn
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-D4YQ7ZEM.js";
|
|
10
10
|
|
|
11
11
|
// src/model-cache.ts
|
|
12
12
|
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -73,6 +73,29 @@ var FALLBACK_MODELS = [
|
|
|
73
73
|
{ id: "gpt-5.5", displayName: "GPT-5.5 (via Cursor)" }
|
|
74
74
|
];
|
|
75
75
|
|
|
76
|
+
// src/model-variants.ts
|
|
77
|
+
var REASONING_PARAM = /think|reason|effort/i;
|
|
78
|
+
var BOOLEAN_VALUES = /* @__PURE__ */ new Set(["true", "false"]);
|
|
79
|
+
function buildModelVariants(item) {
|
|
80
|
+
const out = {};
|
|
81
|
+
for (const param of item.parameters ?? []) {
|
|
82
|
+
if (!REASONING_PARAM.test(param.id)) continue;
|
|
83
|
+
const values = (param.values ?? []).map((v) => v.value);
|
|
84
|
+
if (values.length === 0) continue;
|
|
85
|
+
if (values.every((v) => BOOLEAN_VALUES.has(v))) {
|
|
86
|
+
if (values.includes("true")) {
|
|
87
|
+
out[param.id.toLowerCase()] = { params: { [param.id]: "true" } };
|
|
88
|
+
}
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
for (const value of values) {
|
|
92
|
+
const key = out[value] === void 0 ? value : `${param.id}-${value}`;
|
|
93
|
+
out[key] = { params: { [param.id]: value } };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
76
99
|
// src/model-discovery.ts
|
|
77
100
|
async function discoverModels(options = {}) {
|
|
78
101
|
const apiKey = resolveCursorApiKey(options.apiKey);
|
|
@@ -129,27 +152,13 @@ function toOpencodeModels(items) {
|
|
|
129
152
|
attachment: true,
|
|
130
153
|
reasoning: modelSupportsReasoning(item),
|
|
131
154
|
temperature: false,
|
|
132
|
-
tool_call: true
|
|
155
|
+
tool_call: true,
|
|
156
|
+
variants: buildModelVariants(item)
|
|
133
157
|
};
|
|
134
158
|
}
|
|
135
159
|
return out;
|
|
136
160
|
}
|
|
137
161
|
|
|
138
|
-
// src/model-variants.ts
|
|
139
|
-
var REASONING_PARAM = /think|reason|effort/i;
|
|
140
|
-
function buildModelVariants(item) {
|
|
141
|
-
const out = {};
|
|
142
|
-
for (const param of item.parameters ?? []) {
|
|
143
|
-
if (!REASONING_PARAM.test(param.id)) continue;
|
|
144
|
-
for (const { value } of param.values ?? []) {
|
|
145
|
-
const key = param.id.toLowerCase() === "thinking" ? value : `${param.id}-${value}`;
|
|
146
|
-
out[key] = { params: { [param.id]: value } };
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
out["plan"] = { mode: "plan" };
|
|
150
|
-
return out;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
162
|
// src/plugin/model-v2.ts
|
|
154
163
|
var PROVIDER_ID = "cursor";
|
|
155
164
|
var NPM_PACKAGE = "@stablekernel/opencode-cursor";
|
|
@@ -535,9 +544,17 @@ var CursorPlugin = async (input) => {
|
|
|
535
544
|
// Bridge opencode's session id to the provider: it lands in
|
|
536
545
|
// providerOptions.cursor.sessionID, which the provider reads to pool/resume a
|
|
537
546
|
// Cursor agent per session (when the `session` option is enabled).
|
|
547
|
+
//
|
|
548
|
+
// Also map opencode's plan AGENT to Cursor's plan mode. This hook fires
|
|
549
|
+
// after opencode merges the selected variant into `output.options`, so an
|
|
550
|
+
// explicit mode from the `plan` variant (or model options) wins — the
|
|
551
|
+
// agent-based default only applies when no mode was set.
|
|
538
552
|
"chat.params": async (input2, output) => {
|
|
539
553
|
if (input2.model?.providerID !== PROVIDER_ID) return;
|
|
540
554
|
output.options = { ...output.options ?? {}, sessionID: input2.sessionID };
|
|
555
|
+
if (input2.agent === "plan" && output.options["mode"] === void 0) {
|
|
556
|
+
output.options["mode"] = "plan";
|
|
557
|
+
}
|
|
541
558
|
},
|
|
542
559
|
tool: {
|
|
543
560
|
cursor_refresh_models: {
|
package/dist/plugin/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/model-cache.ts","../../src/fallback-models.ts","../../src/model-discovery.ts","../../src/model-variants.ts","../../src/plugin/model-v2.ts","../../src/plugin/mcp-config.ts","../../src/plugin/cursor-tools.ts","../../src/provider/cloud-agent.ts","../../src/provider/delegate.ts","../../src/plugin/index.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { ModelListItem } from \"@cursor/sdk\";\n\n/** Default cache lifetime: 24 hours, overridable via env. */\nconst DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;\n\nfunction ttlMs(): number {\n const raw = process.env.OPENCODE_CURSOR_MODEL_CACHE_TTL_MS;\n const parsed = raw ? Number.parseInt(raw, 10) : NaN;\n return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TTL_MS;\n}\n\nfunction cacheDir(): string {\n const base =\n process.env.XDG_CACHE_HOME?.trim() ||\n (homedir() ? join(homedir(), \".cache\") : tmpdir());\n return join(base, \"opencode-cursor\");\n}\n\nfunction cacheFile(fingerprint: string): string {\n return join(cacheDir(), `models-${fingerprint}.json`);\n}\n\n/**\n * Key-independent \"latest known catalog\" file. The `config` plugin hook runs\n * without access to the stored API key, so it can't read the per-key cache.\n * This file lets a keyless caller (the config hook) seed opencode's model\n * picker with the real catalog that a previous *authed* load discovered.\n */\nfunction latestCacheFile(): string {\n return join(cacheDir(), \"models-latest.json\");\n}\n\n/** The latest-catalog seed is kept longer than the per-key cache: the catalog\n * is stable and this only feeds pre-auth UI seeding. */\nconst LATEST_TTL_MS = 30 * 24 * 60 * 60 * 1000;\n\ninterface CacheEnvelope {\n savedAt: number;\n models: ModelListItem[];\n}\n\nfunction readCacheFile(file: string, maxAgeMs: number): ModelListItem[] | undefined {\n try {\n const parsed = JSON.parse(readFileSync(file, \"utf8\")) as CacheEnvelope;\n if (!parsed?.savedAt || !Array.isArray(parsed.models)) return undefined;\n if (Date.now() - parsed.savedAt > maxAgeMs) return undefined;\n return parsed.models;\n } catch {\n return undefined;\n }\n}\n\nfunction writeCacheFile(file: string, models: ModelListItem[]): void {\n try {\n mkdirSync(cacheDir(), { recursive: true });\n const envelope: CacheEnvelope = { savedAt: Date.now(), models };\n writeFileSync(file, JSON.stringify(envelope), \"utf8\");\n } catch {\n // Caching is an optimization; ignore write failures.\n }\n}\n\n/**\n * Return cached models for the given API-key fingerprint when present and still\n * fresh, otherwise `undefined`. Never throws on a missing/corrupt cache.\n */\nexport function readModelCache(fingerprint: string): ModelListItem[] | undefined {\n return readCacheFile(cacheFile(fingerprint), ttlMs());\n}\n\n/** Persist the discovered model list (per-key cache + key-independent latest\n * catalog). Best-effort; never throws. */\nexport function writeModelCache(fingerprint: string, models: ModelListItem[]): void {\n writeCacheFile(cacheFile(fingerprint), models);\n writeCacheFile(latestCacheFile(), models);\n}\n\n/**\n * Return the most recently discovered catalog regardless of API key, when\n * present and within {@link LATEST_TTL_MS}. Used by the keyless `config` hook to\n * seed the picker with the real catalog after a prior authed load.\n */\nexport function readLatestModelCache(): ModelListItem[] | undefined {\n return readCacheFile(latestCacheFile(), LATEST_TTL_MS);\n}\n","import type { ModelListItem } from \"@cursor/sdk\";\n\n/**\n * A small static snapshot of well-known Cursor models, used only when live\n * discovery is unavailable (no API key, offline, or an SDK error). The live\n * `Cursor.models.list()` result always takes precedence; this just lets the\n * provider appear in opencode with sensible defaults so the user can reach the\n * login flow. Refresh the real catalog with the `cursor_refresh_models` tool.\n */\nexport const FALLBACK_MODELS: ModelListItem[] = [\n {\n id: \"composer-2.5\",\n displayName: \"Composer 2.5\",\n description: \"Cursor's default agent model (fallback entry).\",\n parameters: [\n { id: \"thinking\", displayName: \"Thinking\", values: [{ value: \"off\" }, { value: \"on\" }] },\n ],\n },\n { id: \"claude-opus-4-8\", displayName: \"Claude Opus 4.8 (via Cursor)\" },\n { id: \"claude-sonnet-4-6\", displayName: \"Claude Sonnet 4.6 (via Cursor)\" },\n { id: \"gpt-5.5\", displayName: \"GPT-5.5 (via Cursor)\" },\n];\n","import type { ModelListItem } from \"@cursor/sdk\";\nimport { fingerprintApiKey, resolveCursorApiKey } from \"./api-key.js\";\nimport { readLatestModelCache, readModelCache, writeModelCache } from \"./model-cache.js\";\nimport { FALLBACK_MODELS } from \"./fallback-models.js\";\nimport { loadCursorSdk } from \"./cursor-runtime.js\";\n\nexport type ModelSource = \"live\" | \"cache\" | \"fallback\";\n\nexport interface DiscoveryResult {\n models: ModelListItem[];\n source: ModelSource;\n /** Human-readable note when discovery degraded (e.g. missing key, error). */\n warning?: string;\n}\n\nexport interface DiscoverOptions {\n /** Explicit key; falls back to CURSOR_API_KEY. */\n apiKey?: string;\n /** Bypass the on-disk cache and force a live `Cursor.models.list()`. */\n forceRefresh?: boolean;\n}\n\n/**\n * Discover the Cursor model catalog. Tries (in order): on-disk cache (unless\n * forced), live `Cursor.models.list()`, then the static fallback snapshot.\n * Always resolves — failures degrade to the fallback with a `warning`.\n */\nexport async function discoverModels(options: DiscoverOptions = {}): Promise<DiscoveryResult> {\n const apiKey = resolveCursorApiKey(options.apiKey);\n if (!apiKey) {\n // No key here (e.g. the keyless `config` hook). Prefer the real catalog a\n // prior authed load cached, so opencode's picker shows the full list rather\n // than only the static snapshot.\n const latest = readLatestModelCache();\n if (latest && latest.length > 0) return { models: latest, source: \"cache\" };\n return {\n models: FALLBACK_MODELS,\n source: \"fallback\",\n warning:\n \"No Cursor API key found. Run `opencode auth login` and choose Cursor, or set CURSOR_API_KEY. Showing fallback models.\",\n };\n }\n\n const fingerprint = fingerprintApiKey(apiKey);\n\n if (!options.forceRefresh) {\n const cached = readModelCache(fingerprint);\n if (cached && cached.length > 0) {\n return { models: cached, source: \"cache\" };\n }\n }\n\n try {\n const { Cursor } = await loadCursorSdk();\n const models = await Cursor.models.list({ apiKey });\n if (models.length > 0) {\n writeModelCache(fingerprint, models);\n return { models, source: \"live\" };\n }\n return {\n models: FALLBACK_MODELS,\n source: \"fallback\",\n warning: \"Cursor.models.list() returned no models; showing fallback models.\",\n };\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n // A stale cache is better than nothing on a transient failure.\n const stale = readModelCache(fingerprint);\n if (stale && stale.length > 0) {\n return { models: stale, source: \"cache\", warning: `Live discovery failed (${detail}); using cached models.` };\n }\n return {\n models: FALLBACK_MODELS,\n source: \"fallback\",\n warning: `Live discovery failed (${detail}); showing fallback models.`,\n };\n }\n}\n\n/** True when a model exposes a thinking/reasoning parameter. */\nexport function modelSupportsReasoning(item: ModelListItem): boolean {\n return (item.parameters ?? []).some((p) => /think|reason/i.test(p.id));\n}\n\n/** Shape of a single entry in opencode's `provider.<id>.models` config map. */\nexport interface OpencodeModelConfigEntry {\n id: string;\n name: string;\n attachment: boolean;\n reasoning: boolean;\n temperature: boolean;\n tool_call: boolean;\n}\n\n/**\n * Map discovered Cursor models to opencode's provider config `models` map. The\n * Cursor SDK runs an agent (it calls tools itself), so every model is marked\n * `tool_call: true` and `temperature: false`.\n */\nexport function toOpencodeModels(items: ModelListItem[]): Record<string, OpencodeModelConfigEntry> {\n const out: Record<string, OpencodeModelConfigEntry> = {};\n for (const item of items) {\n out[item.id] = {\n id: item.id,\n name: item.displayName || item.id,\n attachment: true,\n reasoning: modelSupportsReasoning(item),\n temperature: false,\n tool_call: true,\n };\n }\n return out;\n}\n","import type { ModelListItem } from \"@cursor/sdk\";\n\n/**\n * A Cursor model \"variant\" as opencode stores it: an options object that, when\n * the variant is selected, is merged into `providerOptions.cursor` and read back\n * by {@link resolveControls}.\n */\nexport interface CursorVariant {\n params?: Record<string, string>;\n mode?: \"agent\" | \"plan\";\n}\n\nconst REASONING_PARAM = /think|reason|effort/i;\n\n/**\n * Derive opencode model variants from a Cursor model's parameters so the\n * variant picker can expose thinking/reasoning levels and a plan-mode option.\n * Each variant's object is exactly what {@link resolveControls} consumes.\n */\nexport function buildModelVariants(item: ModelListItem): Record<string, CursorVariant> {\n const out: Record<string, CursorVariant> = {};\n\n for (const param of item.parameters ?? []) {\n if (!REASONING_PARAM.test(param.id)) continue;\n for (const { value } of param.values ?? []) {\n // Key is unique across params; value object carries the param id+value.\n const key = param.id.toLowerCase() === \"thinking\" ? value : `${param.id}-${value}`;\n out[key] = { params: { [param.id]: value } };\n }\n }\n\n // Plan mode is orthogonal to model params and never auto-signaled by opencode,\n // so always offer it as a selectable variant.\n out[\"plan\"] = { mode: \"plan\" };\n\n return out;\n}\n","import type { Model as ModelV2 } from \"@opencode-ai/sdk/v2\";\nimport type { ModelListItem } from \"@cursor/sdk\";\nimport { modelSupportsReasoning } from \"../model-discovery.js\";\nimport { buildModelVariants } from \"../model-variants.js\";\n\nexport const PROVIDER_ID = \"cursor\";\nexport const NPM_PACKAGE = \"@stablekernel/opencode-cursor\";\n\n/**\n * The npm specifier opencode uses to load the provider SDK. Defaults to the\n * published package name; can be overridden with a `file://...` URL (which\n * opencode imports directly, skipping a registry install) via\n * `OPENCODE_CURSOR_PROVIDER_NPM` — useful for local development and CI before\n * the package is published.\n */\nexport function providerNpm(): string {\n return process.env.OPENCODE_CURSOR_PROVIDER_NPM?.trim() || NPM_PACKAGE;\n}\n\n/**\n * Build opencode's rich runtime `Model` objects from discovered Cursor models.\n * Used by the auth-aware `provider.models()` hook. Fields opencode does not get\n * from the Cursor catalog are filled with safe defaults (zero cost — Cursor\n * bills separately; generous context limits).\n */\nexport function buildModelV2Map(items: ModelListItem[]): Record<string, ModelV2> {\n const out: Record<string, ModelV2> = {};\n for (const item of items) {\n out[item.id] = {\n id: item.id,\n providerID: PROVIDER_ID,\n api: { id: item.id, url: \"\", npm: providerNpm() },\n name: item.displayName || item.id,\n capabilities: {\n temperature: false,\n reasoning: modelSupportsReasoning(item),\n attachment: true,\n toolcall: true,\n input: { text: true, audio: false, image: true, video: false, pdf: false },\n output: { text: true, audio: false, image: false, video: false, pdf: false },\n interleaved: false,\n },\n cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },\n limit: { context: 200_000, output: 32_000 },\n status: \"active\",\n options: {},\n headers: {},\n release_date: \"\",\n variants: buildModelVariants(item) as ModelV2[\"variants\"],\n };\n }\n return out;\n}\n","import type { Config } from \"@opencode-ai/plugin\";\nimport type { McpServerConfig } from \"@cursor/sdk\";\n\n/** The value type of opencode's `config.mcp` map. */\ntype OpencodeMcp = NonNullable<Config[\"mcp\"]>;\ntype OpencodeMcpEntry = OpencodeMcp[string];\n\n/**\n * Translate opencode's configured MCP servers (`config.mcp`) into the Cursor\n * SDK's `McpServerConfig` shape so the same servers (e.g. Serena) can be handed\n * to the Cursor agent via `Agent.create({ mcpServers })`.\n *\n * MCP servers are independent processes addressed by a launch spec, so opencode\n * and the Cursor agent can each connect to the same server. Disabled entries\n * (`enabled: false`) are skipped. opencode-only fields with no Cursor\n * equivalent (timeout, oauth) are dropped.\n */\nexport function translateMcpServers(mcp: Config[\"mcp\"]): Record<string, McpServerConfig> {\n const out: Record<string, McpServerConfig> = {};\n if (!mcp) return out;\n\n for (const [name, entry] of Object.entries(mcp) as Array<[string, OpencodeMcpEntry]>) {\n if (!entry || entry.enabled === false) continue;\n\n if (entry.type === \"local\") {\n const [command, ...args] = entry.command ?? [];\n if (!command) continue;\n out[name] = {\n type: \"stdio\",\n command,\n ...(args.length > 0 ? { args } : {}),\n ...(entry.environment && Object.keys(entry.environment).length > 0\n ? { env: entry.environment }\n : {}),\n };\n } else if (entry.type === \"remote\") {\n if (!entry.url) continue;\n out[name] = {\n type: \"http\",\n url: entry.url,\n ...(entry.headers && Object.keys(entry.headers).length > 0\n ? { headers: entry.headers }\n : {}),\n };\n }\n }\n\n return out;\n}\n","import { tool, type ToolContext, type ToolDefinition } from \"@opencode-ai/plugin\";\nimport { runCloudAgent } from \"../provider/cloud-agent.js\";\nimport { runDelegate } from \"../provider/delegate.js\";\n\nconst s = tool.schema;\n\nexport interface CursorToolDeps {\n /**\n * Resolve the Cursor API key (from opencode auth, captured by the plugin's\n * auth loader, or the CURSOR_API_KEY env var). Returns undefined when no key\n * is available so the tool can return a clear \"needs auth\" message.\n */\n resolveApiKey: () => string | undefined;\n /** Default working directory for local delegation (the session worktree/cwd). */\n defaultCwd: () => string;\n}\n\nconst NEEDS_AUTH =\n \"No Cursor API key available. Run `opencode auth login` and choose Cursor, or set CURSOR_API_KEY.\";\n\n/**\n * Request approval for a sensitive Cursor invocation. `context.ask` is the\n * opencode mechanism a custom tool uses to gate itself; it honors the user's\n * `permission` config (allow resolves silently, ask prompts, deny rejects).\n *\n * Returns `{ ok: true }` when approved, or `{ ok: false, reason }` when the\n * request was rejected. We deliberately do not claim the rejection was a policy\n * \"deny\" — `context.ask` rejects on both an explicit deny and an internal\n * failure, and conflating them produces misleading messages. The gate is\n * fail-closed: any rejection (including a host that doesn't provide `ask`)\n * blocks the call rather than silently allowing it.\n */\nasync function requestApproval(\n context: ToolContext,\n permission: string,\n patterns: string[],\n metadata: Record<string, unknown>,\n): Promise<{ ok: boolean; reason?: string }> {\n try {\n await context.ask({ permission, patterns, always: patterns, metadata });\n return { ok: true };\n } catch (err) {\n return { ok: false, reason: err instanceof Error ? err.message : String(err) };\n }\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * Build the Cursor delegation tools that complement the native provider:\n * - `cursor_cloud_agent`: run a background agent on a remote repo (optionally\n * opening a PR) — work that maps poorly onto the synchronous provider path.\n * - `cursor_delegate`: run a single local Cursor turn as a permission-gated,\n * auditable tool call (for users who want Cursor as a delegate rather than\n * as their primary model).\n *\n * Both are gated via `context.ask`, so a user `permission` policy controls them.\n */\nexport function buildCursorTools(deps: CursorToolDeps): Record<string, ToolDefinition> {\n return {\n cursor_cloud_agent: tool({\n description:\n \"Launch a Cursor background ('cloud') agent on a remote repository. Runs autonomously \" +\n \"(may take minutes) and can open a pull request. Returns the cloud agent id, final \" +\n \"status, result, and PR url when available.\",\n args: {\n prompt: s.string().describe(\"The task/instruction for the background agent.\"),\n repoUrl: s\n .string()\n .describe(\"Target repository URL, e.g. https://github.com/owner/repo.\"),\n startingRef: s\n .string()\n .optional()\n .describe(\"Branch or ref to start from (defaults to the repo default branch).\"),\n model: s.string().optional().describe(\"Cursor model id (optional for cloud).\"),\n mode: s.enum([\"agent\", \"plan\"]).optional().describe(\"Conversation mode.\"),\n thinking: s.string().optional().describe(\"Thinking level, e.g. 'high'.\"),\n autoCreatePR: s\n .boolean()\n .optional()\n .describe(\"Open a pull request automatically when finished.\"),\n workOnCurrentBranch: s\n .boolean()\n .optional()\n .describe(\"Operate on the current branch instead of creating a new one.\"),\n },\n execute: async (args, context) => {\n const apiKey = deps.resolveApiKey();\n if (!apiKey) return NEEDS_AUTH;\n\n const approval = await requestApproval(\n context,\n \"cursor_cloud_agent\",\n [args.repoUrl],\n { repoUrl: args.repoUrl, autoCreatePR: args.autoCreatePR ?? false },\n );\n if (!approval.ok) {\n return `Cloud agent not approved for ${args.repoUrl}${approval.reason ? `: ${approval.reason}` : \".\"}`;\n }\n\n let result;\n try {\n result = await runCloudAgent({\n apiKey,\n prompt: args.prompt,\n repoUrl: args.repoUrl,\n ...(args.startingRef ? { startingRef: args.startingRef } : {}),\n ...(args.model ? { model: args.model } : {}),\n ...(args.mode ? { mode: args.mode } : {}),\n ...(args.thinking ? { thinking: args.thinking } : {}),\n ...(args.autoCreatePR !== undefined ? { autoCreatePR: args.autoCreatePR } : {}),\n ...(args.workOnCurrentBranch !== undefined\n ? { workOnCurrentBranch: args.workOnCurrentBranch }\n : {}),\n abortSignal: context.abort,\n });\n } catch (err) {\n return `Cloud agent failed: ${errorMessage(err)}`;\n }\n\n const lines = [\n `Cloud agent ${result.agentId} — ${result.status}`,\n ...(result.prUrl ? [`PR: ${result.prUrl}`] : []),\n ...(result.branches.length > 0\n ? [`Branches: ${result.branches.map((b) => b.branch ?? b.repoUrl).join(\", \")}`]\n : []),\n ...(result.result ? [\"\", result.result] : []),\n ...(result.progress.length > 0 ? [\"\", \"Progress:\", ...result.progress] : []),\n ];\n\n return {\n title: `Cursor cloud agent (${result.status})`,\n output: lines.join(\"\\n\"),\n metadata: {\n agentId: result.agentId,\n status: result.status,\n prUrl: result.prUrl ?? null,\n durationMs: result.durationMs ?? null,\n },\n };\n },\n }),\n\n cursor_delegate: tool({\n description:\n \"Delegate a single subtask to a local Cursor agent and return its result. Use to hand \" +\n \"off discrete work to Cursor while keeping your primary model in control. Permission-gated.\",\n args: {\n prompt: s.string().describe(\"The subtask to delegate to Cursor.\"),\n model: s.string().describe(\"Cursor model id to run the delegation on.\"),\n mode: s.enum([\"agent\", \"plan\"]).optional().describe(\"Conversation mode.\"),\n thinking: s.string().optional().describe(\"Thinking level, e.g. 'high'.\"),\n cwd: s\n .string()\n .optional()\n .describe(\"Working directory (defaults to the session directory).\"),\n sandbox: s.boolean().optional().describe(\"Run the agent's tools in Cursor's sandbox.\"),\n agentId: s\n .string()\n .optional()\n .describe(\"Resume a specific Cursor agent id instead of starting fresh.\"),\n },\n execute: async (args, context) => {\n const apiKey = deps.resolveApiKey();\n if (!apiKey) return NEEDS_AUTH;\n\n const approval = await requestApproval(context, \"cursor_delegate\", [args.model], {\n model: args.model,\n prompt: args.prompt,\n });\n if (!approval.ok) {\n return `Delegation to ${args.model} not approved${approval.reason ? `: ${approval.reason}` : \".\"}`;\n }\n\n let result;\n try {\n result = await runDelegate({\n apiKey,\n prompt: args.prompt,\n model: args.model,\n cwd: args.cwd ?? context.directory ?? deps.defaultCwd(),\n ...(args.mode ? { mode: args.mode } : {}),\n ...(args.thinking ? { thinking: args.thinking } : {}),\n ...(args.sandbox !== undefined ? { sandbox: args.sandbox } : {}),\n ...(args.agentId ? { agentId: args.agentId } : {}),\n abortSignal: context.abort,\n });\n } catch (err) {\n return `Delegation failed: ${errorMessage(err)}`;\n }\n\n const toolNote =\n result.toolActivity.length > 0\n ? `\\n\\n(${result.toolActivity.length} tool call(s)` +\n `${result.toolActivity.some((t) => t.isError) ? \", some failed\" : \"\"})`\n : \"\";\n\n return {\n title: `Cursor delegate (${args.model})`,\n output: (result.text || \"(no text output)\") + toolNote,\n metadata: {\n agentId: result.agentId,\n model: args.model,\n toolCalls: result.toolActivity.length,\n usage: result.usage ?? null,\n },\n };\n },\n }),\n };\n}\n","import type { AgentModeOption, ConversationStep, InteractionUpdate } from \"@cursor/sdk\";\nimport { loadCursorSdk } from \"../cursor-runtime.js\";\nimport { buildModelSelection } from \"./controls.js\";\n\n/**\n * A target repository for a cloud agent. Cursor's cloud runtime accepts an\n * array of repos; the tool surface exposes the common single-repo case.\n */\nexport interface CloudRepoTarget {\n url: string;\n startingRef?: string;\n}\n\nexport interface CloudAgentParams {\n apiKey: string;\n /** The instruction/task for the background agent. */\n prompt: string;\n /** Target repository URL (e.g. https://github.com/owner/repo). */\n repoUrl: string;\n /** Branch/ref to start from. Defaults to the repo's default branch. */\n startingRef?: string;\n /** Cursor model id. Optional for cloud (server picks a default otherwise). */\n model?: string;\n /** Conversation mode; defaults to \"agent\". */\n mode?: AgentModeOption;\n /** Convenience for the Cursor `thinking` model param (e.g. \"high\"). */\n thinking?: string;\n /** When true, open a PR automatically once the agent finishes. */\n autoCreatePR?: boolean;\n /** Operate on the current branch instead of creating a new one. */\n workOnCurrentBranch?: boolean;\n /** Cancels the run when aborted (wired to the tool's abort signal). */\n abortSignal?: AbortSignal;\n}\n\nexport interface CloudAgentBranch {\n repoUrl: string;\n branch?: string;\n prUrl?: string;\n}\n\nexport interface CloudAgentResult {\n agentId: string;\n /** Terminal run status: \"finished\" | \"error\" | \"cancelled\". */\n status: string;\n /** The agent's final textual result, when present. */\n result?: string;\n /** First PR url found across result branches (when `autoCreatePR`). */\n prUrl?: string;\n /** Per-repo branch/PR info reported by the run. */\n branches: CloudAgentBranch[];\n durationMs?: number;\n /** Human-readable progress lines captured from status/step/summary updates. */\n progress: string[];\n}\n\n/**\n * Run a Cursor background (\"cloud\") agent against a remote repository and wait\n * for it to finish, returning the final status, result text, and any PR url.\n *\n * A cloud agent can run for minutes and produce a PR rather than a chat reply,\n * which maps poorly onto the synchronous provider `doStream` path — so this is\n * exposed as an opencode tool instead (see plugin/index.ts). Progress is\n * collected into `progress[]` (opencode custom tools return a single result\n * rather than a live stream) and the lifecycle is bridged through the same\n * `loadCursorSdk` plumbing the provider uses.\n */\nexport async function runCloudAgent(params: CloudAgentParams): Promise<CloudAgentResult> {\n const { Agent } = await loadCursorSdk();\n const modelSelection = params.model\n ? buildModelSelection(params.model, params.thinking ? { thinking: params.thinking } : undefined)\n : undefined;\n const mode: AgentModeOption = params.mode ?? \"agent\";\n\n const createOptions = {\n apiKey: params.apiKey,\n ...(modelSelection ? { model: modelSelection } : {}),\n mode,\n cloud: {\n repos: [\n {\n url: params.repoUrl,\n ...(params.startingRef ? { startingRef: params.startingRef } : {}),\n },\n ],\n ...(params.autoCreatePR !== undefined ? { autoCreatePR: params.autoCreatePR } : {}),\n ...(params.workOnCurrentBranch !== undefined\n ? { workOnCurrentBranch: params.workOnCurrentBranch }\n : {}),\n },\n };\n\n const progress: string[] = [];\n const agent = await Agent.create(createOptions);\n\n // `onDelta` carries fine-grained updates; for a cloud (background) run the\n // higher-signal progress arrives via `onStep` (whole conversation steps) and\n // `run.onDidChangeStatus`. We capture all three — whichever the runtime emits.\n const onDelta = ({ update }: { update: InteractionUpdate }) => {\n if (update.type === \"summary\") progress.push(`summary: ${update.summary}`);\n };\n\n const onStep = ({ step }: { step: ConversationStep }) => {\n progress.push(`step: ${describeStep(step)}`);\n };\n\n try {\n const run = await agent.send(params.prompt, { mode, onDelta, onStep });\n\n const off = run.onDidChangeStatus?.((status: string) => {\n progress.push(`status: ${status}`);\n });\n const onAbort = () => {\n run.cancel().catch(() => {});\n };\n params.abortSignal?.addEventListener(\"abort\", onAbort);\n\n try {\n const result = await run.wait();\n const branches: CloudAgentBranch[] = (result.git?.branches ?? []).map((b) => ({\n repoUrl: b.repoUrl,\n ...(b.branch ? { branch: b.branch } : {}),\n ...(b.prUrl ? { prUrl: b.prUrl } : {}),\n }));\n const prUrl = branches.find((b) => b.prUrl)?.prUrl;\n return {\n agentId: agent.agentId,\n status: result.status,\n ...(result.result !== undefined ? { result: result.result } : {}),\n ...(prUrl ? { prUrl } : {}),\n branches,\n ...(result.durationMs !== undefined ? { durationMs: result.durationMs } : {}),\n progress,\n };\n } finally {\n off?.();\n params.abortSignal?.removeEventListener(\"abort\", onAbort);\n }\n } finally {\n try {\n agent.close();\n } catch {\n // best effort; cloud agents persist server-side regardless.\n }\n }\n}\n\n/** A short, log-friendly description of a conversation step for progress output. */\nfunction describeStep(step: ConversationStep): string {\n if (step.type === \"toolCall\") return `toolCall:${step.message.type}`;\n return step.type;\n}\n","import type { AgentModeOption } from \"@cursor/sdk\";\nimport type { CursorUsage } from \"./agent-events.js\";\nimport { streamAgentTurn } from \"./agent-events.js\";\nimport { resolveControls } from \"./controls.js\";\nimport { acquireAgent } from \"./session-pool.js\";\n\nexport interface DelegateParams {\n apiKey: string;\n /** The subtask to delegate to the Cursor agent. */\n prompt: string;\n /** Cursor model id to run the delegation on. */\n model: string;\n /** Conversation mode; defaults to \"agent\". */\n mode?: AgentModeOption;\n /** Convenience for the Cursor `thinking` model param (e.g. \"high\"). */\n thinking?: string;\n /** Working directory the local agent operates in. */\n cwd: string;\n /** Run the agent's tools inside Cursor's sandbox. */\n sandbox?: boolean;\n /** Resume a specific Cursor agent by id instead of creating a fresh one. */\n agentId?: string;\n /** Cancels the run when aborted (wired to the tool's abort signal). */\n abortSignal?: AbortSignal;\n}\n\nexport interface DelegateToolActivity {\n name: string;\n isError: boolean;\n}\n\nexport interface DelegateResult {\n agentId: string;\n text: string;\n reasoning: string;\n toolActivity: DelegateToolActivity[];\n usage?: CursorUsage;\n}\n\n/**\n * Run a single delegated turn on a fresh (or explicitly resumed) local Cursor\n * agent and aggregate the outcome into a plain result. This backs the opt-in\n * `cursor_delegate` tool, which gives users a permission-gated boundary around\n * Cursor (the provider path runs Cursor's own loop without per-call gating).\n *\n * Reuses the provider's `acquireAgent` + `streamAgentTurn` plumbing; the turn\n * is consumed eagerly here because a tool returns a single result rather than a\n * live stream.\n */\nexport async function runDelegate(params: DelegateParams): Promise<DelegateResult> {\n const { mode, modelSelection } = resolveControls(\n params.model,\n {\n mode: params.mode ?? \"agent\",\n ...(params.thinking ? { params: { thinking: params.thinking } } : {}),\n },\n undefined,\n );\n\n const acquired = await acquireAgent({\n apiKey: params.apiKey,\n modelSelection,\n mode,\n cwd: params.cwd,\n ...(params.sandbox !== undefined ? { sandbox: params.sandbox } : {}),\n ...(params.agentId ? { agentId: params.agentId } : {}),\n session: false,\n });\n\n const text: string[] = [];\n const reasoning: string[] = [];\n const toolActivity: DelegateToolActivity[] = [];\n let usage: CursorUsage | undefined;\n\n try {\n for await (const event of streamAgentTurn(\n acquired.agent,\n { text: params.prompt },\n { mode, ...(params.abortSignal ? { abortSignal: params.abortSignal } : {}) },\n )) {\n switch (event.type) {\n case \"text-delta\":\n text.push(event.text);\n break;\n case \"reasoning-delta\":\n reasoning.push(event.text);\n break;\n case \"tool-call\":\n toolActivity.push({ name: event.name, isError: false });\n break;\n case \"tool-result\":\n if (event.isError) toolActivity.push({ name: event.name, isError: true });\n break;\n case \"usage\":\n usage = event.usage;\n break;\n case \"finish\":\n // The aggregated result text; prefer it when deltas were absent.\n if (event.text && text.length === 0) text.push(event.text);\n break;\n }\n }\n } finally {\n acquired.release();\n }\n\n return {\n agentId: acquired.agent.agentId,\n text: text.join(\"\"),\n reasoning: reasoning.join(\"\"),\n toolActivity,\n ...(usage ? { usage } : {}),\n };\n}\n","import type { Plugin } from \"@opencode-ai/plugin\";\nimport type { Auth } from \"@opencode-ai/sdk/v2\";\nimport { resolveCursorApiKey } from \"../api-key.js\";\nimport { discoverModels, toOpencodeModels } from \"../model-discovery.js\";\nimport { buildModelV2Map, PROVIDER_ID, providerNpm } from \"./model-v2.js\";\nimport { translateMcpServers } from \"./mcp-config.js\";\nimport { buildCursorTools } from \"./cursor-tools.js\";\n\nfunction apiKeyFromAuth(auth: Auth | undefined): string | undefined {\n return auth?.type === \"api\" ? auth.key : undefined;\n}\n\n/**\n * opencode plugin that adds a \"Cursor\" provider backed by the official Cursor\n * SDK (`@cursor/sdk`).\n *\n * - `auth`: registers an API-key login for Cursor and a `loader` that feeds the\n * key into the AI-SDK provider factory. The key is validated on first use\n * (model discovery / first call), not at login — see the note on `methods`.\n * - `config`: registers the provider (npm package + discovered/fallback models)\n * so it shows up in opencode immediately.\n * - `provider.models`: auth-aware live model discovery via `Cursor.models.list`.\n * - `tool.cursor_refresh_models`: force-refresh the model catalog.\n */\nexport const CursorPlugin: Plugin = async (input) => {\n // The Cursor API key resolved by opencode's auth loader, captured so the\n // delegation tools (which don't receive auth directly) can reuse it. Falls\n // back to the CURSOR_API_KEY env var when the loader hasn't run.\n let capturedApiKey: string | undefined;\n\n return {\n auth: {\n provider: PROVIDER_ID,\n loader: async (getAuth) => {\n const apiKey = resolveCursorApiKey(apiKeyFromAuth(await getAuth().catch(() => undefined)));\n if (apiKey) {\n capturedApiKey = apiKey;\n // The `config` hook (which seeds opencode's model picker) runs without\n // a key. Warm the catalog cache here — the loader is the hook that\n // reliably has the key — so the next launch seeds the full live\n // catalog instead of the static fallback. Fire-and-forget: discovery\n // never throws and must not block auth/provider load.\n void discoverModels({ apiKey });\n }\n return apiKey ? { apiKey } : {};\n },\n // A single API-key method. opencode always shows its built-in \"Enter your\n // API key\" prompt for `type: \"api\"`, so we intentionally do NOT declare\n // custom `prompts` (that asks for the key a second time) or an `authorize`\n // callback. opencode only passes `authorize` the *custom-prompt* inputs —\n // never the built-in key — so validating the key in `authorize` would\n // force that redundant extra prompt. Instead the key is validated on first\n // use (model discovery / the first call both surface a bad key clearly).\n methods: [{ type: \"api\", label: \"Cursor API Key\" }],\n },\n\n config: async (config) => {\n const { models } = await discoverModels({});\n config.provider ??= {};\n const existing = config.provider[PROVIDER_ID] ?? {};\n const existingOptions = (existing.options ?? {}) as Record<string, unknown>;\n\n // Forward opencode's configured MCP servers (e.g. Serena) to the Cursor\n // agent so it can use the same servers. Opt out via\n // `provider.cursor.options.forwardMcp: false`.\n const forwardMcp = existingOptions[\"forwardMcp\"] !== false;\n const userMcp = (existingOptions[\"mcpServers\"] ?? {}) as Record<string, unknown>;\n const mcpServers = forwardMcp\n ? { ...userMcp, ...translateMcpServers(config.mcp) }\n : userMcp;\n\n config.provider[PROVIDER_ID] = {\n name: \"Cursor\",\n npm: providerNpm(),\n ...existing,\n options: {\n ...existingOptions,\n ...(Object.keys(mcpServers).length > 0 ? { mcpServers } : {}),\n },\n models: { ...toOpencodeModels(models), ...(existing.models ?? {}) },\n };\n },\n\n provider: {\n id: PROVIDER_ID,\n models: async (_provider, ctx) => {\n const apiKey = apiKeyFromAuth(ctx.auth);\n const { models } = await discoverModels({ apiKey });\n return buildModelV2Map(models);\n },\n },\n\n // Bridge opencode's session id to the provider: it lands in\n // providerOptions.cursor.sessionID, which the provider reads to pool/resume a\n // Cursor agent per session (when the `session` option is enabled).\n \"chat.params\": async (input, output) => {\n if (input.model?.providerID !== PROVIDER_ID) return;\n output.options = { ...(output.options ?? {}), sessionID: input.sessionID };\n },\n\n tool: {\n cursor_refresh_models: {\n description:\n \"Refresh the live Cursor model catalog (bypasses the 24h cache) and report the available models.\",\n args: {},\n execute: async () => {\n const result = await discoverModels({ forceRefresh: true });\n const lines = result.models.map((m) => `- ${m.id} — ${m.displayName}`);\n const header =\n result.source === \"live\"\n ? `Refreshed ${result.models.length} Cursor models (live):`\n : `Could not fetch live models (${result.source}). ${result.warning ?? \"\"}`.trim();\n return {\n title: `Cursor models (${result.source})`,\n output: [header, ...lines].join(\"\\n\"),\n metadata: { source: result.source, count: result.models.length },\n };\n },\n },\n // Delegation tools that complement the provider: a cloud/background agent\n // and a permission-gated local delegate. They resolve the Cursor key from\n // the auth loader (captured above) or CURSOR_API_KEY.\n ...buildCursorTools({\n resolveApiKey: () => resolveCursorApiKey(capturedApiKey),\n defaultCwd: () => input?.directory ?? process.cwd(),\n }),\n },\n };\n};\n\nexport default CursorPlugin;\n"],"mappings":"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","names":["input"]}
|
|
1
|
+
{"version":3,"sources":["../../src/model-cache.ts","../../src/fallback-models.ts","../../src/model-variants.ts","../../src/model-discovery.ts","../../src/plugin/model-v2.ts","../../src/plugin/mcp-config.ts","../../src/plugin/cursor-tools.ts","../../src/provider/cloud-agent.ts","../../src/provider/delegate.ts","../../src/plugin/index.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { ModelListItem } from \"@cursor/sdk\";\n\n/** Default cache lifetime: 24 hours, overridable via env. */\nconst DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;\n\nfunction ttlMs(): number {\n const raw = process.env.OPENCODE_CURSOR_MODEL_CACHE_TTL_MS;\n const parsed = raw ? Number.parseInt(raw, 10) : NaN;\n return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TTL_MS;\n}\n\nfunction cacheDir(): string {\n const base =\n process.env.XDG_CACHE_HOME?.trim() ||\n (homedir() ? join(homedir(), \".cache\") : tmpdir());\n return join(base, \"opencode-cursor\");\n}\n\nfunction cacheFile(fingerprint: string): string {\n return join(cacheDir(), `models-${fingerprint}.json`);\n}\n\n/**\n * Key-independent \"latest known catalog\" file. The `config` plugin hook runs\n * without access to the stored API key, so it can't read the per-key cache.\n * This file lets a keyless caller (the config hook) seed opencode's model\n * picker with the real catalog that a previous *authed* load discovered.\n */\nfunction latestCacheFile(): string {\n return join(cacheDir(), \"models-latest.json\");\n}\n\n/** The latest-catalog seed is kept longer than the per-key cache: the catalog\n * is stable and this only feeds pre-auth UI seeding. */\nconst LATEST_TTL_MS = 30 * 24 * 60 * 60 * 1000;\n\ninterface CacheEnvelope {\n savedAt: number;\n models: ModelListItem[];\n}\n\nfunction readCacheFile(file: string, maxAgeMs: number): ModelListItem[] | undefined {\n try {\n const parsed = JSON.parse(readFileSync(file, \"utf8\")) as CacheEnvelope;\n if (!parsed?.savedAt || !Array.isArray(parsed.models)) return undefined;\n if (Date.now() - parsed.savedAt > maxAgeMs) return undefined;\n return parsed.models;\n } catch {\n return undefined;\n }\n}\n\nfunction writeCacheFile(file: string, models: ModelListItem[]): void {\n try {\n mkdirSync(cacheDir(), { recursive: true });\n const envelope: CacheEnvelope = { savedAt: Date.now(), models };\n writeFileSync(file, JSON.stringify(envelope), \"utf8\");\n } catch {\n // Caching is an optimization; ignore write failures.\n }\n}\n\n/**\n * Return cached models for the given API-key fingerprint when present and still\n * fresh, otherwise `undefined`. Never throws on a missing/corrupt cache.\n */\nexport function readModelCache(fingerprint: string): ModelListItem[] | undefined {\n return readCacheFile(cacheFile(fingerprint), ttlMs());\n}\n\n/** Persist the discovered model list (per-key cache + key-independent latest\n * catalog). Best-effort; never throws. */\nexport function writeModelCache(fingerprint: string, models: ModelListItem[]): void {\n writeCacheFile(cacheFile(fingerprint), models);\n writeCacheFile(latestCacheFile(), models);\n}\n\n/**\n * Return the most recently discovered catalog regardless of API key, when\n * present and within {@link LATEST_TTL_MS}. Used by the keyless `config` hook to\n * seed the picker with the real catalog after a prior authed load.\n */\nexport function readLatestModelCache(): ModelListItem[] | undefined {\n return readCacheFile(latestCacheFile(), LATEST_TTL_MS);\n}\n","import type { ModelListItem } from \"@cursor/sdk\";\n\n/**\n * A small static snapshot of well-known Cursor models, used only when live\n * discovery is unavailable (no API key, offline, or an SDK error). The live\n * `Cursor.models.list()` result always takes precedence; this just lets the\n * provider appear in opencode with sensible defaults so the user can reach the\n * login flow. Refresh the real catalog with the `cursor_refresh_models` tool.\n */\nexport const FALLBACK_MODELS: ModelListItem[] = [\n {\n id: \"composer-2.5\",\n displayName: \"Composer 2.5\",\n description: \"Cursor's default agent model (fallback entry).\",\n parameters: [\n { id: \"thinking\", displayName: \"Thinking\", values: [{ value: \"off\" }, { value: \"on\" }] },\n ],\n },\n { id: \"claude-opus-4-8\", displayName: \"Claude Opus 4.8 (via Cursor)\" },\n { id: \"claude-sonnet-4-6\", displayName: \"Claude Sonnet 4.6 (via Cursor)\" },\n { id: \"gpt-5.5\", displayName: \"GPT-5.5 (via Cursor)\" },\n];\n","import type { ModelListItem } from \"@cursor/sdk\";\n\n/**\n * A Cursor model \"variant\" as opencode stores it: an options object that, when\n * the variant is selected, is merged into `providerOptions.cursor` and read back\n * by {@link resolveControls}.\n */\nexport interface CursorVariant {\n params?: Record<string, string>;\n mode?: \"agent\" | \"plan\";\n}\n\nconst REASONING_PARAM = /think|reason|effort/i;\nconst BOOLEAN_VALUES = new Set([\"true\", \"false\"]);\n\n/**\n * Derive opencode model variants from a Cursor model's parameters so the\n * variant picker can expose thinking/reasoning levels. Each variant's object is\n * exactly what {@link resolveControls} consumes. Plan mode is NOT a variant:\n * opencode's plan agent (Tab) is mapped to Cursor's plan mode by the plugin's\n * `chat.params` hook.\n */\nexport function buildModelVariants(item: ModelListItem): Record<string, CursorVariant> {\n const out: Record<string, CursorVariant> = {};\n\n for (const param of item.parameters ?? []) {\n if (!REASONING_PARAM.test(param.id)) continue;\n const values = (param.values ?? []).map((v) => v.value);\n if (values.length === 0) continue;\n\n if (values.every((v) => BOOLEAN_VALUES.has(v))) {\n // Boolean toggle (e.g. thinking=[\"false\",\"true\"]). Literal true/false\n // variant names are meaningless in the picker — surface a single\n // variant named after the param that switches it on. \"Off\" is the\n // model's default (no variant selected).\n if (values.includes(\"true\")) {\n out[param.id.toLowerCase()] = { params: { [param.id]: \"true\" } };\n }\n continue;\n }\n\n for (const value of values) {\n // Key by the bare value (e.g. \"high\"); prefix with the param id only\n // when two params share a value (e.g. reasoning-low vs effort-low).\n const key = out[value] === undefined ? value : `${param.id}-${value}`;\n out[key] = { params: { [param.id]: value } };\n }\n }\n\n return out;\n}\n","import type { ModelListItem } from \"@cursor/sdk\";\nimport { fingerprintApiKey, resolveCursorApiKey } from \"./api-key.js\";\nimport { readLatestModelCache, readModelCache, writeModelCache } from \"./model-cache.js\";\nimport { FALLBACK_MODELS } from \"./fallback-models.js\";\nimport { loadCursorSdk } from \"./cursor-runtime.js\";\nimport { buildModelVariants, type CursorVariant } from \"./model-variants.js\";\n\nexport type ModelSource = \"live\" | \"cache\" | \"fallback\";\n\nexport interface DiscoveryResult {\n models: ModelListItem[];\n source: ModelSource;\n /** Human-readable note when discovery degraded (e.g. missing key, error). */\n warning?: string;\n}\n\nexport interface DiscoverOptions {\n /** Explicit key; falls back to CURSOR_API_KEY. */\n apiKey?: string;\n /** Bypass the on-disk cache and force a live `Cursor.models.list()`. */\n forceRefresh?: boolean;\n}\n\n/**\n * Discover the Cursor model catalog. Tries (in order): on-disk cache (unless\n * forced), live `Cursor.models.list()`, then the static fallback snapshot.\n * Always resolves — failures degrade to the fallback with a `warning`.\n */\nexport async function discoverModels(options: DiscoverOptions = {}): Promise<DiscoveryResult> {\n const apiKey = resolveCursorApiKey(options.apiKey);\n if (!apiKey) {\n // No key here (e.g. the keyless `config` hook). Prefer the real catalog a\n // prior authed load cached, so opencode's picker shows the full list rather\n // than only the static snapshot.\n const latest = readLatestModelCache();\n if (latest && latest.length > 0) return { models: latest, source: \"cache\" };\n return {\n models: FALLBACK_MODELS,\n source: \"fallback\",\n warning:\n \"No Cursor API key found. Run `opencode auth login` and choose Cursor, or set CURSOR_API_KEY. Showing fallback models.\",\n };\n }\n\n const fingerprint = fingerprintApiKey(apiKey);\n\n if (!options.forceRefresh) {\n const cached = readModelCache(fingerprint);\n if (cached && cached.length > 0) {\n return { models: cached, source: \"cache\" };\n }\n }\n\n try {\n const { Cursor } = await loadCursorSdk();\n const models = await Cursor.models.list({ apiKey });\n if (models.length > 0) {\n writeModelCache(fingerprint, models);\n return { models, source: \"live\" };\n }\n return {\n models: FALLBACK_MODELS,\n source: \"fallback\",\n warning: \"Cursor.models.list() returned no models; showing fallback models.\",\n };\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n // A stale cache is better than nothing on a transient failure.\n const stale = readModelCache(fingerprint);\n if (stale && stale.length > 0) {\n return { models: stale, source: \"cache\", warning: `Live discovery failed (${detail}); using cached models.` };\n }\n return {\n models: FALLBACK_MODELS,\n source: \"fallback\",\n warning: `Live discovery failed (${detail}); showing fallback models.`,\n };\n }\n}\n\n/** True when a model exposes a thinking/reasoning parameter. */\nexport function modelSupportsReasoning(item: ModelListItem): boolean {\n return (item.parameters ?? []).some((p) => /think|reason/i.test(p.id));\n}\n\n/** Shape of a single entry in opencode's `provider.<id>.models` config map. */\nexport interface OpencodeModelConfigEntry {\n id: string;\n name: string;\n attachment: boolean;\n reasoning: boolean;\n temperature: boolean;\n tool_call: boolean;\n /**\n * opencode model variants (thinking levels + plan mode). They MUST be seeded\n * here: opencode discards the plugin `provider.models()` hook for providers\n * absent from its models.dev catalog, so this config map is the only channel\n * through which cursor model variants reach the picker.\n */\n variants: Record<string, CursorVariant>;\n}\n\n/**\n * Map discovered Cursor models to opencode's provider config `models` map. The\n * Cursor SDK runs an agent (it calls tools itself), so every model is marked\n * `tool_call: true` and `temperature: false`.\n */\nexport function toOpencodeModels(items: ModelListItem[]): Record<string, OpencodeModelConfigEntry> {\n const out: Record<string, OpencodeModelConfigEntry> = {};\n for (const item of items) {\n out[item.id] = {\n id: item.id,\n name: item.displayName || item.id,\n attachment: true,\n reasoning: modelSupportsReasoning(item),\n temperature: false,\n tool_call: true,\n variants: buildModelVariants(item),\n };\n }\n return out;\n}\n","import type { Model as ModelV2 } from \"@opencode-ai/sdk/v2\";\nimport type { ModelListItem } from \"@cursor/sdk\";\nimport { modelSupportsReasoning } from \"../model-discovery.js\";\nimport { buildModelVariants } from \"../model-variants.js\";\n\nexport const PROVIDER_ID = \"cursor\";\nexport const NPM_PACKAGE = \"@stablekernel/opencode-cursor\";\n\n/**\n * The npm specifier opencode uses to load the provider SDK. Defaults to the\n * published package name; can be overridden with a `file://...` URL (which\n * opencode imports directly, skipping a registry install) via\n * `OPENCODE_CURSOR_PROVIDER_NPM` — useful for local development and CI before\n * the package is published.\n */\nexport function providerNpm(): string {\n return process.env.OPENCODE_CURSOR_PROVIDER_NPM?.trim() || NPM_PACKAGE;\n}\n\n/**\n * Build opencode's rich runtime `Model` objects from discovered Cursor models.\n * Used by the auth-aware `provider.models()` hook. Fields opencode does not get\n * from the Cursor catalog are filled with safe defaults (zero cost — Cursor\n * bills separately; generous context limits).\n */\nexport function buildModelV2Map(items: ModelListItem[]): Record<string, ModelV2> {\n const out: Record<string, ModelV2> = {};\n for (const item of items) {\n out[item.id] = {\n id: item.id,\n providerID: PROVIDER_ID,\n api: { id: item.id, url: \"\", npm: providerNpm() },\n name: item.displayName || item.id,\n capabilities: {\n temperature: false,\n reasoning: modelSupportsReasoning(item),\n attachment: true,\n toolcall: true,\n input: { text: true, audio: false, image: true, video: false, pdf: false },\n output: { text: true, audio: false, image: false, video: false, pdf: false },\n interleaved: false,\n },\n cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },\n limit: { context: 200_000, output: 32_000 },\n status: \"active\",\n options: {},\n headers: {},\n release_date: \"\",\n variants: buildModelVariants(item) as ModelV2[\"variants\"],\n };\n }\n return out;\n}\n","import type { Config } from \"@opencode-ai/plugin\";\nimport type { McpServerConfig } from \"@cursor/sdk\";\n\n/** The value type of opencode's `config.mcp` map. */\ntype OpencodeMcp = NonNullable<Config[\"mcp\"]>;\ntype OpencodeMcpEntry = OpencodeMcp[string];\n\n/**\n * Translate opencode's configured MCP servers (`config.mcp`) into the Cursor\n * SDK's `McpServerConfig` shape so the same servers (e.g. Serena) can be handed\n * to the Cursor agent via `Agent.create({ mcpServers })`.\n *\n * MCP servers are independent processes addressed by a launch spec, so opencode\n * and the Cursor agent can each connect to the same server. Disabled entries\n * (`enabled: false`) are skipped. opencode-only fields with no Cursor\n * equivalent (timeout, oauth) are dropped.\n */\nexport function translateMcpServers(mcp: Config[\"mcp\"]): Record<string, McpServerConfig> {\n const out: Record<string, McpServerConfig> = {};\n if (!mcp) return out;\n\n for (const [name, entry] of Object.entries(mcp) as Array<[string, OpencodeMcpEntry]>) {\n if (!entry || entry.enabled === false) continue;\n\n if (entry.type === \"local\") {\n const [command, ...args] = entry.command ?? [];\n if (!command) continue;\n out[name] = {\n type: \"stdio\",\n command,\n ...(args.length > 0 ? { args } : {}),\n ...(entry.environment && Object.keys(entry.environment).length > 0\n ? { env: entry.environment }\n : {}),\n };\n } else if (entry.type === \"remote\") {\n if (!entry.url) continue;\n out[name] = {\n type: \"http\",\n url: entry.url,\n ...(entry.headers && Object.keys(entry.headers).length > 0\n ? { headers: entry.headers }\n : {}),\n };\n }\n }\n\n return out;\n}\n","import { tool, type ToolContext, type ToolDefinition } from \"@opencode-ai/plugin\";\nimport { runCloudAgent } from \"../provider/cloud-agent.js\";\nimport { runDelegate } from \"../provider/delegate.js\";\n\nconst s = tool.schema;\n\nexport interface CursorToolDeps {\n /**\n * Resolve the Cursor API key (from opencode auth, captured by the plugin's\n * auth loader, or the CURSOR_API_KEY env var). Returns undefined when no key\n * is available so the tool can return a clear \"needs auth\" message.\n */\n resolveApiKey: () => string | undefined;\n /** Default working directory for local delegation (the session worktree/cwd). */\n defaultCwd: () => string;\n}\n\nconst NEEDS_AUTH =\n \"No Cursor API key available. Run `opencode auth login` and choose Cursor, or set CURSOR_API_KEY.\";\n\n/**\n * Request approval for a sensitive Cursor invocation. `context.ask` is the\n * opencode mechanism a custom tool uses to gate itself; it honors the user's\n * `permission` config (allow resolves silently, ask prompts, deny rejects).\n *\n * Returns `{ ok: true }` when approved, or `{ ok: false, reason }` when the\n * request was rejected. We deliberately do not claim the rejection was a policy\n * \"deny\" — `context.ask` rejects on both an explicit deny and an internal\n * failure, and conflating them produces misleading messages. The gate is\n * fail-closed: any rejection (including a host that doesn't provide `ask`)\n * blocks the call rather than silently allowing it.\n */\nasync function requestApproval(\n context: ToolContext,\n permission: string,\n patterns: string[],\n metadata: Record<string, unknown>,\n): Promise<{ ok: boolean; reason?: string }> {\n try {\n await context.ask({ permission, patterns, always: patterns, metadata });\n return { ok: true };\n } catch (err) {\n return { ok: false, reason: err instanceof Error ? err.message : String(err) };\n }\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * Build the Cursor delegation tools that complement the native provider:\n * - `cursor_cloud_agent`: run a background agent on a remote repo (optionally\n * opening a PR) — work that maps poorly onto the synchronous provider path.\n * - `cursor_delegate`: run a single local Cursor turn as a permission-gated,\n * auditable tool call (for users who want Cursor as a delegate rather than\n * as their primary model).\n *\n * Both are gated via `context.ask`, so a user `permission` policy controls them.\n */\nexport function buildCursorTools(deps: CursorToolDeps): Record<string, ToolDefinition> {\n return {\n cursor_cloud_agent: tool({\n description:\n \"Launch a Cursor background ('cloud') agent on a remote repository. Runs autonomously \" +\n \"(may take minutes) and can open a pull request. Returns the cloud agent id, final \" +\n \"status, result, and PR url when available.\",\n args: {\n prompt: s.string().describe(\"The task/instruction for the background agent.\"),\n repoUrl: s\n .string()\n .describe(\"Target repository URL, e.g. https://github.com/owner/repo.\"),\n startingRef: s\n .string()\n .optional()\n .describe(\"Branch or ref to start from (defaults to the repo default branch).\"),\n model: s.string().optional().describe(\"Cursor model id (optional for cloud).\"),\n mode: s.enum([\"agent\", \"plan\"]).optional().describe(\"Conversation mode.\"),\n thinking: s.string().optional().describe(\"Thinking level, e.g. 'high'.\"),\n autoCreatePR: s\n .boolean()\n .optional()\n .describe(\"Open a pull request automatically when finished.\"),\n workOnCurrentBranch: s\n .boolean()\n .optional()\n .describe(\"Operate on the current branch instead of creating a new one.\"),\n },\n execute: async (args, context) => {\n const apiKey = deps.resolveApiKey();\n if (!apiKey) return NEEDS_AUTH;\n\n const approval = await requestApproval(\n context,\n \"cursor_cloud_agent\",\n [args.repoUrl],\n { repoUrl: args.repoUrl, autoCreatePR: args.autoCreatePR ?? false },\n );\n if (!approval.ok) {\n return `Cloud agent not approved for ${args.repoUrl}${approval.reason ? `: ${approval.reason}` : \".\"}`;\n }\n\n let result;\n try {\n result = await runCloudAgent({\n apiKey,\n prompt: args.prompt,\n repoUrl: args.repoUrl,\n ...(args.startingRef ? { startingRef: args.startingRef } : {}),\n ...(args.model ? { model: args.model } : {}),\n ...(args.mode ? { mode: args.mode } : {}),\n ...(args.thinking ? { thinking: args.thinking } : {}),\n ...(args.autoCreatePR !== undefined ? { autoCreatePR: args.autoCreatePR } : {}),\n ...(args.workOnCurrentBranch !== undefined\n ? { workOnCurrentBranch: args.workOnCurrentBranch }\n : {}),\n abortSignal: context.abort,\n });\n } catch (err) {\n return `Cloud agent failed: ${errorMessage(err)}`;\n }\n\n const lines = [\n `Cloud agent ${result.agentId} — ${result.status}`,\n ...(result.prUrl ? [`PR: ${result.prUrl}`] : []),\n ...(result.branches.length > 0\n ? [`Branches: ${result.branches.map((b) => b.branch ?? b.repoUrl).join(\", \")}`]\n : []),\n ...(result.result ? [\"\", result.result] : []),\n ...(result.progress.length > 0 ? [\"\", \"Progress:\", ...result.progress] : []),\n ];\n\n return {\n title: `Cursor cloud agent (${result.status})`,\n output: lines.join(\"\\n\"),\n metadata: {\n agentId: result.agentId,\n status: result.status,\n prUrl: result.prUrl ?? null,\n durationMs: result.durationMs ?? null,\n },\n };\n },\n }),\n\n cursor_delegate: tool({\n description:\n \"Delegate a single subtask to a local Cursor agent and return its result. Use to hand \" +\n \"off discrete work to Cursor while keeping your primary model in control. Permission-gated.\",\n args: {\n prompt: s.string().describe(\"The subtask to delegate to Cursor.\"),\n model: s.string().describe(\"Cursor model id to run the delegation on.\"),\n mode: s.enum([\"agent\", \"plan\"]).optional().describe(\"Conversation mode.\"),\n thinking: s.string().optional().describe(\"Thinking level, e.g. 'high'.\"),\n cwd: s\n .string()\n .optional()\n .describe(\"Working directory (defaults to the session directory).\"),\n sandbox: s.boolean().optional().describe(\"Run the agent's tools in Cursor's sandbox.\"),\n agentId: s\n .string()\n .optional()\n .describe(\"Resume a specific Cursor agent id instead of starting fresh.\"),\n },\n execute: async (args, context) => {\n const apiKey = deps.resolveApiKey();\n if (!apiKey) return NEEDS_AUTH;\n\n const approval = await requestApproval(context, \"cursor_delegate\", [args.model], {\n model: args.model,\n prompt: args.prompt,\n });\n if (!approval.ok) {\n return `Delegation to ${args.model} not approved${approval.reason ? `: ${approval.reason}` : \".\"}`;\n }\n\n let result;\n try {\n result = await runDelegate({\n apiKey,\n prompt: args.prompt,\n model: args.model,\n cwd: args.cwd ?? context.directory ?? deps.defaultCwd(),\n ...(args.mode ? { mode: args.mode } : {}),\n ...(args.thinking ? { thinking: args.thinking } : {}),\n ...(args.sandbox !== undefined ? { sandbox: args.sandbox } : {}),\n ...(args.agentId ? { agentId: args.agentId } : {}),\n abortSignal: context.abort,\n });\n } catch (err) {\n return `Delegation failed: ${errorMessage(err)}`;\n }\n\n const toolNote =\n result.toolActivity.length > 0\n ? `\\n\\n(${result.toolActivity.length} tool call(s)` +\n `${result.toolActivity.some((t) => t.isError) ? \", some failed\" : \"\"})`\n : \"\";\n\n return {\n title: `Cursor delegate (${args.model})`,\n output: (result.text || \"(no text output)\") + toolNote,\n metadata: {\n agentId: result.agentId,\n model: args.model,\n toolCalls: result.toolActivity.length,\n usage: result.usage ?? null,\n },\n };\n },\n }),\n };\n}\n","import type { AgentModeOption, ConversationStep, InteractionUpdate } from \"@cursor/sdk\";\nimport { loadCursorSdk } from \"../cursor-runtime.js\";\nimport { buildModelSelection } from \"./controls.js\";\n\n/**\n * A target repository for a cloud agent. Cursor's cloud runtime accepts an\n * array of repos; the tool surface exposes the common single-repo case.\n */\nexport interface CloudRepoTarget {\n url: string;\n startingRef?: string;\n}\n\nexport interface CloudAgentParams {\n apiKey: string;\n /** The instruction/task for the background agent. */\n prompt: string;\n /** Target repository URL (e.g. https://github.com/owner/repo). */\n repoUrl: string;\n /** Branch/ref to start from. Defaults to the repo's default branch. */\n startingRef?: string;\n /** Cursor model id. Optional for cloud (server picks a default otherwise). */\n model?: string;\n /** Conversation mode; defaults to \"agent\". */\n mode?: AgentModeOption;\n /** Convenience for the Cursor `thinking` model param (e.g. \"high\"). */\n thinking?: string;\n /** When true, open a PR automatically once the agent finishes. */\n autoCreatePR?: boolean;\n /** Operate on the current branch instead of creating a new one. */\n workOnCurrentBranch?: boolean;\n /** Cancels the run when aborted (wired to the tool's abort signal). */\n abortSignal?: AbortSignal;\n}\n\nexport interface CloudAgentBranch {\n repoUrl: string;\n branch?: string;\n prUrl?: string;\n}\n\nexport interface CloudAgentResult {\n agentId: string;\n /** Terminal run status: \"finished\" | \"error\" | \"cancelled\". */\n status: string;\n /** The agent's final textual result, when present. */\n result?: string;\n /** First PR url found across result branches (when `autoCreatePR`). */\n prUrl?: string;\n /** Per-repo branch/PR info reported by the run. */\n branches: CloudAgentBranch[];\n durationMs?: number;\n /** Human-readable progress lines captured from status/step/summary updates. */\n progress: string[];\n}\n\n/**\n * Run a Cursor background (\"cloud\") agent against a remote repository and wait\n * for it to finish, returning the final status, result text, and any PR url.\n *\n * A cloud agent can run for minutes and produce a PR rather than a chat reply,\n * which maps poorly onto the synchronous provider `doStream` path — so this is\n * exposed as an opencode tool instead (see plugin/index.ts). Progress is\n * collected into `progress[]` (opencode custom tools return a single result\n * rather than a live stream) and the lifecycle is bridged through the same\n * `loadCursorSdk` plumbing the provider uses.\n */\nexport async function runCloudAgent(params: CloudAgentParams): Promise<CloudAgentResult> {\n const { Agent } = await loadCursorSdk();\n const modelSelection = params.model\n ? buildModelSelection(params.model, params.thinking ? { thinking: params.thinking } : undefined)\n : undefined;\n const mode: AgentModeOption = params.mode ?? \"agent\";\n\n const createOptions = {\n apiKey: params.apiKey,\n ...(modelSelection ? { model: modelSelection } : {}),\n mode,\n cloud: {\n repos: [\n {\n url: params.repoUrl,\n ...(params.startingRef ? { startingRef: params.startingRef } : {}),\n },\n ],\n ...(params.autoCreatePR !== undefined ? { autoCreatePR: params.autoCreatePR } : {}),\n ...(params.workOnCurrentBranch !== undefined\n ? { workOnCurrentBranch: params.workOnCurrentBranch }\n : {}),\n },\n };\n\n const progress: string[] = [];\n const agent = await Agent.create(createOptions);\n\n // `onDelta` carries fine-grained updates; for a cloud (background) run the\n // higher-signal progress arrives via `onStep` (whole conversation steps) and\n // `run.onDidChangeStatus`. We capture all three — whichever the runtime emits.\n const onDelta = ({ update }: { update: InteractionUpdate }) => {\n if (update.type === \"summary\") progress.push(`summary: ${update.summary}`);\n };\n\n const onStep = ({ step }: { step: ConversationStep }) => {\n progress.push(`step: ${describeStep(step)}`);\n };\n\n try {\n const run = await agent.send(params.prompt, { mode, onDelta, onStep });\n\n const off = run.onDidChangeStatus?.((status: string) => {\n progress.push(`status: ${status}`);\n });\n const onAbort = () => {\n run.cancel().catch(() => {});\n };\n params.abortSignal?.addEventListener(\"abort\", onAbort);\n\n try {\n const result = await run.wait();\n const branches: CloudAgentBranch[] = (result.git?.branches ?? []).map((b) => ({\n repoUrl: b.repoUrl,\n ...(b.branch ? { branch: b.branch } : {}),\n ...(b.prUrl ? { prUrl: b.prUrl } : {}),\n }));\n const prUrl = branches.find((b) => b.prUrl)?.prUrl;\n return {\n agentId: agent.agentId,\n status: result.status,\n ...(result.result !== undefined ? { result: result.result } : {}),\n ...(prUrl ? { prUrl } : {}),\n branches,\n ...(result.durationMs !== undefined ? { durationMs: result.durationMs } : {}),\n progress,\n };\n } finally {\n off?.();\n params.abortSignal?.removeEventListener(\"abort\", onAbort);\n }\n } finally {\n try {\n agent.close();\n } catch {\n // best effort; cloud agents persist server-side regardless.\n }\n }\n}\n\n/** A short, log-friendly description of a conversation step for progress output. */\nfunction describeStep(step: ConversationStep): string {\n if (step.type === \"toolCall\") return `toolCall:${step.message.type}`;\n return step.type;\n}\n","import type { AgentModeOption } from \"@cursor/sdk\";\nimport type { CursorUsage } from \"./agent-events.js\";\nimport { streamAgentTurn } from \"./agent-events.js\";\nimport { resolveControls } from \"./controls.js\";\nimport { acquireAgent } from \"./session-pool.js\";\n\nexport interface DelegateParams {\n apiKey: string;\n /** The subtask to delegate to the Cursor agent. */\n prompt: string;\n /** Cursor model id to run the delegation on. */\n model: string;\n /** Conversation mode; defaults to \"agent\". */\n mode?: AgentModeOption;\n /** Convenience for the Cursor `thinking` model param (e.g. \"high\"). */\n thinking?: string;\n /** Working directory the local agent operates in. */\n cwd: string;\n /** Run the agent's tools inside Cursor's sandbox. */\n sandbox?: boolean;\n /** Resume a specific Cursor agent by id instead of creating a fresh one. */\n agentId?: string;\n /** Cancels the run when aborted (wired to the tool's abort signal). */\n abortSignal?: AbortSignal;\n}\n\nexport interface DelegateToolActivity {\n name: string;\n isError: boolean;\n}\n\nexport interface DelegateResult {\n agentId: string;\n text: string;\n reasoning: string;\n toolActivity: DelegateToolActivity[];\n usage?: CursorUsage;\n}\n\n/**\n * Run a single delegated turn on a fresh (or explicitly resumed) local Cursor\n * agent and aggregate the outcome into a plain result. This backs the opt-in\n * `cursor_delegate` tool, which gives users a permission-gated boundary around\n * Cursor (the provider path runs Cursor's own loop without per-call gating).\n *\n * Reuses the provider's `acquireAgent` + `streamAgentTurn` plumbing; the turn\n * is consumed eagerly here because a tool returns a single result rather than a\n * live stream.\n */\nexport async function runDelegate(params: DelegateParams): Promise<DelegateResult> {\n const { mode, modelSelection } = resolveControls(\n params.model,\n {\n mode: params.mode ?? \"agent\",\n ...(params.thinking ? { params: { thinking: params.thinking } } : {}),\n },\n undefined,\n );\n\n const acquired = await acquireAgent({\n apiKey: params.apiKey,\n modelSelection,\n mode,\n cwd: params.cwd,\n ...(params.sandbox !== undefined ? { sandbox: params.sandbox } : {}),\n ...(params.agentId ? { agentId: params.agentId } : {}),\n session: false,\n });\n\n const text: string[] = [];\n const reasoning: string[] = [];\n const toolActivity: DelegateToolActivity[] = [];\n let usage: CursorUsage | undefined;\n\n try {\n for await (const event of streamAgentTurn(\n acquired.agent,\n { text: params.prompt },\n { mode, ...(params.abortSignal ? { abortSignal: params.abortSignal } : {}) },\n )) {\n switch (event.type) {\n case \"text-delta\":\n text.push(event.text);\n break;\n case \"reasoning-delta\":\n reasoning.push(event.text);\n break;\n case \"tool-call\":\n toolActivity.push({ name: event.name, isError: false });\n break;\n case \"tool-result\":\n if (event.isError) toolActivity.push({ name: event.name, isError: true });\n break;\n case \"usage\":\n usage = event.usage;\n break;\n case \"finish\":\n // The aggregated result text; prefer it when deltas were absent.\n if (event.text && text.length === 0) text.push(event.text);\n break;\n }\n }\n } finally {\n acquired.release();\n }\n\n return {\n agentId: acquired.agent.agentId,\n text: text.join(\"\"),\n reasoning: reasoning.join(\"\"),\n toolActivity,\n ...(usage ? { usage } : {}),\n };\n}\n","import type { Plugin } from \"@opencode-ai/plugin\";\nimport type { Auth } from \"@opencode-ai/sdk/v2\";\nimport { resolveCursorApiKey } from \"../api-key.js\";\nimport { discoverModels, toOpencodeModels } from \"../model-discovery.js\";\nimport { buildModelV2Map, PROVIDER_ID, providerNpm } from \"./model-v2.js\";\nimport { translateMcpServers } from \"./mcp-config.js\";\nimport { buildCursorTools } from \"./cursor-tools.js\";\n\nfunction apiKeyFromAuth(auth: Auth | undefined): string | undefined {\n return auth?.type === \"api\" ? auth.key : undefined;\n}\n\n/**\n * opencode plugin that adds a \"Cursor\" provider backed by the official Cursor\n * SDK (`@cursor/sdk`).\n *\n * - `auth`: registers an API-key login for Cursor and a `loader` that feeds the\n * key into the AI-SDK provider factory. The key is validated on first use\n * (model discovery / first call), not at login — see the note on `methods`.\n * - `config`: registers the provider (npm package + discovered/fallback models)\n * so it shows up in opencode immediately.\n * - `provider.models`: auth-aware live model discovery via `Cursor.models.list`.\n * - `tool.cursor_refresh_models`: force-refresh the model catalog.\n */\nexport const CursorPlugin: Plugin = async (input) => {\n // The Cursor API key resolved by opencode's auth loader, captured so the\n // delegation tools (which don't receive auth directly) can reuse it. Falls\n // back to the CURSOR_API_KEY env var when the loader hasn't run.\n let capturedApiKey: string | undefined;\n\n return {\n auth: {\n provider: PROVIDER_ID,\n loader: async (getAuth) => {\n const apiKey = resolveCursorApiKey(apiKeyFromAuth(await getAuth().catch(() => undefined)));\n if (apiKey) {\n capturedApiKey = apiKey;\n // The `config` hook (which seeds opencode's model picker) runs without\n // a key. Warm the catalog cache here — the loader is the hook that\n // reliably has the key — so the next launch seeds the full live\n // catalog instead of the static fallback. Fire-and-forget: discovery\n // never throws and must not block auth/provider load.\n void discoverModels({ apiKey });\n }\n return apiKey ? { apiKey } : {};\n },\n // A single API-key method. opencode always shows its built-in \"Enter your\n // API key\" prompt for `type: \"api\"`, so we intentionally do NOT declare\n // custom `prompts` (that asks for the key a second time) or an `authorize`\n // callback. opencode only passes `authorize` the *custom-prompt* inputs —\n // never the built-in key — so validating the key in `authorize` would\n // force that redundant extra prompt. Instead the key is validated on first\n // use (model discovery / the first call both surface a bad key clearly).\n methods: [{ type: \"api\", label: \"Cursor API Key\" }],\n },\n\n config: async (config) => {\n const { models } = await discoverModels({});\n config.provider ??= {};\n const existing = config.provider[PROVIDER_ID] ?? {};\n const existingOptions = (existing.options ?? {}) as Record<string, unknown>;\n\n // Forward opencode's configured MCP servers (e.g. Serena) to the Cursor\n // agent so it can use the same servers. Opt out via\n // `provider.cursor.options.forwardMcp: false`.\n const forwardMcp = existingOptions[\"forwardMcp\"] !== false;\n const userMcp = (existingOptions[\"mcpServers\"] ?? {}) as Record<string, unknown>;\n const mcpServers = forwardMcp\n ? { ...userMcp, ...translateMcpServers(config.mcp) }\n : userMcp;\n\n config.provider[PROVIDER_ID] = {\n name: \"Cursor\",\n npm: providerNpm(),\n ...existing,\n options: {\n ...existingOptions,\n ...(Object.keys(mcpServers).length > 0 ? { mcpServers } : {}),\n },\n models: { ...toOpencodeModels(models), ...(existing.models ?? {}) },\n };\n },\n\n provider: {\n id: PROVIDER_ID,\n models: async (_provider, ctx) => {\n const apiKey = apiKeyFromAuth(ctx.auth);\n const { models } = await discoverModels({ apiKey });\n return buildModelV2Map(models);\n },\n },\n\n // Bridge opencode's session id to the provider: it lands in\n // providerOptions.cursor.sessionID, which the provider reads to pool/resume a\n // Cursor agent per session (when the `session` option is enabled).\n //\n // Also map opencode's plan AGENT to Cursor's plan mode. This hook fires\n // after opencode merges the selected variant into `output.options`, so an\n // explicit mode from the `plan` variant (or model options) wins — the\n // agent-based default only applies when no mode was set.\n \"chat.params\": async (input, output) => {\n if (input.model?.providerID !== PROVIDER_ID) return;\n output.options = { ...(output.options ?? {}), sessionID: input.sessionID };\n if (input.agent === \"plan\" && output.options[\"mode\"] === undefined) {\n output.options[\"mode\"] = \"plan\";\n }\n },\n\n tool: {\n cursor_refresh_models: {\n description:\n \"Refresh the live Cursor model catalog (bypasses the 24h cache) and report the available models.\",\n args: {},\n execute: async () => {\n const result = await discoverModels({ forceRefresh: true });\n const lines = result.models.map((m) => `- ${m.id} — ${m.displayName}`);\n const header =\n result.source === \"live\"\n ? `Refreshed ${result.models.length} Cursor models (live):`\n : `Could not fetch live models (${result.source}). ${result.warning ?? \"\"}`.trim();\n return {\n title: `Cursor models (${result.source})`,\n output: [header, ...lines].join(\"\\n\"),\n metadata: { source: result.source, count: result.models.length },\n };\n },\n },\n // Delegation tools that complement the provider: a cloud/background agent\n // and a permission-gated local delegate. They resolve the Cursor key from\n // the auth loader (captured above) or CURSOR_API_KEY.\n ...buildCursorTools({\n resolveApiKey: () => resolveCursorApiKey(capturedApiKey),\n defaultCwd: () => input?.directory ?? process.cwd(),\n }),\n },\n };\n};\n\nexport default CursorPlugin;\n"],"mappings":"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","names":["input"]}
|
package/dist/provider/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
resolveControls,
|
|
4
4
|
resolveCursorApiKey,
|
|
5
5
|
streamAgentTurn
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-D4YQ7ZEM.js";
|
|
7
7
|
|
|
8
8
|
// src/provider/index.ts
|
|
9
9
|
import { NoSuchModelError } from "@ai-sdk/provider";
|
|
@@ -180,6 +180,7 @@ function cursorEventsToStream(events, toolDisplay = "reasoning") {
|
|
|
180
180
|
async start(controller) {
|
|
181
181
|
controller.enqueue({ type: "stream-start", warnings: [] });
|
|
182
182
|
let textId;
|
|
183
|
+
let textCount = 0;
|
|
183
184
|
let reasoningId;
|
|
184
185
|
let reasoningCount = 0;
|
|
185
186
|
let usage;
|
|
@@ -197,15 +198,22 @@ function cursorEventsToStream(events, toolDisplay = "reasoning") {
|
|
|
197
198
|
reasoningId = void 0;
|
|
198
199
|
}
|
|
199
200
|
};
|
|
201
|
+
const closeText = () => {
|
|
202
|
+
if (textId) {
|
|
203
|
+
controller.enqueue({ type: "text-end", id: textId });
|
|
204
|
+
textId = void 0;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
200
207
|
const ensureText = () => {
|
|
201
208
|
closeReasoning();
|
|
202
209
|
if (!textId) {
|
|
203
|
-
textId =
|
|
210
|
+
textId = `text-${textCount++}`;
|
|
204
211
|
controller.enqueue({ type: "text-start", id: textId });
|
|
205
212
|
}
|
|
206
213
|
return textId;
|
|
207
214
|
};
|
|
208
215
|
const ensureReasoning = () => {
|
|
216
|
+
closeText();
|
|
209
217
|
if (!reasoningId) {
|
|
210
218
|
reasoningId = `reasoning-${reasoningCount++}`;
|
|
211
219
|
controller.enqueue({ type: "reasoning-start", id: reasoningId });
|
|
@@ -258,14 +266,14 @@ ${formatToolCall(event.name, event.input)}
|
|
|
258
266
|
}
|
|
259
267
|
closeDanglingToolCalls();
|
|
260
268
|
closeReasoning();
|
|
261
|
-
|
|
269
|
+
closeText();
|
|
262
270
|
controller.enqueue({ type: "finish", usage: usage ?? EMPTY_USAGE, finishReason: FINISH_STOP });
|
|
263
271
|
controller.close();
|
|
264
272
|
} catch (err) {
|
|
265
273
|
controller.enqueue({ type: "error", error: err });
|
|
266
274
|
closeDanglingToolCalls();
|
|
267
275
|
closeReasoning();
|
|
268
|
-
|
|
276
|
+
closeText();
|
|
269
277
|
controller.enqueue({ type: "finish", usage: usage ?? EMPTY_USAGE, finishReason: FINISH_ERROR });
|
|
270
278
|
controller.close();
|
|
271
279
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/provider/index.ts","../../src/provider/language-model.ts","../../src/provider/message-map.ts","../../src/provider/stream-map.ts"],"sourcesContent":["import type { EmbeddingModelV3, ImageModelV3, ProviderV3 } from \"@ai-sdk/provider\";\nimport { NoSuchModelError } from \"@ai-sdk/provider\";\nimport type { AgentDefinition, AgentModeOption, McpServerConfig, SettingSource } from \"@cursor/sdk\";\nimport { resolveCursorApiKey } from \"../api-key.js\";\nimport { CursorLanguageModel, type CursorModelConfig } from \"./language-model.js\";\nimport type { ToolDisplay } from \"./stream-map.js\";\n\nexport interface CursorProviderOptions {\n /**\n * Cursor API key. opencode passes this from the provider's resolved auth /\n * options. When omitted, falls back to the CURSOR_API_KEY environment\n * variable at call time.\n */\n apiKey?: string;\n /** Provider id, supplied by opencode as `name`. Defaults to \"cursor\". */\n name?: string;\n /** Working directory for the local Cursor agent. Defaults to process.cwd(). */\n cwd?: string;\n /** Default conversation mode: \"agent\" (default) or \"plan\". Overridable per-request. */\n mode?: AgentModeOption;\n /** Default Cursor model params (id -> value), e.g. { thinking: \"high\" }. */\n params?: Record<string, string>;\n /**\n * MCP servers to make available to the Cursor agent, keyed by name. The\n * plugin's `config` hook populates this by translating opencode's configured\n * `config.mcp` servers, so the agent can use the same MCP servers (e.g.\n * Serena) that opencode does.\n */\n mcpServers?: Record<string, McpServerConfig>;\n /**\n * Cursor settings layers to load from the local filesystem (\"project\",\n * \"user\", \"all\", ...). Enables the agent to pick up your Cursor skills,\n * rules, and `.cursor/mcp.json` servers.\n */\n settingSources?: SettingSource[];\n /** Run the agent's tools inside Cursor's sandbox. */\n sandbox?: boolean;\n /** Cursor subagent definitions (`{ description, prompt, model?, mcpServers? }`). */\n agents?: Record<string, AgentDefinition>;\n /**\n * Reuse one Cursor agent per opencode session (resume across turns instead of\n * creating a fresh agent each turn). Off by default.\n */\n session?: boolean;\n /**\n * How Cursor's internal tool activity (shell/read/edit/mcp/…) is surfaced:\n * - `\"reasoning\"` (default): compact reasoning lines (works on every host).\n * - `\"blocks\"`: structured provider-executed `tool-call`/`tool-result` parts\n * so opencode renders proper tool blocks. Opt-in; requires a V3-native host.\n */\n toolDisplay?: ToolDisplay;\n}\n\n/**\n * Cursor provider for the Vercel AI SDK (V3), backed by the official\n * `@cursor/sdk` local agent runtime.\n *\n * opencode loads this package by its `npm` provider config, finds the export\n * whose name starts with `create`, calls it with `{ name, apiKey, ...options }`,\n * and then calls `.languageModel(modelId)`.\n */\nexport function createCursor(options: CursorProviderOptions = {}): ProviderV3 {\n const mcpServers =\n options.mcpServers && Object.keys(options.mcpServers).length > 0 ? options.mcpServers : undefined;\n const config: CursorModelConfig = {\n providerName: options.name ?? \"cursor\",\n apiKey: resolveCursorApiKey(options.apiKey),\n cwd: options.cwd ?? process.cwd(),\n mode: options.mode ?? \"agent\",\n ...(options.params ? { params: options.params } : {}),\n ...(mcpServers ? { mcpServers } : {}),\n ...(options.settingSources ? { settingSources: options.settingSources } : {}),\n ...(options.sandbox !== undefined ? { sandbox: options.sandbox } : {}),\n ...(options.agents ? { agents: options.agents } : {}),\n ...(options.session !== undefined ? { session: options.session } : {}),\n ...(options.toolDisplay ? { toolDisplay: options.toolDisplay } : {}),\n };\n\n const notImplemented = (kind: string, modelId: string): never => {\n throw new NoSuchModelError({\n modelId,\n modelType: kind as \"languageModel\",\n message: `The Cursor provider does not support ${kind} models.`,\n });\n };\n\n return {\n specificationVersion: \"v3\",\n languageModel: (modelId: string) => new CursorLanguageModel(modelId, config),\n embeddingModel: (modelId: string): EmbeddingModelV3 =>\n notImplemented(\"embeddingModel\", modelId),\n imageModel: (modelId: string): ImageModelV3 => notImplemented(\"imageModel\", modelId),\n };\n}\n","import type {\n LanguageModelV3,\n LanguageModelV3CallOptions,\n LanguageModelV3Content,\n LanguageModelV3FinishReason,\n LanguageModelV3StreamPart,\n LanguageModelV3Usage,\n} from \"@ai-sdk/provider\";\nimport { LoadAPIKeyError } from \"@ai-sdk/provider\";\nimport type {\n AgentDefinition,\n McpServerConfig,\n SettingSource,\n AgentModeOption,\n} from \"@cursor/sdk\";\nimport { resolveCursorApiKey } from \"../api-key.js\";\nimport { latestUserMessage, promptToCursorMessage } from \"./message-map.js\";\nimport { streamAgentTurn, type CursorEvent } from \"./agent-events.js\";\nimport { cursorEventsToContent, cursorEventsToStream, type ToolDisplay } from \"./stream-map.js\";\nimport { resolveControls } from \"./controls.js\";\nimport { acquireAgent } from \"./session-pool.js\";\n\nexport interface CursorModelConfig {\n /** Provider id used for logging and the providerOptions key (e.g. \"cursor\"). */\n providerName: string;\n /** Explicit API key; re-resolved against the env at call time when absent. */\n apiKey?: string;\n /** Working directory the local Cursor agent operates in. */\n cwd: string;\n /** Default conversation mode; overridable per-request via providerOptions. */\n mode: AgentModeOption;\n /** Default Cursor model params (id -> value); overridable per-request. */\n params?: Record<string, string>;\n /** MCP servers forwarded to the Cursor agent (e.g. opencode's Serena). */\n mcpServers?: Record<string, McpServerConfig>;\n /** Cursor settings layers to load from disk (skills, rules, .cursor/mcp.json). */\n settingSources?: SettingSource[];\n /** Run the agent's tools inside Cursor's sandbox. */\n sandbox?: boolean;\n /** Cursor subagent definitions made available to the agent. */\n agents?: Record<string, AgentDefinition>;\n /**\n * Reuse one Cursor agent per opencode session (resume across turns, sending\n * only the new message). Off by default; the default per-turn-fresh path\n * re-sends the full transcript and is robust to opencode's non-chat calls.\n */\n session?: boolean;\n /**\n * How Cursor's internal tool activity is surfaced (see {@link ToolDisplay}).\n * Defaults to `\"reasoning\"`.\n */\n toolDisplay?: ToolDisplay;\n}\n\n/**\n * A Vercel AI SDK `LanguageModelV3` backed by a local Cursor agent. opencode\n * loads this via the provider factory and calls `doStream` / `doGenerate`.\n * The event→stream translation lives in stream-map.ts so it can be unit tested\n * without a live agent.\n */\nexport class CursorLanguageModel implements LanguageModelV3 {\n readonly specificationVersion = \"v3\" as const;\n readonly modelId: string;\n readonly provider: string;\n // Images are passed inline as base64 data, so no URLs are fetched natively.\n readonly supportedUrls: Record<string, RegExp[]> = {};\n\n constructor(\n modelId: string,\n private readonly config: CursorModelConfig,\n ) {\n this.modelId = modelId;\n this.provider = config.providerName;\n }\n\n private requireApiKey(): string {\n const apiKey = resolveCursorApiKey(this.config.apiKey);\n if (!apiKey) {\n throw new LoadAPIKeyError({\n message:\n \"Cursor API key missing. Run `opencode auth login` and choose Cursor, or set CURSOR_API_KEY.\",\n });\n }\n return apiKey;\n }\n\n private async *agentRun(options: LanguageModelV3CallOptions): AsyncGenerator<CursorEvent> {\n // opencode delivers per-request controls (merged model options + selected\n // variant) under providerOptions keyed by our provider id. The session id is\n // injected there by the plugin's chat.params hook.\n const providerOptions = options.providerOptions?.[this.provider] as\n | Record<string, unknown>\n | undefined;\n const { mode, modelSelection } = resolveControls(\n this.modelId,\n { mode: this.config.mode, params: this.config.params },\n providerOptions,\n );\n const sessionID =\n typeof providerOptions?.[\"sessionID\"] === \"string\"\n ? (providerOptions[\"sessionID\"] as string)\n : undefined;\n const useSession = this.config.session === true && Boolean(sessionID);\n // Power users can resume a specific Cursor agent via\n // `providerOptions.cursor.agentId`; it takes precedence over session pooling.\n const explicitAgentId =\n typeof providerOptions?.[\"agentId\"] === \"string\"\n ? (providerOptions[\"agentId\"] as string)\n : undefined;\n\n const acquired = await acquireAgent({\n apiKey: this.requireApiKey(),\n modelSelection,\n mode,\n cwd: this.config.cwd,\n ...(this.config.settingSources ? { settingSources: this.config.settingSources } : {}),\n ...(this.config.sandbox !== undefined ? { sandbox: this.config.sandbox } : {}),\n ...(this.config.mcpServers ? { mcpServers: this.config.mcpServers } : {}),\n ...(this.config.agents ? { agents: this.config.agents } : {}),\n ...(useSession ? { name: `opencode/${sessionID!.slice(-8)}` } : {}),\n ...(explicitAgentId ? { agentId: explicitAgentId } : {}),\n sessionID,\n session: useSession,\n });\n\n // A resumed agent already remembers the prior conversation, so send only the\n // new turn; otherwise send the full transcript.\n const message = acquired.resumed\n ? (latestUserMessage(options.prompt) ?? promptToCursorMessage(options.prompt))\n : promptToCursorMessage(options.prompt);\n\n try {\n yield* streamAgentTurn(acquired.agent, message, { mode, abortSignal: options.abortSignal });\n } finally {\n acquired.release();\n }\n }\n\n async doStream(options: LanguageModelV3CallOptions): Promise<{\n stream: ReadableStream<LanguageModelV3StreamPart>;\n }> {\n return { stream: cursorEventsToStream(this.agentRun(options), this.config.toolDisplay) };\n }\n\n async doGenerate(options: LanguageModelV3CallOptions): Promise<{\n content: Array<LanguageModelV3Content>;\n finishReason: LanguageModelV3FinishReason;\n usage: LanguageModelV3Usage;\n warnings: Array<never>;\n }> {\n const result = await cursorEventsToContent(this.agentRun(options), this.config.toolDisplay);\n return { ...result, warnings: [] };\n }\n}\n","import type { LanguageModelV3Prompt } from \"@ai-sdk/provider\";\nimport type { SDKImage, SDKUserMessage } from \"@cursor/sdk\";\n\n/**\n * Convert an AI-SDK prompt (the full conversation opencode sends on every call)\n * into a single Cursor `SDKUserMessage`.\n *\n * The Cursor agent keeps its own per-agent conversation memory, but opencode\n * re-sends the whole history each turn. To stay correct without double-counting\n * context, we create a fresh agent per turn (see language-model.ts) and flatten\n * the entire prompt into one transcript message. Images from the final user\n * turn are attached natively so multimodal models can see them.\n */\nexport function promptToCursorMessage(prompt: LanguageModelV3Prompt): SDKUserMessage {\n const lines: string[] = [];\n const images: SDKImage[] = [];\n\n prompt.forEach((message, index) => {\n const isLast = index === prompt.length - 1;\n switch (message.role) {\n case \"system\":\n lines.push(`# System\\n${message.content}`);\n break;\n case \"user\": {\n const text: string[] = [];\n for (const part of message.content) {\n if (part.type === \"text\") text.push(part.text);\n else if (part.type === \"file\" && part.mediaType.startsWith(\"image/\")) {\n const image = fileToImage(part.data, part.mediaType);\n // Only attach images natively for the final user turn; earlier ones\n // are referenced by transcript order.\n if (isLast && image) images.push(image);\n text.push(\"[image attached]\");\n }\n }\n lines.push(`# User\\n${text.join(\"\\n\")}`);\n break;\n }\n case \"assistant\": {\n const text: string[] = [];\n for (const part of message.content) {\n if (part.type === \"text\") text.push(part.text);\n else if (part.type === \"reasoning\") text.push(`(thinking) ${part.text}`);\n else if (part.type === \"tool-call\") text.push(`[called ${part.toolName}(${part.input})]`);\n else if (part.type === \"tool-result\") text.push(`[result of ${part.toolName}]`);\n }\n lines.push(`# Assistant\\n${text.join(\"\\n\")}`);\n break;\n }\n case \"tool\": {\n for (const part of message.content) {\n if (part.type === \"tool-result\") {\n lines.push(`# Tool result (${part.toolName})\\n${JSON.stringify(part.output)}`);\n }\n }\n break;\n }\n }\n });\n\n const out: SDKUserMessage = { text: lines.join(\"\\n\\n\") };\n if (images.length > 0) out.images = images;\n return out;\n}\n\nfunction fileToImage(\n data: string | Uint8Array | URL,\n mediaType: string,\n): SDKImage | undefined {\n if (data instanceof URL) return { url: data.toString() };\n if (typeof data === \"string\") {\n // Either a URL or already-base64 encoded data.\n if (/^https?:\\/\\//i.test(data)) return { url: data };\n return { data, mimeType: mediaType };\n }\n if (data instanceof Uint8Array) {\n return { data: Buffer.from(data).toString(\"base64\"), mimeType: mediaType };\n }\n return undefined;\n}\n\n/**\n * Extract only the final user turn as a Cursor message. Used when resuming a\n * pooled agent that already remembers the prior conversation, so we send just\n * the new message instead of the whole transcript. Returns `undefined` if the\n * last message isn't a user turn (caller should fall back to the full transcript).\n */\nexport function latestUserMessage(prompt: LanguageModelV3Prompt): SDKUserMessage | undefined {\n const last = prompt[prompt.length - 1];\n if (!last || last.role !== \"user\") return undefined;\n\n const text: string[] = [];\n const images: SDKImage[] = [];\n for (const part of last.content) {\n if (part.type === \"text\") text.push(part.text);\n else if (part.type === \"file\" && part.mediaType.startsWith(\"image/\")) {\n const image = fileToImage(part.data, part.mediaType);\n if (image) images.push(image);\n text.push(\"[image attached]\");\n }\n }\n\n const out: SDKUserMessage = { text: text.join(\"\\n\") };\n if (images.length > 0) out.images = images;\n return out;\n}\n\n","import type {\n LanguageModelV3Content,\n LanguageModelV3FinishReason,\n LanguageModelV3StreamPart,\n LanguageModelV3Usage,\n} from \"@ai-sdk/provider\";\nimport type { CursorEvent, CursorUsage } from \"./agent-events.js\";\n\n/**\n * How Cursor's internal tool activity (shell/read/edit/mcp/…) is surfaced to\n * opencode:\n * - `\"reasoning\"` (default): rendered as compact reasoning lines. Robust on\n * every host — no tool-call parts cross the execution boundary.\n * - `\"blocks\"`: emitted as provider-executed AI-SDK `tool-call`/`tool-result`\n * parts so opencode renders structured tool blocks. The parts must carry\n * BOTH `providerExecuted: true` AND `dynamic: true` — ai's `parseToolCall`\n * (v6, `doParseToolCall`) only exempts that combination from registered-tool\n * validation; without `dynamic` an unknown name raises `NoSuchToolError`,\n * which opencode's `experimental_repairToolCall` rewrites into its \"invalid\"\n * tool. Names are also prefixed (`cursor_…`) so they can never collide with\n * a tool opencode has registered (`read`, `grep`, `task`, …) — a colliding\n * name is validated against that tool's input schema instead of being\n * treated as dynamic.\n */\nexport type ToolDisplay = \"reasoning\" | \"blocks\";\n\nconst FINISH_STOP: LanguageModelV3FinishReason = { unified: \"stop\", raw: undefined };\nconst FINISH_ERROR: LanguageModelV3FinishReason = { unified: \"error\", raw: undefined };\n\nfunction safeJsonString(input: unknown): string {\n try {\n return typeof input === \"string\" ? input : JSON.stringify(input ?? {});\n } catch {\n return \"{}\";\n }\n}\n\n/**\n * Tool name as it crosses into opencode in \"blocks\" mode. Prefixed so it can\n * never collide with a tool opencode has registered, and sanitized because MCP\n * names contain `/` (e.g. `serena/find_symbol` → `cursor_serena_find_symbol`).\n */\nfunction blockToolName(name: string): string {\n return `cursor_${name.replace(/[^A-Za-z0-9_-]/g, \"_\")}`;\n}\n\n/**\n * Build a provider-executed dynamic `tool-call` stream part (V3). `input` is a\n * stringified JSON object per the spec.\n */\nfunction toolCallPart(id: string, name: string, input: unknown): LanguageModelV3StreamPart {\n return {\n type: \"tool-call\",\n toolCallId: id,\n toolName: blockToolName(name),\n input: safeJsonString(input),\n providerExecuted: true,\n dynamic: true,\n } as LanguageModelV3StreamPart;\n}\n\n/**\n * Build a provider-executed dynamic `tool-result` stream part. Per the V3 spec\n * (and ai v6's `runToolsTransformation`, which reads `chunk.result` /\n * `chunk.isError`) the payload goes in `result`; `result` is typed\n * `NonNullable<JSONValue>` so a missing Cursor result is coalesced to `null`\n * and cast.\n */\nfunction toolResultPart(\n id: string,\n name: string,\n result: unknown,\n isError: boolean,\n): LanguageModelV3StreamPart {\n return {\n type: \"tool-result\",\n toolCallId: id,\n toolName: blockToolName(name),\n result: (result ?? null) as never,\n isError,\n providerExecuted: true,\n dynamic: true,\n } as LanguageModelV3StreamPart;\n}\n\n/** Content-item equivalents of the tool parts above, for `doGenerate`. */\nfunction toolCallContent(id: string, name: string, input: unknown): LanguageModelV3Content {\n return {\n type: \"tool-call\",\n toolCallId: id,\n toolName: blockToolName(name),\n input: safeJsonString(input),\n providerExecuted: true,\n dynamic: true,\n } as LanguageModelV3Content;\n}\nfunction toolResultContent(\n id: string,\n name: string,\n result: unknown,\n isError: boolean,\n): LanguageModelV3Content {\n return {\n type: \"tool-result\",\n toolCallId: id,\n toolName: blockToolName(name),\n result: (result ?? null) as never,\n isError,\n providerExecuted: true,\n dynamic: true,\n } as LanguageModelV3Content;\n}\n\nexport const EMPTY_USAGE: LanguageModelV3Usage = {\n inputTokens: { total: undefined, noCache: undefined, cacheRead: undefined, cacheWrite: undefined },\n outputTokens: { total: undefined, text: undefined, reasoning: undefined },\n};\n\nexport function mapUsage(usage: CursorUsage): LanguageModelV3Usage {\n return {\n inputTokens: {\n total: usage.inputTokens,\n noCache: undefined,\n cacheRead: usage.cacheReadTokens,\n cacheWrite: usage.cacheWriteTokens,\n },\n outputTokens: { total: usage.outputTokens, text: undefined, reasoning: undefined },\n };\n}\n\n/**\n * Render Cursor's internal tool activity as a short, human-readable line.\n *\n * Cursor runs its own agent loop and executes its own tools (shell/read/edit/\n * mcp/…). We surface that activity as reasoning text — NOT as AI-SDK\n * `tool-call`/`tool-result` parts. opencode (a V3-native host) only treats\n * registered tools as callable; a provider-executed call naming a tool it\n * doesn't know (e.g. `mcp`, `shell`) is rejected as an \"unavailable tool\".\n * Rendering as reasoning keeps the activity visible without crossing the\n * tool-execution boundary. Tool outputs can be huge (file contents, search\n * dumps), so only the call (name + short arg summary) and error status are\n * shown — never the raw result.\n */\nfunction formatToolCall(name: string, input: unknown): string {\n let arg = \"\";\n try {\n const s = typeof input === \"string\" ? input : JSON.stringify(input);\n if (s && s !== \"{}\" && s !== '\"\"') arg = ` ${s.length > 120 ? `${s.slice(0, 120)}…` : s}`;\n } catch {\n // Non-serializable input; show the name only.\n }\n return `[tool] ${name}${arg}`;\n}\n\n/**\n * Synthetic error payload for a tool call whose completion never arrived\n * (run errored/cancelled/wedged mid-tool). Mirrors Cursor's own\n * `{status:\"error\"}` result union so consumers see a consistent shape.\n * Without a matching result, opencode renders the part as\n * \"Tool execution aborted\" and the block dangles forever.\n */\nconst DANGLING_TOOL_RESULT = {\n status: \"error\",\n error: \"Cursor run ended before this tool call completed.\",\n};\n\n/**\n * Translate the normalized Cursor agent events into an AI-SDK V3 stream.\n *\n * Pure with respect to the event source, so it can be tested by feeding a\n * fixed event sequence (no live agent required). Reasoning blocks are closed\n * before text begins so reasoning/text parts nest cleanly. Tool activity is\n * rendered into the reasoning channel (see {@link formatToolCall}).\n */\nexport function cursorEventsToStream(\n events: AsyncIterable<CursorEvent>,\n toolDisplay: ToolDisplay = \"reasoning\",\n): ReadableStream<LanguageModelV3StreamPart> {\n return new ReadableStream<LanguageModelV3StreamPart>({\n async start(controller) {\n controller.enqueue({ type: \"stream-start\", warnings: [] });\n\n let textId: string | undefined;\n let reasoningId: string | undefined;\n let reasoningCount = 0;\n let usage: LanguageModelV3Usage | undefined;\n let streamedText = false;\n // Open (unanswered) tool calls in blocks mode: id -> original tool name.\n const openToolCalls = new Map<string, string>();\n const closeDanglingToolCalls = () => {\n for (const [id, name] of openToolCalls) {\n controller.enqueue(toolResultPart(id, name, DANGLING_TOOL_RESULT, true));\n }\n openToolCalls.clear();\n };\n\n const closeReasoning = () => {\n if (reasoningId) {\n controller.enqueue({ type: \"reasoning-end\", id: reasoningId });\n reasoningId = undefined;\n }\n };\n const ensureText = () => {\n closeReasoning();\n if (!textId) {\n textId = \"text-0\";\n controller.enqueue({ type: \"text-start\", id: textId });\n }\n return textId;\n };\n const ensureReasoning = () => {\n if (!reasoningId) {\n reasoningId = `reasoning-${reasoningCount++}`;\n controller.enqueue({ type: \"reasoning-start\", id: reasoningId });\n }\n return reasoningId;\n };\n const reasoningLine = (text: string) => {\n controller.enqueue({ type: \"reasoning-delta\", id: ensureReasoning(), delta: text });\n };\n\n try {\n for await (const event of events) {\n switch (event.type) {\n case \"text-delta\":\n streamedText = true;\n controller.enqueue({ type: \"text-delta\", id: ensureText(), delta: event.text });\n break;\n case \"reasoning-delta\":\n reasoningLine(event.text);\n break;\n case \"tool-call\":\n if (toolDisplay === \"blocks\") {\n openToolCalls.set(event.id, event.name);\n controller.enqueue(toolCallPart(event.id, event.name, event.input));\n } else {\n reasoningLine(`\\n${formatToolCall(event.name, event.input)}\\n`);\n }\n break;\n case \"tool-result\":\n if (toolDisplay === \"blocks\") {\n openToolCalls.delete(event.id);\n controller.enqueue(\n toolResultPart(event.id, event.name, event.result, event.isError),\n );\n } else if (event.isError) {\n reasoningLine(`[tool] ${event.name} failed\\n`);\n }\n break;\n case \"usage\":\n usage = mapUsage(event.usage);\n break;\n case \"finish\":\n if (!streamedText && event.text) {\n controller.enqueue({ type: \"text-delta\", id: ensureText(), delta: event.text });\n }\n break;\n }\n }\n\n closeDanglingToolCalls();\n closeReasoning();\n if (textId) controller.enqueue({ type: \"text-end\", id: textId });\n controller.enqueue({ type: \"finish\", usage: usage ?? EMPTY_USAGE, finishReason: FINISH_STOP });\n controller.close();\n } catch (err) {\n controller.enqueue({ type: \"error\", error: err });\n closeDanglingToolCalls();\n closeReasoning();\n if (textId) controller.enqueue({ type: \"text-end\", id: textId });\n controller.enqueue({ type: \"finish\", usage: usage ?? EMPTY_USAGE, finishReason: FINISH_ERROR });\n controller.close();\n }\n },\n });\n}\n\n/**\n * Aggregate the normalized Cursor agent events into a non-streaming result for\n * `doGenerate`. Same event source contract as {@link cursorEventsToStream}.\n * Tool activity is folded into the reasoning text (display only).\n */\nexport async function cursorEventsToContent(\n events: AsyncIterable<CursorEvent>,\n toolDisplay: ToolDisplay = \"reasoning\",\n): Promise<{\n content: Array<LanguageModelV3Content>;\n finishReason: LanguageModelV3FinishReason;\n usage: LanguageModelV3Usage;\n}> {\n const content: Array<LanguageModelV3Content> = [];\n const toolParts: Array<LanguageModelV3Content> = [];\n // Open (unanswered) tool calls in blocks mode: id -> original tool name.\n const openToolCalls = new Map<string, string>();\n let text = \"\";\n let reasoning = \"\";\n let usage: LanguageModelV3Usage = EMPTY_USAGE;\n let finishReason: LanguageModelV3FinishReason = FINISH_STOP;\n\n try {\n for await (const event of events) {\n switch (event.type) {\n case \"text-delta\":\n text += event.text;\n break;\n case \"reasoning-delta\":\n reasoning += event.text;\n break;\n case \"tool-call\":\n if (toolDisplay === \"blocks\") {\n openToolCalls.set(event.id, event.name);\n toolParts.push(toolCallContent(event.id, event.name, event.input));\n } else {\n reasoning += `\\n${formatToolCall(event.name, event.input)}\\n`;\n }\n break;\n case \"tool-result\":\n if (toolDisplay === \"blocks\") {\n openToolCalls.delete(event.id);\n toolParts.push(toolResultContent(event.id, event.name, event.result, event.isError));\n } else if (event.isError) {\n reasoning += `[tool] ${event.name} failed\\n`;\n }\n break;\n case \"usage\":\n usage = mapUsage(event.usage);\n break;\n case \"finish\":\n if (!text && event.text) text = event.text;\n break;\n }\n }\n } catch {\n finishReason = FINISH_ERROR;\n }\n\n // Close out any tool call whose completion never arrived (see DANGLING_TOOL_RESULT).\n for (const [id, name] of openToolCalls) {\n toolParts.push(toolResultContent(id, name, DANGLING_TOOL_RESULT, true));\n }\n openToolCalls.clear();\n\n if (reasoning) content.push({ type: \"reasoning\", text: reasoning });\n content.push(...toolParts);\n if (text) content.push({ type: \"text\", text });\n\n return { content, finishReason, usage };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/provider/index.ts","../../src/provider/language-model.ts","../../src/provider/message-map.ts","../../src/provider/stream-map.ts"],"sourcesContent":["import type { EmbeddingModelV3, ImageModelV3, ProviderV3 } from \"@ai-sdk/provider\";\nimport { NoSuchModelError } from \"@ai-sdk/provider\";\nimport type { AgentDefinition, AgentModeOption, McpServerConfig, SettingSource } from \"@cursor/sdk\";\nimport { resolveCursorApiKey } from \"../api-key.js\";\nimport { CursorLanguageModel, type CursorModelConfig } from \"./language-model.js\";\nimport type { ToolDisplay } from \"./stream-map.js\";\n\nexport interface CursorProviderOptions {\n /**\n * Cursor API key. opencode passes this from the provider's resolved auth /\n * options. When omitted, falls back to the CURSOR_API_KEY environment\n * variable at call time.\n */\n apiKey?: string;\n /** Provider id, supplied by opencode as `name`. Defaults to \"cursor\". */\n name?: string;\n /** Working directory for the local Cursor agent. Defaults to process.cwd(). */\n cwd?: string;\n /** Default conversation mode: \"agent\" (default) or \"plan\". Overridable per-request. */\n mode?: AgentModeOption;\n /** Default Cursor model params (id -> value), e.g. { thinking: \"high\" }. */\n params?: Record<string, string>;\n /**\n * MCP servers to make available to the Cursor agent, keyed by name. The\n * plugin's `config` hook populates this by translating opencode's configured\n * `config.mcp` servers, so the agent can use the same MCP servers (e.g.\n * Serena) that opencode does.\n */\n mcpServers?: Record<string, McpServerConfig>;\n /**\n * Cursor settings layers to load from the local filesystem (\"project\",\n * \"user\", \"all\", ...). Enables the agent to pick up your Cursor skills,\n * rules, and `.cursor/mcp.json` servers.\n */\n settingSources?: SettingSource[];\n /** Run the agent's tools inside Cursor's sandbox. */\n sandbox?: boolean;\n /** Cursor subagent definitions (`{ description, prompt, model?, mcpServers? }`). */\n agents?: Record<string, AgentDefinition>;\n /**\n * Reuse one Cursor agent per opencode session (resume across turns instead of\n * creating a fresh agent each turn). Off by default.\n */\n session?: boolean;\n /**\n * How Cursor's internal tool activity (shell/read/edit/mcp/…) is surfaced:\n * - `\"reasoning\"` (default): compact reasoning lines (works on every host).\n * - `\"blocks\"`: structured provider-executed `tool-call`/`tool-result` parts\n * so opencode renders proper tool blocks. Opt-in; requires a V3-native host.\n */\n toolDisplay?: ToolDisplay;\n}\n\n/**\n * Cursor provider for the Vercel AI SDK (V3), backed by the official\n * `@cursor/sdk` local agent runtime.\n *\n * opencode loads this package by its `npm` provider config, finds the export\n * whose name starts with `create`, calls it with `{ name, apiKey, ...options }`,\n * and then calls `.languageModel(modelId)`.\n */\nexport function createCursor(options: CursorProviderOptions = {}): ProviderV3 {\n const mcpServers =\n options.mcpServers && Object.keys(options.mcpServers).length > 0 ? options.mcpServers : undefined;\n const config: CursorModelConfig = {\n providerName: options.name ?? \"cursor\",\n apiKey: resolveCursorApiKey(options.apiKey),\n cwd: options.cwd ?? process.cwd(),\n mode: options.mode ?? \"agent\",\n ...(options.params ? { params: options.params } : {}),\n ...(mcpServers ? { mcpServers } : {}),\n ...(options.settingSources ? { settingSources: options.settingSources } : {}),\n ...(options.sandbox !== undefined ? { sandbox: options.sandbox } : {}),\n ...(options.agents ? { agents: options.agents } : {}),\n ...(options.session !== undefined ? { session: options.session } : {}),\n ...(options.toolDisplay ? { toolDisplay: options.toolDisplay } : {}),\n };\n\n const notImplemented = (kind: string, modelId: string): never => {\n throw new NoSuchModelError({\n modelId,\n modelType: kind as \"languageModel\",\n message: `The Cursor provider does not support ${kind} models.`,\n });\n };\n\n return {\n specificationVersion: \"v3\",\n languageModel: (modelId: string) => new CursorLanguageModel(modelId, config),\n embeddingModel: (modelId: string): EmbeddingModelV3 =>\n notImplemented(\"embeddingModel\", modelId),\n imageModel: (modelId: string): ImageModelV3 => notImplemented(\"imageModel\", modelId),\n };\n}\n","import type {\n LanguageModelV3,\n LanguageModelV3CallOptions,\n LanguageModelV3Content,\n LanguageModelV3FinishReason,\n LanguageModelV3StreamPart,\n LanguageModelV3Usage,\n} from \"@ai-sdk/provider\";\nimport { LoadAPIKeyError } from \"@ai-sdk/provider\";\nimport type {\n AgentDefinition,\n McpServerConfig,\n SettingSource,\n AgentModeOption,\n} from \"@cursor/sdk\";\nimport { resolveCursorApiKey } from \"../api-key.js\";\nimport { latestUserMessage, promptToCursorMessage } from \"./message-map.js\";\nimport { streamAgentTurn, type CursorEvent } from \"./agent-events.js\";\nimport { cursorEventsToContent, cursorEventsToStream, type ToolDisplay } from \"./stream-map.js\";\nimport { resolveControls } from \"./controls.js\";\nimport { acquireAgent } from \"./session-pool.js\";\n\nexport interface CursorModelConfig {\n /** Provider id used for logging and the providerOptions key (e.g. \"cursor\"). */\n providerName: string;\n /** Explicit API key; re-resolved against the env at call time when absent. */\n apiKey?: string;\n /** Working directory the local Cursor agent operates in. */\n cwd: string;\n /** Default conversation mode; overridable per-request via providerOptions. */\n mode: AgentModeOption;\n /** Default Cursor model params (id -> value); overridable per-request. */\n params?: Record<string, string>;\n /** MCP servers forwarded to the Cursor agent (e.g. opencode's Serena). */\n mcpServers?: Record<string, McpServerConfig>;\n /** Cursor settings layers to load from disk (skills, rules, .cursor/mcp.json). */\n settingSources?: SettingSource[];\n /** Run the agent's tools inside Cursor's sandbox. */\n sandbox?: boolean;\n /** Cursor subagent definitions made available to the agent. */\n agents?: Record<string, AgentDefinition>;\n /**\n * Reuse one Cursor agent per opencode session (resume across turns, sending\n * only the new message). Off by default; the default per-turn-fresh path\n * re-sends the full transcript and is robust to opencode's non-chat calls.\n */\n session?: boolean;\n /**\n * How Cursor's internal tool activity is surfaced (see {@link ToolDisplay}).\n * Defaults to `\"reasoning\"`.\n */\n toolDisplay?: ToolDisplay;\n}\n\n/**\n * A Vercel AI SDK `LanguageModelV3` backed by a local Cursor agent. opencode\n * loads this via the provider factory and calls `doStream` / `doGenerate`.\n * The event→stream translation lives in stream-map.ts so it can be unit tested\n * without a live agent.\n */\nexport class CursorLanguageModel implements LanguageModelV3 {\n readonly specificationVersion = \"v3\" as const;\n readonly modelId: string;\n readonly provider: string;\n // Images are passed inline as base64 data, so no URLs are fetched natively.\n readonly supportedUrls: Record<string, RegExp[]> = {};\n\n constructor(\n modelId: string,\n private readonly config: CursorModelConfig,\n ) {\n this.modelId = modelId;\n this.provider = config.providerName;\n }\n\n private requireApiKey(): string {\n const apiKey = resolveCursorApiKey(this.config.apiKey);\n if (!apiKey) {\n throw new LoadAPIKeyError({\n message:\n \"Cursor API key missing. Run `opencode auth login` and choose Cursor, or set CURSOR_API_KEY.\",\n });\n }\n return apiKey;\n }\n\n private async *agentRun(options: LanguageModelV3CallOptions): AsyncGenerator<CursorEvent> {\n // opencode delivers per-request controls (merged model options + selected\n // variant) under providerOptions keyed by our provider id. The session id is\n // injected there by the plugin's chat.params hook.\n const providerOptions = options.providerOptions?.[this.provider] as\n | Record<string, unknown>\n | undefined;\n const { mode, modelSelection } = resolveControls(\n this.modelId,\n { mode: this.config.mode, params: this.config.params },\n providerOptions,\n );\n const sessionID =\n typeof providerOptions?.[\"sessionID\"] === \"string\"\n ? (providerOptions[\"sessionID\"] as string)\n : undefined;\n const useSession = this.config.session === true && Boolean(sessionID);\n // Power users can resume a specific Cursor agent via\n // `providerOptions.cursor.agentId`; it takes precedence over session pooling.\n const explicitAgentId =\n typeof providerOptions?.[\"agentId\"] === \"string\"\n ? (providerOptions[\"agentId\"] as string)\n : undefined;\n\n const acquired = await acquireAgent({\n apiKey: this.requireApiKey(),\n modelSelection,\n mode,\n cwd: this.config.cwd,\n ...(this.config.settingSources ? { settingSources: this.config.settingSources } : {}),\n ...(this.config.sandbox !== undefined ? { sandbox: this.config.sandbox } : {}),\n ...(this.config.mcpServers ? { mcpServers: this.config.mcpServers } : {}),\n ...(this.config.agents ? { agents: this.config.agents } : {}),\n ...(useSession ? { name: `opencode/${sessionID!.slice(-8)}` } : {}),\n ...(explicitAgentId ? { agentId: explicitAgentId } : {}),\n sessionID,\n session: useSession,\n });\n\n // A resumed agent already remembers the prior conversation, so send only the\n // new turn; otherwise send the full transcript.\n const message = acquired.resumed\n ? (latestUserMessage(options.prompt) ?? promptToCursorMessage(options.prompt))\n : promptToCursorMessage(options.prompt);\n\n try {\n yield* streamAgentTurn(acquired.agent, message, { mode, abortSignal: options.abortSignal });\n } finally {\n acquired.release();\n }\n }\n\n async doStream(options: LanguageModelV3CallOptions): Promise<{\n stream: ReadableStream<LanguageModelV3StreamPart>;\n }> {\n return { stream: cursorEventsToStream(this.agentRun(options), this.config.toolDisplay) };\n }\n\n async doGenerate(options: LanguageModelV3CallOptions): Promise<{\n content: Array<LanguageModelV3Content>;\n finishReason: LanguageModelV3FinishReason;\n usage: LanguageModelV3Usage;\n warnings: Array<never>;\n }> {\n const result = await cursorEventsToContent(this.agentRun(options), this.config.toolDisplay);\n return { ...result, warnings: [] };\n }\n}\n","import type { LanguageModelV3Prompt } from \"@ai-sdk/provider\";\nimport type { SDKImage, SDKUserMessage } from \"@cursor/sdk\";\n\n/**\n * Convert an AI-SDK prompt (the full conversation opencode sends on every call)\n * into a single Cursor `SDKUserMessage`.\n *\n * The Cursor agent keeps its own per-agent conversation memory, but opencode\n * re-sends the whole history each turn. To stay correct without double-counting\n * context, we create a fresh agent per turn (see language-model.ts) and flatten\n * the entire prompt into one transcript message. Images from the final user\n * turn are attached natively so multimodal models can see them.\n */\nexport function promptToCursorMessage(prompt: LanguageModelV3Prompt): SDKUserMessage {\n const lines: string[] = [];\n const images: SDKImage[] = [];\n\n prompt.forEach((message, index) => {\n const isLast = index === prompt.length - 1;\n switch (message.role) {\n case \"system\":\n lines.push(`# System\\n${message.content}`);\n break;\n case \"user\": {\n const text: string[] = [];\n for (const part of message.content) {\n if (part.type === \"text\") text.push(part.text);\n else if (part.type === \"file\" && part.mediaType.startsWith(\"image/\")) {\n const image = fileToImage(part.data, part.mediaType);\n // Only attach images natively for the final user turn; earlier ones\n // are referenced by transcript order.\n if (isLast && image) images.push(image);\n text.push(\"[image attached]\");\n }\n }\n lines.push(`# User\\n${text.join(\"\\n\")}`);\n break;\n }\n case \"assistant\": {\n const text: string[] = [];\n for (const part of message.content) {\n if (part.type === \"text\") text.push(part.text);\n else if (part.type === \"reasoning\") text.push(`(thinking) ${part.text}`);\n else if (part.type === \"tool-call\") text.push(`[called ${part.toolName}(${part.input})]`);\n else if (part.type === \"tool-result\") text.push(`[result of ${part.toolName}]`);\n }\n lines.push(`# Assistant\\n${text.join(\"\\n\")}`);\n break;\n }\n case \"tool\": {\n for (const part of message.content) {\n if (part.type === \"tool-result\") {\n lines.push(`# Tool result (${part.toolName})\\n${JSON.stringify(part.output)}`);\n }\n }\n break;\n }\n }\n });\n\n const out: SDKUserMessage = { text: lines.join(\"\\n\\n\") };\n if (images.length > 0) out.images = images;\n return out;\n}\n\nfunction fileToImage(\n data: string | Uint8Array | URL,\n mediaType: string,\n): SDKImage | undefined {\n if (data instanceof URL) return { url: data.toString() };\n if (typeof data === \"string\") {\n // Either a URL or already-base64 encoded data.\n if (/^https?:\\/\\//i.test(data)) return { url: data };\n return { data, mimeType: mediaType };\n }\n if (data instanceof Uint8Array) {\n return { data: Buffer.from(data).toString(\"base64\"), mimeType: mediaType };\n }\n return undefined;\n}\n\n/**\n * Extract only the final user turn as a Cursor message. Used when resuming a\n * pooled agent that already remembers the prior conversation, so we send just\n * the new message instead of the whole transcript. Returns `undefined` if the\n * last message isn't a user turn (caller should fall back to the full transcript).\n */\nexport function latestUserMessage(prompt: LanguageModelV3Prompt): SDKUserMessage | undefined {\n const last = prompt[prompt.length - 1];\n if (!last || last.role !== \"user\") return undefined;\n\n const text: string[] = [];\n const images: SDKImage[] = [];\n for (const part of last.content) {\n if (part.type === \"text\") text.push(part.text);\n else if (part.type === \"file\" && part.mediaType.startsWith(\"image/\")) {\n const image = fileToImage(part.data, part.mediaType);\n if (image) images.push(image);\n text.push(\"[image attached]\");\n }\n }\n\n const out: SDKUserMessage = { text: text.join(\"\\n\") };\n if (images.length > 0) out.images = images;\n return out;\n}\n\n","import type {\n LanguageModelV3Content,\n LanguageModelV3FinishReason,\n LanguageModelV3StreamPart,\n LanguageModelV3Usage,\n} from \"@ai-sdk/provider\";\nimport type { CursorEvent, CursorUsage } from \"./agent-events.js\";\n\n/**\n * How Cursor's internal tool activity (shell/read/edit/mcp/…) is surfaced to\n * opencode:\n * - `\"reasoning\"` (default): rendered as compact reasoning lines. Robust on\n * every host — no tool-call parts cross the execution boundary.\n * - `\"blocks\"`: emitted as provider-executed AI-SDK `tool-call`/`tool-result`\n * parts so opencode renders structured tool blocks. The parts must carry\n * BOTH `providerExecuted: true` AND `dynamic: true` — ai's `parseToolCall`\n * (v6, `doParseToolCall`) only exempts that combination from registered-tool\n * validation; without `dynamic` an unknown name raises `NoSuchToolError`,\n * which opencode's `experimental_repairToolCall` rewrites into its \"invalid\"\n * tool. Names are also prefixed (`cursor_…`) so they can never collide with\n * a tool opencode has registered (`read`, `grep`, `task`, …) — a colliding\n * name is validated against that tool's input schema instead of being\n * treated as dynamic.\n */\nexport type ToolDisplay = \"reasoning\" | \"blocks\";\n\nconst FINISH_STOP: LanguageModelV3FinishReason = { unified: \"stop\", raw: undefined };\nconst FINISH_ERROR: LanguageModelV3FinishReason = { unified: \"error\", raw: undefined };\n\nfunction safeJsonString(input: unknown): string {\n try {\n return typeof input === \"string\" ? input : JSON.stringify(input ?? {});\n } catch {\n return \"{}\";\n }\n}\n\n/**\n * Tool name as it crosses into opencode in \"blocks\" mode. Prefixed so it can\n * never collide with a tool opencode has registered, and sanitized because MCP\n * names contain `/` (e.g. `serena/find_symbol` → `cursor_serena_find_symbol`).\n */\nfunction blockToolName(name: string): string {\n return `cursor_${name.replace(/[^A-Za-z0-9_-]/g, \"_\")}`;\n}\n\n/**\n * Build a provider-executed dynamic `tool-call` stream part (V3). `input` is a\n * stringified JSON object per the spec.\n */\nfunction toolCallPart(id: string, name: string, input: unknown): LanguageModelV3StreamPart {\n return {\n type: \"tool-call\",\n toolCallId: id,\n toolName: blockToolName(name),\n input: safeJsonString(input),\n providerExecuted: true,\n dynamic: true,\n } as LanguageModelV3StreamPart;\n}\n\n/**\n * Build a provider-executed dynamic `tool-result` stream part. Per the V3 spec\n * (and ai v6's `runToolsTransformation`, which reads `chunk.result` /\n * `chunk.isError`) the payload goes in `result`; `result` is typed\n * `NonNullable<JSONValue>` so a missing Cursor result is coalesced to `null`\n * and cast.\n */\nfunction toolResultPart(\n id: string,\n name: string,\n result: unknown,\n isError: boolean,\n): LanguageModelV3StreamPart {\n return {\n type: \"tool-result\",\n toolCallId: id,\n toolName: blockToolName(name),\n result: (result ?? null) as never,\n isError,\n providerExecuted: true,\n dynamic: true,\n } as LanguageModelV3StreamPart;\n}\n\n/** Content-item equivalents of the tool parts above, for `doGenerate`. */\nfunction toolCallContent(id: string, name: string, input: unknown): LanguageModelV3Content {\n return {\n type: \"tool-call\",\n toolCallId: id,\n toolName: blockToolName(name),\n input: safeJsonString(input),\n providerExecuted: true,\n dynamic: true,\n } as LanguageModelV3Content;\n}\nfunction toolResultContent(\n id: string,\n name: string,\n result: unknown,\n isError: boolean,\n): LanguageModelV3Content {\n return {\n type: \"tool-result\",\n toolCallId: id,\n toolName: blockToolName(name),\n result: (result ?? null) as never,\n isError,\n providerExecuted: true,\n dynamic: true,\n } as LanguageModelV3Content;\n}\n\nexport const EMPTY_USAGE: LanguageModelV3Usage = {\n inputTokens: { total: undefined, noCache: undefined, cacheRead: undefined, cacheWrite: undefined },\n outputTokens: { total: undefined, text: undefined, reasoning: undefined },\n};\n\nexport function mapUsage(usage: CursorUsage): LanguageModelV3Usage {\n return {\n inputTokens: {\n total: usage.inputTokens,\n noCache: undefined,\n cacheRead: usage.cacheReadTokens,\n cacheWrite: usage.cacheWriteTokens,\n },\n outputTokens: { total: usage.outputTokens, text: undefined, reasoning: undefined },\n };\n}\n\n/**\n * Render Cursor's internal tool activity as a short, human-readable line.\n *\n * Cursor runs its own agent loop and executes its own tools (shell/read/edit/\n * mcp/…). We surface that activity as reasoning text — NOT as AI-SDK\n * `tool-call`/`tool-result` parts. opencode (a V3-native host) only treats\n * registered tools as callable; a provider-executed call naming a tool it\n * doesn't know (e.g. `mcp`, `shell`) is rejected as an \"unavailable tool\".\n * Rendering as reasoning keeps the activity visible without crossing the\n * tool-execution boundary. Tool outputs can be huge (file contents, search\n * dumps), so only the call (name + short arg summary) and error status are\n * shown — never the raw result.\n */\nfunction formatToolCall(name: string, input: unknown): string {\n let arg = \"\";\n try {\n const s = typeof input === \"string\" ? input : JSON.stringify(input);\n if (s && s !== \"{}\" && s !== '\"\"') arg = ` ${s.length > 120 ? `${s.slice(0, 120)}…` : s}`;\n } catch {\n // Non-serializable input; show the name only.\n }\n return `[tool] ${name}${arg}`;\n}\n\n/**\n * Synthetic error payload for a tool call whose completion never arrived\n * (run errored/cancelled/wedged mid-tool). Mirrors Cursor's own\n * `{status:\"error\"}` result union so consumers see a consistent shape.\n * Without a matching result, opencode renders the part as\n * \"Tool execution aborted\" and the block dangles forever.\n */\nconst DANGLING_TOOL_RESULT = {\n status: \"error\",\n error: \"Cursor run ended before this tool call completed.\",\n};\n\n/**\n * Translate the normalized Cursor agent events into an AI-SDK V3 stream.\n *\n * Pure with respect to the event source, so it can be tested by feeding a\n * fixed event sequence (no live agent required). Reasoning blocks are closed\n * before text begins so reasoning/text parts nest cleanly. Tool activity is\n * rendered into the reasoning channel (see {@link formatToolCall}).\n */\nexport function cursorEventsToStream(\n events: AsyncIterable<CursorEvent>,\n toolDisplay: ToolDisplay = \"reasoning\",\n): ReadableStream<LanguageModelV3StreamPart> {\n return new ReadableStream<LanguageModelV3StreamPart>({\n async start(controller) {\n controller.enqueue({ type: \"stream-start\", warnings: [] });\n\n let textId: string | undefined;\n let textCount = 0;\n let reasoningId: string | undefined;\n let reasoningCount = 0;\n let usage: LanguageModelV3Usage | undefined;\n let streamedText = false;\n // Open (unanswered) tool calls in blocks mode: id -> original tool name.\n const openToolCalls = new Map<string, string>();\n const closeDanglingToolCalls = () => {\n for (const [id, name] of openToolCalls) {\n controller.enqueue(toolResultPart(id, name, DANGLING_TOOL_RESULT, true));\n }\n openToolCalls.clear();\n };\n\n const closeReasoning = () => {\n if (reasoningId) {\n controller.enqueue({ type: \"reasoning-end\", id: reasoningId });\n reasoningId = undefined;\n }\n };\n // Close the open text part when reasoning resumes: hosts position a part\n // where it STARTED, so appending later text to an earlier part would\n // render the final answer above the reasoning that preceded it.\n const closeText = () => {\n if (textId) {\n controller.enqueue({ type: \"text-end\", id: textId });\n textId = undefined;\n }\n };\n const ensureText = () => {\n closeReasoning();\n if (!textId) {\n textId = `text-${textCount++}`;\n controller.enqueue({ type: \"text-start\", id: textId });\n }\n return textId;\n };\n const ensureReasoning = () => {\n closeText();\n if (!reasoningId) {\n reasoningId = `reasoning-${reasoningCount++}`;\n controller.enqueue({ type: \"reasoning-start\", id: reasoningId });\n }\n return reasoningId;\n };\n const reasoningLine = (text: string) => {\n controller.enqueue({ type: \"reasoning-delta\", id: ensureReasoning(), delta: text });\n };\n\n try {\n for await (const event of events) {\n switch (event.type) {\n case \"text-delta\":\n streamedText = true;\n controller.enqueue({ type: \"text-delta\", id: ensureText(), delta: event.text });\n break;\n case \"reasoning-delta\":\n reasoningLine(event.text);\n break;\n case \"tool-call\":\n if (toolDisplay === \"blocks\") {\n openToolCalls.set(event.id, event.name);\n controller.enqueue(toolCallPart(event.id, event.name, event.input));\n } else {\n reasoningLine(`\\n${formatToolCall(event.name, event.input)}\\n`);\n }\n break;\n case \"tool-result\":\n if (toolDisplay === \"blocks\") {\n openToolCalls.delete(event.id);\n controller.enqueue(\n toolResultPart(event.id, event.name, event.result, event.isError),\n );\n } else if (event.isError) {\n reasoningLine(`[tool] ${event.name} failed\\n`);\n }\n break;\n case \"usage\":\n usage = mapUsage(event.usage);\n break;\n case \"finish\":\n if (!streamedText && event.text) {\n controller.enqueue({ type: \"text-delta\", id: ensureText(), delta: event.text });\n }\n break;\n }\n }\n\n closeDanglingToolCalls();\n closeReasoning();\n closeText();\n controller.enqueue({ type: \"finish\", usage: usage ?? EMPTY_USAGE, finishReason: FINISH_STOP });\n controller.close();\n } catch (err) {\n controller.enqueue({ type: \"error\", error: err });\n closeDanglingToolCalls();\n closeReasoning();\n closeText();\n controller.enqueue({ type: \"finish\", usage: usage ?? EMPTY_USAGE, finishReason: FINISH_ERROR });\n controller.close();\n }\n },\n });\n}\n\n/**\n * Aggregate the normalized Cursor agent events into a non-streaming result for\n * `doGenerate`. Same event source contract as {@link cursorEventsToStream}.\n * Tool activity is folded into the reasoning text (display only).\n */\nexport async function cursorEventsToContent(\n events: AsyncIterable<CursorEvent>,\n toolDisplay: ToolDisplay = \"reasoning\",\n): Promise<{\n content: Array<LanguageModelV3Content>;\n finishReason: LanguageModelV3FinishReason;\n usage: LanguageModelV3Usage;\n}> {\n const content: Array<LanguageModelV3Content> = [];\n const toolParts: Array<LanguageModelV3Content> = [];\n // Open (unanswered) tool calls in blocks mode: id -> original tool name.\n const openToolCalls = new Map<string, string>();\n let text = \"\";\n let reasoning = \"\";\n let usage: LanguageModelV3Usage = EMPTY_USAGE;\n let finishReason: LanguageModelV3FinishReason = FINISH_STOP;\n\n try {\n for await (const event of events) {\n switch (event.type) {\n case \"text-delta\":\n text += event.text;\n break;\n case \"reasoning-delta\":\n reasoning += event.text;\n break;\n case \"tool-call\":\n if (toolDisplay === \"blocks\") {\n openToolCalls.set(event.id, event.name);\n toolParts.push(toolCallContent(event.id, event.name, event.input));\n } else {\n reasoning += `\\n${formatToolCall(event.name, event.input)}\\n`;\n }\n break;\n case \"tool-result\":\n if (toolDisplay === \"blocks\") {\n openToolCalls.delete(event.id);\n toolParts.push(toolResultContent(event.id, event.name, event.result, event.isError));\n } else if (event.isError) {\n reasoning += `[tool] ${event.name} failed\\n`;\n }\n break;\n case \"usage\":\n usage = mapUsage(event.usage);\n break;\n case \"finish\":\n if (!text && event.text) text = event.text;\n break;\n }\n }\n } catch {\n finishReason = FINISH_ERROR;\n }\n\n // Close out any tool call whose completion never arrived (see DANGLING_TOOL_RESULT).\n for (const [id, name] of openToolCalls) {\n toolParts.push(toolResultContent(id, name, DANGLING_TOOL_RESULT, true));\n }\n openToolCalls.clear();\n\n if (reasoning) content.push({ type: \"reasoning\", text: reasoning });\n content.push(...toolParts);\n if (text) content.push({ type: \"text\", text });\n\n return { content, finishReason, usage };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stablekernel/opencode-cursor",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.2",
|
|
4
4
|
"description": "opencode provider plugin backed by the official Cursor SDK (@cursor/sdk) — adds a Cursor provider and lists its models",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
"types": "./dist/provider/index.d.ts",
|
|
38
38
|
"import": "./dist/provider/index.js"
|
|
39
39
|
},
|
|
40
|
+
"./server": {
|
|
41
|
+
"types": "./dist/plugin/index.d.ts",
|
|
42
|
+
"import": "./dist/plugin/index.js"
|
|
43
|
+
},
|
|
40
44
|
"./plugin": {
|
|
41
45
|
"types": "./dist/plugin/index.d.ts",
|
|
42
46
|
"import": "./dist/plugin/index.js"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/api-key.ts","../src/provider/agent-events.ts","../src/provider/controls.ts","../src/cursor-runtime.ts","../src/provider/agent-backend.ts","../src/provider/sidecar-client.ts","../src/provider/session-pool.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\n\n/** Environment variable the Cursor SDK itself reads as a fallback. */\nexport const CURSOR_API_KEY_ENV_VAR = \"CURSOR_API_KEY\";\n\n/**\n * Values that are *not* real keys but rather instructions to read the key from\n * the environment. opencode config commonly stores literal `{env:...}` style\n * placeholders, and users sometimes paste the variable name itself.\n */\nconst PLACEHOLDERS = new Set<string>([\n CURSOR_API_KEY_ENV_VAR,\n `$${CURSOR_API_KEY_ENV_VAR}`,\n `\\${${CURSOR_API_KEY_ENV_VAR}}`,\n]);\n\n/**\n * Resolve a usable Cursor API key.\n *\n * Resolution order: an explicit, non-placeholder candidate (e.g. from opencode\n * auth storage or provider options) wins; otherwise fall back to the\n * `CURSOR_API_KEY` environment variable. Returns `undefined` when no key is\n * available so callers can present a clear \"needs auth\" path.\n *\n * The key is never logged or persisted by this module.\n */\nexport function resolveCursorApiKey(candidate?: string | null): string | undefined {\n const trimmed = candidate?.trim();\n if (trimmed && !PLACEHOLDERS.has(trimmed)) return trimmed;\n const fromEnv = process.env[CURSOR_API_KEY_ENV_VAR]?.trim();\n return fromEnv ? fromEnv : undefined;\n}\n\n/**\n * Produce a short, non-reversible fingerprint of an API key. Used purely to key\n * the on-disk model cache so the cache invalidates when the key changes. The\n * raw key is never written to disk.\n */\nexport function fingerprintApiKey(apiKey: string): string {\n return createHash(\"sha256\").update(apiKey).digest(\"hex\").slice(0, 16);\n}\n","import type { AgentModeOption, SDKUserMessage } from \"@cursor/sdk\";\nimport type { AgentLike, AgentRunLike } from \"./agent-backend.js\";\n\n/** Token usage as reported by Cursor's `turn-ended` update. */\nexport interface CursorUsage {\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n}\n\n/** Normalized events bridged from the Cursor SDK's push callbacks. */\nexport type CursorEvent =\n | { type: \"text-delta\"; text: string }\n | { type: \"reasoning-delta\"; text: string }\n | { type: \"tool-call\"; id: string; name: string; input: unknown }\n | { type: \"tool-result\"; id: string; name: string; result: unknown; isError: boolean }\n | { type: \"usage\"; usage: CursorUsage }\n | { type: \"finish\"; text?: string };\n\nexport interface StreamAgentTurnOptions {\n mode: AgentModeOption;\n abortSignal?: AbortSignal;\n}\n\n/**\n * Human-readable name for a Cursor tool call. Most Cursor tools carry their\n * name in `toolCall.type` (shell/read/edit/…), but an MCP tool call has\n * `type: \"mcp\"` with the real tool in `args.toolName` (and server in\n * `args.providerIdentifier`) — surface that instead of the literal \"mcp\".\n */\nfunction toolDisplayName(toolCall: ({ type?: string } & Record<string, any>) | undefined): string {\n if (!toolCall) return \"tool\";\n if (toolCall.type === \"mcp\") {\n const name = toolCall.args?.toolName;\n const server = toolCall.args?.providerIdentifier;\n if (name) return server ? `${server}/${name}` : String(name);\n return \"mcp\";\n }\n return toolCall.type ?? \"tool\";\n}\n\n/**\n * Stream a single turn on an already-acquired Cursor agent and yield normalized\n * events. The agent's lifecycle (create/resume/close) is owned by the caller\n * (see session-pool.ts) so it can be reused across turns. The SDK streams via\n * `onDelta` callbacks; we bridge those into a pull-based async generator so both\n * `doStream` and `doGenerate` can consume them.\n */\nexport async function* streamAgentTurn(\n agent: AgentLike,\n message: SDKUserMessage,\n options: StreamAgentTurnOptions,\n): AsyncGenerator<CursorEvent> {\n const queue: CursorEvent[] = [];\n let wake: (() => void) | undefined;\n let finished = false;\n let failure: unknown;\n\n // Opt-in stderr tracing of what the live agent emits (set OPENCODE_CURSOR_DEBUG=1).\n const debug = process.env.OPENCODE_CURSOR_DEBUG === \"1\";\n const counts: Record<string, number> = {};\n\n const push = (event: CursorEvent) => {\n queue.push(event);\n wake?.();\n wake = undefined;\n };\n\n const onDelta = ({ update }: { update: { type: string } & Record<string, any> }) => {\n if (debug) counts[update.type] = (counts[update.type] ?? 0) + 1;\n switch (update.type) {\n case \"text-delta\":\n push({ type: \"text-delta\", text: update.text });\n break;\n case \"thinking-delta\":\n push({ type: \"reasoning-delta\", text: update.text });\n break;\n case \"tool-call-started\":\n push({\n type: \"tool-call\",\n id: String(update.callId),\n name: toolDisplayName(update.toolCall),\n input: update.toolCall?.args ?? {},\n });\n break;\n case \"tool-call-completed\": {\n const tool = update.toolCall ?? {};\n const result = tool.result;\n // MCP failures often arrive as {status:\"success\", value:{isError:true}}\n // (the MCP-protocol error flag), not as a top-level status error.\n const mcpError = tool.type === \"mcp\" && result?.value?.isError === true;\n push({\n type: \"tool-result\",\n id: String(update.callId),\n name: toolDisplayName(tool),\n result: result ?? null,\n isError: result?.status === \"error\" || mcpError,\n });\n break;\n }\n case \"turn-ended\":\n if (update.usage) push({ type: \"usage\", usage: update.usage as CursorUsage });\n break;\n }\n };\n\n const runHolder: { run?: AgentRunLike } = {};\n const onAbort = () => {\n void Promise.resolve(runHolder.run?.cancel()).catch(() => {});\n };\n options.abortSignal?.addEventListener(\"abort\", onAbort);\n\n // A previous opencode/CLI crash (or a second instance racing on the same\n // agent store) can leave a persisted run wedged; the SDK then rejects new\n // sends with AgentBusyError. Retry once with the SDK's documented recovery\n // path (local.force expires the wedged run) instead of failing the turn.\n const sendTurn = async (): Promise<AgentRunLike> => {\n try {\n return await agent.send(message, { mode: options.mode, onDelta });\n } catch (err) {\n if (err instanceof Error && err.name === \"AgentBusyError\") {\n if (debug) console.error(\"[cursor:debug] agent busy; retrying send with local.force\");\n return agent.send(message, { mode: options.mode, onDelta, local: { force: true } });\n }\n throw err;\n }\n };\n\n // Kick off the turn. Resolve text from run.wait() for models that don't emit\n // incremental text deltas.\n void sendTurn()\n .then(async (run) => {\n runHolder.run = run;\n const result = await run.wait();\n if (debug) {\n console.error(\n `[cursor:debug] updates=${JSON.stringify(counts)} status=${result.status} resultLen=${(result.result ?? \"\").length}`,\n );\n }\n if (result.status === \"error\") {\n // Surface the failure instead of finishing silently — a silent stop\n // leaves opencode showing dangling tool calls with no explanation.\n throw new Error(\n `Cursor run ended with status \"error\"${result.result ? `: ${result.result}` : \"\"}`,\n );\n }\n // A cancelled run finishes without fabricating final text.\n push({ type: \"finish\", ...(result.status === \"cancelled\" ? {} : { text: result.result }) });\n })\n .catch((err) => {\n failure = err;\n if (debug) console.error(`[cursor:debug] send failed: ${err instanceof Error ? err.message : String(err)}`);\n })\n .finally(() => {\n finished = true;\n wake?.();\n wake = undefined;\n });\n\n try {\n while (true) {\n if (queue.length > 0) {\n yield queue.shift()!;\n continue;\n }\n if (finished) break;\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n }\n // Drain anything queued right before completion.\n while (queue.length > 0) yield queue.shift()!;\n if (failure) throw failure;\n } finally {\n options.abortSignal?.removeEventListener(\"abort\", onAbort);\n }\n}\n","import type { AgentModeOption, ModelSelection } from \"@cursor/sdk\";\n\n/** Per-model static control defaults (from provider/model config options). */\nexport interface StaticControls {\n mode: AgentModeOption;\n /** Default Cursor model params (id -> value), e.g. { thinking: \"high\" }. */\n params?: Record<string, string>;\n}\n\nexport interface ResolvedControls {\n mode: AgentModeOption;\n modelSelection: ModelSelection;\n}\n\n/**\n * Build a Cursor `ModelSelection` from a model id and an optional map of model\n * params (e.g. `{ thinking: \"high\" }`). Shared by the provider control\n * resolution and the cloud/delegate tools so param handling stays consistent.\n */\nexport function buildModelSelection(\n modelId: string,\n params?: Record<string, string>,\n): ModelSelection {\n const paramList = Object.entries(params ?? {}).map(([id, value]) => ({ id, value }));\n return paramList.length > 0 ? { id: modelId, params: paramList } : { id: modelId };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isMode(value: unknown): value is AgentModeOption {\n return value === \"agent\" || value === \"plan\";\n}\n\n/**\n * Resolve the per-turn Cursor controls from static config plus opencode's\n * per-request `providerOptions.cursor` (which carries merged model `options` and\n * the selected model `variant`). Per-request values win over static defaults.\n *\n * Recognized keys in `providerOptions.cursor`:\n * - `mode`: \"agent\" | \"plan\"\n * - `params`: Record<string,string> of Cursor model params (e.g. { thinking: \"high\" })\n * - `thinking`: string convenience, mapped to the `thinking` param if not already set\n */\nexport function resolveControls(\n modelId: string,\n staticControls: StaticControls,\n providerOptions: Record<string, unknown> | undefined,\n): ResolvedControls {\n const po = providerOptions ?? {};\n\n const mode: AgentModeOption = isMode(po[\"mode\"]) ? po[\"mode\"] : staticControls.mode;\n\n const params: Record<string, string> = { ...(staticControls.params ?? {}) };\n if (isRecord(po[\"params\"])) {\n for (const [key, value] of Object.entries(po[\"params\"])) {\n if (value != null) params[key] = String(value);\n }\n }\n if (typeof po[\"thinking\"] === \"string\" && params[\"thinking\"] === undefined) {\n params[\"thinking\"] = po[\"thinking\"];\n }\n\n return { mode, modelSelection: buildModelSelection(modelId, params) };\n}\n","/**\n * Lazy loader for the official Cursor SDK (`@cursor/sdk`).\n *\n * The SDK is heavy and only needed once a Cursor model is actually used or\n * models are discovered, so it is imported on demand. A failed import (e.g. the\n * dependency is missing) degrades gracefully into a clear error instead of\n * crashing opencode at startup.\n */\nexport type CursorSdkModule = typeof import(\"@cursor/sdk\");\n\nlet cached: Promise<CursorSdkModule> | undefined;\n\nexport async function loadCursorSdk(): Promise<CursorSdkModule> {\n if (!cached) {\n cached = import(\"@cursor/sdk\").catch((err: unknown) => {\n // Allow a later retry if the failure was transient.\n cached = undefined;\n const detail = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[opencode-cursor] Failed to load \"@cursor/sdk\". Make sure it is installed ` +\n `(\\`npm install @cursor/sdk\\`). Original error: ${detail}`,\n );\n });\n }\n return cached;\n}\n","/**\n * Selects where Cursor agents run:\n *\n * - \"in-process\": straight through `@cursor/sdk` in this process (Node — the\n * normal path for tests, scripts, and any non-Bun host).\n * - \"sidecar\": a spawned Node child hosting the SDK (Bun — opencode's runtime —\n * has a `node:http2` bug that kills Cursor's streaming RPC with\n * NGHTTP2_FRAME_SIZE_ERROR, losing tool-completion updates; see\n * src/sidecar/agent-host.mjs).\n *\n * Override with OPENCODE_CURSOR_SIDECAR=1/0 (force on/off).\n */\nimport { execSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { loadCursorSdk } from \"../cursor-runtime.js\";\nimport { SidecarClient, type AgentLike } from \"./sidecar-client.js\";\n\nexport type { AgentLike, AgentRunLike, AgentSendOptions } from \"./sidecar-client.js\";\n\nexport type BackendKind = \"in-process\" | \"sidecar\";\n\nexport interface AgentBackend {\n kind: BackendKind;\n createAgent(options: unknown): Promise<AgentLike>;\n resumeAgent(agentId: string, options: unknown): Promise<AgentLike>;\n}\n\nexport interface BackendEnvironment {\n isBun: boolean;\n /** Resolved node executable, or undefined when not on PATH. */\n nodePath: string | undefined;\n}\n\n/** Pure selection logic (unit-testable without spawning anything). */\nexport function resolveBackendKind(env: BackendEnvironment): BackendKind {\n const override = process.env[\"OPENCODE_CURSOR_SIDECAR\"];\n if (override === \"0\" || override === \"false\") return \"in-process\";\n if (override === \"1\" || override === \"true\") return env.nodePath ? \"sidecar\" : \"in-process\";\n return env.isBun && env.nodePath ? \"sidecar\" : \"in-process\";\n}\n\nfunction detectNode(): string | undefined {\n try {\n const out = execSync(process.platform === \"win32\" ? \"where node\" : \"command -v node\", {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n return out.split(\"\\n\")[0] || undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction detectEnvironment(): BackendEnvironment {\n const isBun = typeof (globalThis as { Bun?: unknown }).Bun !== \"undefined\";\n // Only pay the PATH lookup when the answer can matter.\n const needsNode = isBun || process.env[\"OPENCODE_CURSOR_SIDECAR\"] === \"1\";\n return { isBun, nodePath: needsNode ? detectNode() : process.execPath };\n}\n\nfunction inProcessBackend(): AgentBackend {\n return {\n kind: \"in-process\",\n createAgent: async (options) => {\n const { Agent } = await loadCursorSdk();\n return (await Agent.create(options as never)) as unknown as AgentLike;\n },\n resumeAgent: async (agentId, options) => {\n const { Agent } = await loadCursorSdk();\n return (await Agent.resume(agentId, options as never)) as unknown as AgentLike;\n },\n };\n}\n\n/**\n * Locate the sidecar script across layouts: tsup may place this module in\n * dist/provider/index.js or hoist it into a root-level dist/chunk-*.js, and in\n * dev/tests it runs straight from src/. Try each known relative position.\n */\nexport function resolveSidecarScript(): string | undefined {\n const candidates = [\n \"./sidecar/agent-host.js\", // importer is a chunk at dist root\n \"../sidecar/agent-host.js\", // importer is dist/provider/index.js\n \"../sidecar/agent-host.mjs\", // importer is src/provider/*.ts (dev/tests)\n ];\n for (const candidate of candidates) {\n const path = fileURLToPath(new URL(candidate, import.meta.url));\n if (existsSync(path)) return path;\n }\n return undefined;\n}\n\nfunction sidecarBackend(nodePath: string, scriptPath: string): AgentBackend {\n const client = new SidecarClient({ scriptPath, nodePath });\n return {\n kind: \"sidecar\",\n createAgent: (options) => client.createAgent(options),\n resumeAgent: (agentId, options) => client.resumeAgent(agentId, options),\n };\n}\n\nlet cached: AgentBackend | undefined;\n\n/** Resolve (and cache) the agent backend for this process. */\nexport function loadAgentBackend(): AgentBackend {\n if (!cached) {\n const env = detectEnvironment();\n const kind = resolveBackendKind(env);\n const scriptPath = kind === \"sidecar\" ? resolveSidecarScript() : undefined;\n // A user who explicitly opted out (OPENCODE_CURSOR_SIDECAR=0/false) has\n // accepted the in-process behavior and should not be warned.\n const override = process.env[\"OPENCODE_CURSOR_SIDECAR\"];\n const optedOut = override === \"0\" || override === \"false\";\n if (env.isBun && !optedOut && (kind === \"in-process\" || !scriptPath)) {\n console.error(\n \"[opencode-cursor] Running under Bun without a usable Node sidecar \" +\n `(node: ${env.nodePath ?? \"not found\"}, script: ${scriptPath ?? \"not found\"}): ` +\n \"Cursor native tool calls may fail (Bun node:http2 incompatibility). \" +\n \"Install Node.js to enable the sidecar, or set OPENCODE_CURSOR_SIDECAR=0 \" +\n \"to silence this warning.\",\n );\n }\n cached =\n kind === \"sidecar\" && env.nodePath && scriptPath\n ? sidecarBackend(env.nodePath, scriptPath)\n : inProcessBackend();\n }\n return cached;\n}\n\n/** Test hook. */\nexport function resetAgentBackend(): void {\n cached = undefined;\n}\n","/**\n * Client half of the Node sidecar (see src/sidecar/agent-host.mjs for the\n * protocol and the why). Spawns one Node child per client and multiplexes\n * agent create/resume/send/cancel/close requests over JSON-lines stdio,\n * exposing agents through the same minimal surface the provider already\n * consumes ({@link AgentLike}), so session-pool/agent-events need no\n * sidecar-specific logic.\n */\nimport { spawn, type ChildProcessByStdio } from \"node:child_process\";\nimport { createInterface, type Interface } from \"node:readline\";\nimport type { Readable, Writable } from \"node:stream\";\n\n/** Minimal run surface the provider consumes (subset of the SDK's Run). */\nexport interface AgentRunLike {\n wait(): Promise<{ status: string; result?: string }>;\n cancel(): void | Promise<void>;\n}\n\nexport interface AgentSendOptions {\n mode?: string;\n onDelta?: (input: { update: Record<string, unknown> & { type: string } }) => void;\n local?: { force?: boolean };\n}\n\n/** Minimal agent surface the provider consumes (subset of the SDK's SDKAgent). */\nexport interface AgentLike {\n agentId: string;\n send(message: unknown, options?: AgentSendOptions): Promise<AgentRunLike>;\n close(): void;\n}\n\nexport interface SidecarClientOptions {\n /** Path to the agent-host script. */\n scriptPath: string;\n /** Node executable; default \"node\" from PATH. */\n nodePath?: string;\n /** Extra environment for the child (merged over process.env). */\n env?: Record<string, string>;\n /** Mirror child stderr to this process (debug aid). */\n debug?: boolean;\n}\n\ninterface Pending {\n resolve: (msg: Record<string, unknown>) => void;\n reject: (err: Error) => void;\n /** Streaming hooks for \"send\" requests. */\n onUpdate?: (update: Record<string, unknown> & { type: string }) => void;\n onResult?: (result: { status: string; result?: string }) => void;\n onStreamError?: (err: Error) => void;\n}\n\nfunction reviveError(error: unknown): Error {\n const e = (error ?? {}) as { name?: string; message?: string };\n const err = new Error(e.message ?? \"sidecar error\");\n if (e.name) err.name = e.name;\n return err;\n}\n\nexport class SidecarClient {\n private readonly options: SidecarClientOptions;\n private child: ChildProcessByStdio<Writable, Readable, Readable> | undefined;\n private reader: Interface | undefined;\n private readonly pending = new Map<number, Pending>();\n private nextId = 1;\n private disposed = false;\n\n constructor(options: SidecarClientOptions) {\n this.options = options;\n }\n\n /** Spawn (or reuse) the child process. */\n private ensureChild(): ChildProcessByStdio<Writable, Readable, Readable> {\n if (this.disposed) throw new Error(\"cursor sidecar client disposed\");\n if (this.child) return this.child;\n\n const child = spawn(this.options.nodePath ?? \"node\", [this.options.scriptPath], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: { ...process.env, ...this.options.env },\n });\n this.child = child;\n\n this.reader = createInterface({ input: child.stdout });\n this.reader.on(\"line\", (line) => this.handleLine(line));\n child.stderr.on(\"data\", (chunk: Buffer) => {\n if (this.options.debug || process.env[\"OPENCODE_CURSOR_DEBUG\"]) {\n process.stderr.write(`[cursor:sidecar] ${chunk}`);\n }\n });\n child.on(\"exit\", (code) => {\n this.failAll(new Error(`cursor sidecar exited (code ${code ?? \"unknown\"})`));\n this.child = undefined;\n this.reader?.close();\n this.reader = undefined;\n });\n child.on(\"error\", (err) => {\n this.failAll(new Error(`cursor sidecar failed to start: ${err.message}`));\n this.child = undefined;\n });\n this.updateRefs();\n return child;\n }\n\n /**\n * Keep the child (and its pipes) from holding the parent's event loop open\n * while idle, but ref it whenever a reply is outstanding so the loop can't\n * exit mid-request. Without this, any process that uses the provider and\n * never dispose()s — scripts, tests, opencode itself on shutdown — hangs.\n */\n private updateRefs(): void {\n const child = this.child;\n if (!child) return;\n const refable = [child, child.stdin, child.stdout, child.stderr] as Array<{\n ref?: () => void;\n unref?: () => void;\n }>;\n if (this.pending.size > 0) {\n for (const target of refable) target.ref?.();\n } else {\n for (const target of refable) target.unref?.();\n }\n }\n\n private failAll(err: Error): void {\n for (const pending of this.pending.values()) {\n pending.onStreamError?.(err);\n pending.reject(err);\n }\n this.pending.clear();\n this.updateRefs();\n }\n\n private handleLine(line: string): void {\n if (!line.trim()) return;\n let msg: Record<string, unknown>;\n try {\n msg = JSON.parse(line) as Record<string, unknown>;\n } catch {\n return; // ignore non-protocol noise on stdout\n }\n const id = msg[\"id\"];\n if (typeof id !== \"number\") return;\n const pending = this.pending.get(id);\n if (!pending) return;\n\n const ev = msg[\"ev\"];\n if (ev === \"update\") {\n pending.onUpdate?.(msg[\"update\"] as Record<string, unknown> & { type: string });\n return;\n }\n if (ev === \"result\") {\n this.pending.delete(id);\n this.updateRefs();\n pending.onResult?.(msg[\"result\"] as { status: string; result?: string });\n return;\n }\n if (ev === \"error\") {\n this.pending.delete(id);\n this.updateRefs();\n pending.onStreamError?.(reviveError(msg[\"error\"]));\n return;\n }\n\n if (msg[\"ok\"] === true) {\n // \"send\" acks stay pending for their streaming terminal event.\n if (!pending.onResult) {\n this.pending.delete(id);\n this.updateRefs();\n }\n pending.resolve(msg);\n } else {\n this.pending.delete(id);\n this.updateRefs();\n pending.reject(reviveError(msg[\"error\"]));\n }\n }\n\n private request(\n payload: Record<string, unknown>,\n hooks?: Pick<Pending, \"onUpdate\" | \"onResult\" | \"onStreamError\">,\n ): Promise<Record<string, unknown>> {\n const child = this.ensureChild();\n const id = this.nextId++;\n return new Promise<Record<string, unknown>>((resolve, reject) => {\n this.pending.set(id, { resolve, reject, ...hooks });\n this.updateRefs();\n child.stdin.write(`${JSON.stringify({ id, ...payload })}\\n`, (err) => {\n if (err) {\n this.pending.delete(id);\n this.updateRefs();\n reject(err);\n }\n });\n });\n }\n\n async createAgent(options: unknown): Promise<AgentLike> {\n const res = await this.request({ op: \"create\", options });\n return this.wrapAgent(String(res[\"agentId\"]));\n }\n\n async resumeAgent(agentId: string, options: unknown): Promise<AgentLike> {\n const res = await this.request({ op: \"resume\", agentId, options });\n return this.wrapAgent(String(res[\"agentId\"]));\n }\n\n private wrapAgent(agentId: string): AgentLike {\n return {\n agentId,\n send: (message, options) => this.sendTurn(agentId, message, options),\n close: () => {\n void this.request({ op: \"close\", agentId }).catch(() => {\n // best effort, mirrors SDKAgent.close()\n });\n },\n };\n }\n\n private async sendTurn(\n agentId: string,\n message: unknown,\n options?: AgentSendOptions,\n ): Promise<AgentRunLike> {\n let settle!: {\n resolve: (r: { status: string; result?: string }) => void;\n reject: (e: Error) => void;\n };\n const waited = new Promise<{ status: string; result?: string }>((resolve, reject) => {\n settle = { resolve, reject };\n });\n // Avoid unhandled-rejection noise when the consumer never calls wait().\n waited.catch(() => {});\n\n let sendId: number | undefined;\n const ack = this.request(\n {\n op: \"send\",\n agentId,\n message,\n ...(options?.mode ? { mode: options.mode } : {}),\n ...(options?.local?.force ? { force: true } : {}),\n },\n {\n onUpdate: (update) => options?.onDelta?.({ update }),\n onResult: (result) => settle.resolve(result),\n onStreamError: (err) => settle.reject(err),\n },\n );\n // The request id is allocated synchronously inside request(); capture it\n // for cancel by reading the id we just used.\n sendId = this.nextId - 1;\n\n await ack;\n return {\n wait: () => waited,\n cancel: async () => {\n if (sendId === undefined) return;\n await this.request({ op: \"cancel\", sendId }).catch(() => {});\n },\n };\n }\n\n /** Kill the child and reject anything in flight. */\n dispose(): void {\n this.disposed = true;\n this.failAll(new Error(\"cursor sidecar client disposed\"));\n this.reader?.close();\n this.reader = undefined;\n this.child?.kill();\n this.child = undefined;\n }\n}\n","import type {\n AgentDefinition,\n AgentModeOption,\n McpServerConfig,\n ModelSelection,\n SettingSource,\n} from \"@cursor/sdk\";\nimport { loadAgentBackend, type AgentLike } from \"./agent-backend.js\";\n\n/** sessionID -> Cursor agentId, so a session reuses one Cursor agent across turns. */\nconst pool = new Map<string, string>();\n\n/** Test/diagnostic helpers. */\nexport function getPooledAgentId(sessionID: string): string | undefined {\n return pool.get(sessionID);\n}\nexport function clearAgentPool(): void {\n pool.clear();\n}\n\nexport interface AcquireAgentParams {\n apiKey: string;\n modelSelection: ModelSelection;\n mode: AgentModeOption;\n cwd: string;\n settingSources?: SettingSource[];\n sandbox?: boolean;\n mcpServers?: Record<string, McpServerConfig>;\n agents?: Record<string, AgentDefinition>;\n name?: string;\n /** opencode session id; required for pooling. */\n sessionID?: string;\n /** When true (and sessionID present) reuse/resume one agent per session. */\n session: boolean;\n /**\n * Resume a specific Cursor agent by id. Takes precedence over session\n * pooling; lets power users continue an explicit agent (e.g. one returned by\n * a prior tool call) rather than the session's auto-managed one.\n */\n agentId?: string;\n}\n\nexport interface AcquiredAgent {\n agent: AgentLike;\n /** True when an existing pooled agent was resumed (send only the new turn). */\n resumed: boolean;\n /** Close the agent unless it's pooled (pooled agents persist for the next turn). */\n release: () => void;\n}\n\n/**\n * Get an agent to run a turn: resume the session's pooled agent when possible,\n * otherwise create a fresh one. Resume failures fall back to creation, so a\n * stale/expired pool entry degrades to a correct fresh turn rather than an error.\n */\nexport async function acquireAgent(params: AcquireAgentParams): Promise<AcquiredAgent> {\n const backend = loadAgentBackend();\n\n const createOptions = {\n apiKey: params.apiKey,\n model: params.modelSelection,\n mode: params.mode,\n local: {\n cwd: params.cwd,\n ...(params.settingSources ? { settingSources: params.settingSources } : {}),\n ...(params.sandbox !== undefined ? { sandboxOptions: { enabled: params.sandbox } } : {}),\n },\n ...(params.mcpServers ? { mcpServers: params.mcpServers } : {}),\n ...(params.agents ? { agents: params.agents } : {}),\n ...(params.name ? { name: params.name } : {}),\n };\n\n const pooling = params.session && Boolean(params.sessionID);\n const pooledId = pooling ? pool.get(params.sessionID!) : undefined;\n // An explicit agentId wins over the session's pooled agent.\n const resumeId = params.agentId ?? pooledId;\n\n let agent: AgentLike | undefined;\n let resumed = false;\n if (resumeId) {\n try {\n agent = await backend.resumeAgent(resumeId, createOptions);\n resumed = true;\n } catch {\n // A stale/expired id degrades to a fresh agent; drop a matching pool entry.\n if (pooledId && resumeId === pooledId) pool.delete(params.sessionID!);\n }\n }\n if (!agent) {\n agent = await backend.createAgent(createOptions);\n }\n\n if (pooling) pool.set(params.sessionID!, agent.agentId);\n\n const release = () => {\n if (!pooling) {\n try {\n agent!.close();\n } catch {\n // best effort\n }\n }\n };\n\n return { agent, resumed, release };\n}\n"],"mappings":"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","names":["cached"]}
|