@stablekernel/opencode-cursor 0.1.0-rc.2 → 0.1.0

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/CHANGELOG.md CHANGED
@@ -4,10 +4,10 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
- - `0.1.0-rc.1`first pre-release of the 0.1.0 surface below, published to the
8
- npm `next` dist-tag for validation ahead of the stable `0.1.0`.
7
+ ## [0.1.0]2026-06-10
9
8
 
10
- ## [0.1.0] unreleased
9
+ > Pre-releases: `0.1.0-rc.1` and `0.1.0-rc.2` were published to the npm `next`
10
+ > dist-tag for validation ahead of this stable release.
11
11
 
12
12
  Initial public release. A complete opencode integration for Cursor built on the
13
13
  official `@cursor/sdk`: a streaming chat provider, an auth/config/model plugin,
@@ -21,17 +21,24 @@ and a permission-gated delegation tool surface.
21
21
  tool activity, usage). Implements both `doStream()` and `doGenerate()`.
22
22
  - **Per-request controls** via `providerOptions.cursor` — `mode` (agent/plan),
23
23
  `params`, and `thinking` level; works with opencode's model variant picker.
24
- - **Model variants** auto-generated from `Cursor.models.list` parameters: a
25
- `plan` variant plus one per reasoning level a model advertises.
24
+ - **Model variants** auto-generated from `Cursor.models.list` parameters: one
25
+ per reasoning/effort level a model advertises (boolean params collapse to a
26
+ single on-variant). opencode's plan agent maps to Cursor plan mode.
26
27
  - **Session reuse** (`session: true`) — keeps one Cursor agent per opencode
27
28
  session via `Agent.resume()` across turns, with automatic fallback to a fresh
28
29
  agent. A run wedged by a crashed/duplicate process is recovered by retrying
29
30
  the send once with the SDK's `local.force` escape hatch.
30
- - **`toolDisplay` provider option** (`"reasoning"` default | `"blocks"`):
31
- - `"reasoning"` renders Cursor's internal tool activity (including the real
32
- MCP tool name) as concise `[tool] …` reasoning lines. Always safe tool
33
- calls never cross opencode's tool-execution boundary.
34
- - `"blocks"` emits structured, provider-executed **dynamic** `tool-call` /
31
+ - **Native diff viewer for Cursor edits (blocks mode).** A Cursor `edit` tool
32
+ call is now surfaced under opencode's registered `edit` tool with its real
33
+ unified diff in `metadata.diff`, so opencode renders its built-in diff viewer
34
+ instead of a generic block. The required `oldString`/`newString` (which Cursor
35
+ does not expose) are reconstructed from the diff; the call is provider-executed
36
+ so they are never applied to disk. Any edit without a usable diff (errors,
37
+ unexpected shapes, or a host without a registered `edit` tool) falls back to a
38
+ safe `cursor_edit` block. Other Cursor tools (shell/read/mcp/…) remain
39
+ prefixed `cursor_*` blocks.
40
+ - **`toolDisplay` provider option** (`"blocks"` default | `"reasoning"`):
41
+ - `"blocks"` (default) emits structured, provider-executed **dynamic** `tool-call` /
35
42
  `tool-result` parts so opencode renders native tool blocks. Names are
36
43
  `cursor_`-prefixed and sanitized (`shell` → `cursor_shell`,
37
44
  `serena/find_symbol` → `cursor_serena_find_symbol`) so they can't collide
@@ -42,6 +49,10 @@ and a permission-gated delegation tool surface.
42
49
  errored/cancelled mid-tool) is closed with a synthetic error result so the
43
50
  block never dangles as "Tool execution aborted", and a run that ends with
44
51
  status `error` surfaces the failure instead of finishing silently.
52
+ - `"reasoning"` renders Cursor's internal tool activity (including the real
53
+ MCP tool name) as concise `[tool] …` reasoning lines. Always safe — tool
54
+ calls never cross opencode's tool-execution boundary; the fallback for
55
+ older/non-V3 hosts (`provider.cursor.options.toolDisplay: "reasoning"`).
45
56
 
46
57
  ### Node sidecar (Bun compatibility)
47
58
 
@@ -56,7 +67,8 @@ and a permission-gated delegation tool surface.
56
67
 
57
68
  ### Plugin
58
69
 
59
- - **opencode plugin** (`@stablekernel/opencode-cursor/plugin`): auth hook (API-key login;
70
+ - **opencode plugin** (`@stablekernel/opencode-cursor`, resolved via the package's
71
+ `./server` export): auth hook (API-key login;
60
72
  the key is validated on first use rather than at login), config hook
61
73
  (auto-injects `provider.cursor`),
62
74
  `provider.models()` (live catalog via `Cursor.models.list`), and the
package/README.md CHANGED
@@ -12,7 +12,8 @@ It uses your Cursor API key to:
12
12
  - register a `cursor` provider in opencode,
13
13
  - **list the models available to your account** (live, via `Cursor.models.list()`), and
14
14
  - run chats through Cursor's local agent runtime (`Agent.create` / `agent.send`), streaming
15
- text and reasoning back into opencode (Cursor's own tool activity is surfaced as reasoning).
15
+ text and reasoning back into opencode (Cursor's own tool activity is surfaced as structured tool
16
+ blocks by default; see [Tool display](#tool-display)).
16
17
 
17
18
  This plugin registers Cursor as a **native opencode provider**: its models appear in
18
19
  `opencode models` and the model picker, and you talk to a Cursor model *directly* — with live model
@@ -62,6 +63,25 @@ login. The `provider.models()` hook refreshes the catalog live once you're authe
62
63
 
63
64
  ## Install
64
65
 
66
+ ### Quick install (one line)
67
+
68
+ ```bash
69
+ curl -fsSL https://raw.githubusercontent.com/stablekernel/opencode-cursor/main/install.sh | bash
70
+ ```
71
+
72
+ The script registers the plugin in your **global** `opencode.json`
73
+ (`~/.config/opencode/opencode.json`), checks for Node.js 22+ on your `PATH`, and offers to set
74
+ `CURSOR_API_KEY`. Flags:
75
+
76
+ - `--project` — write `./opencode.json` in the current directory instead of the global config.
77
+ - `--yes` / `-y` — non-interactive; skip all prompts.
78
+
79
+ It backs up an existing config before editing, is safe to re-run (idempotent), and uses `jq` when
80
+ available (falling back to Node). You can review it first:
81
+ [`install.sh`](./install.sh).
82
+
83
+ ### Manual install
84
+
65
85
  ```bash
66
86
  npm install @stablekernel/opencode-cursor
67
87
  ```
@@ -135,7 +155,7 @@ This plugin also registers two **delegation tools** that complement the provider
135
155
  | `session` | `false` | Reuse one Cursor agent per opencode session (resume across turns; see below) |
136
156
  | `forwardMcp` | `true` | Forward opencode's configured MCP servers to the Cursor agent |
137
157
  | `mcpServers` | — | Extra MCP servers (Cursor `McpServerConfig` shape); merged with forwarded ones |
138
- | `toolDisplay` | `"reasoning"` | How Cursor's internal tool activity is shown: `"reasoning"` (compact lines, works everywhere) or `"blocks"` (structured provider-executed tool blocks; opt-in, see [Tool display](#tool-display)) |
158
+ | `toolDisplay` | `"blocks"` | How Cursor's internal tool activity is shown: `"blocks"` (structured provider-executed tool blocks; default, requires opencode 1.16+) or `"reasoning"` (compact lines, the fallback for older/non-V3 hosts). See [Tool display](#tool-display) |
139
159
 
140
160
  ### Session reuse (`session`)
141
161
 
@@ -156,10 +176,19 @@ opencode delivers per-request, provider-specific settings to the model under
156
176
  - `params` → `{ <paramId>: value }` mapped to Cursor `ModelSelection.params`
157
177
  - `thinking` → convenience, mapped to the `thinking` param
158
178
 
159
- These are most naturally driven by opencode's **model variant picker**: the plugin auto-generates a
160
- `plan` variant plus one variant per reasoning/thinking value the model advertises
161
- (`Cursor.models.list()` parameters). Selecting a variant sends its settings through
162
- `providerOptions.cursor`. You can also set them statically per model:
179
+ These are most naturally driven by opencode's **model variant picker**: the plugin auto-generates
180
+ one variant per reasoning/effort level a model advertises (`Cursor.models.list()` parameters). A
181
+ boolean parameter (e.g. `thinking: ["false","true"]`) collapses to a single variant named after the
182
+ parameter that switches it on (the off state is the default — no variant selected); enum parameters
183
+ (e.g. `effort`, `reasoning`) produce one variant per value. Selecting a variant sends its settings
184
+ through `providerOptions.cursor`.
185
+
186
+ > **Plan mode is not a variant.** opencode's **plan agent** (toggled with `Tab`) is mapped to
187
+ > Cursor's plan mode automatically by the plugin's `chat.params` hook, so switching opencode into
188
+ > plan mode puts the Cursor agent into plan mode too. An explicit `mode` from a selected variant or
189
+ > model option still wins.
190
+
191
+ You can also set controls statically per model:
163
192
 
164
193
  ```json
165
194
  { "provider": { "cursor": { "models": {
@@ -302,31 +331,32 @@ no sidecar is spawned.
302
331
  Cursor runs its own agent loop and executes its own tools. The `toolDisplay` option controls how
303
332
  that activity appears in opencode:
304
333
 
305
- - **`"reasoning"` (default)** — each tool call is shown as a compact reasoning line
306
- (`[tool] write {"path":…}`; failures as `[tool] x failed`). Robust on every host: no tool-call
307
- parts cross into opencode, so there's no dependency on how the host treats provider-executed
308
- tools.
309
- - **`"blocks"` (opt-in)** — tool activity is emitted as structured, **provider-executed**
334
+ - **`"blocks"` (default)** — tool activity is emitted as structured, **provider-executed**
310
335
  `tool-call`/`tool-result` parts so opencode renders proper, collapsible tool blocks with inputs
311
336
  and outputs. opencode skips execution for provider-executed calls (they're display-only), so
312
- Cursor's tools (`shell`, `mcp`, …) don't trigger an "unavailable tool" error. This requires a
337
+ Cursor's tools (`shell`, `mcp`, …) don't trigger an "unavailable tool" error. Requires a
313
338
  V3-native opencode host (1.16+).
339
+ - **`"reasoning"` (fallback)** — each tool call is shown as a compact reasoning line
340
+ (`[tool] write {"path":…}`; failures as `[tool] x failed`). Robust on every host: no tool-call
341
+ parts cross into opencode, so there's no dependency on how the host treats provider-executed
342
+ tools. Use this on older/non-V3 opencode hosts.
314
343
 
315
- Enable blocks mode in your opencode config:
344
+ The default needs no configuration. To force the reasoning fallback (e.g. on a pre-1.16 host):
316
345
 
317
346
  ```jsonc
318
347
  {
319
348
  "provider": {
320
349
  "cursor": {
321
- "options": { "toolDisplay": "blocks" }
350
+ "options": { "toolDisplay": "reasoning" }
322
351
  }
323
352
  }
324
353
  }
325
354
  ```
326
355
 
327
- > Why opt-in: `"blocks"` depends on V3-native, provider-executed dynamic tool parts and has been
328
- > verified against opencode 1.16+. `"reasoning"` requires nothing from the host and remains the
329
- > always-safe default. If `"blocks"` renders cleanly for you, it's the nicer experience.
356
+ > Why blocks by default: structured tool blocks are the nicer experience and have been verified
357
+ > against opencode 1.16+. `"blocks"` depends on V3-native, provider-executed dynamic tool parts; if
358
+ > your host predates that (or renders them poorly), set `"toolDisplay": "reasoning"` — it requires
359
+ > nothing from the host and works everywhere.
330
360
 
331
361
  ## Troubleshooting
332
362
 
@@ -336,6 +366,17 @@ Enable blocks mode in your opencode config:
336
366
  with `OPENCODE_CURSOR_SIDECAR=1`.
337
367
  - **"Running under Bun without a usable Node sidecar" warning.** Install Node.js 22+, or set
338
368
  `OPENCODE_CURSOR_SIDECAR=0` to accept in-process behavior and silence the warning.
369
+ - **"Could not locate the bindings file" / `node_sqlite3.node` not found.** `@cursor/sdk` depends on
370
+ the native `sqlite3` addon, and opencode installs plugins with Bun, which skips sqlite3's install
371
+ script — so the prebuilt binary may be missing. The plugin detects this and self-heals on first SDK
372
+ load by running sqlite3's own `prebuild-install -r napi` under your system Node (requires Node on
373
+ `PATH`). If it can't (no Node, offline), it logs a one-line manual fix: `cd` into the printed
374
+ sqlite3 directory and run `npx prebuild-install -r napi` (or `npm rebuild sqlite3`). Set
375
+ `OPENCODE_CURSOR_DEBUG=1` to see the repair output.
376
+ - **Plugin looks enabled but no `cursor` provider/models appear.** opencode caches a plugin by its
377
+ install spec under `~/.cache/opencode/packages/`; a stale cache from an older version can persist.
378
+ Pin an exact version (`@stablekernel/opencode-cursor@<version>`) or delete the cached dir and
379
+ restart so opencode reinstalls.
339
380
  - **Only the four fallback models appear in the picker.** The live catalog loads after the first
340
381
  authenticated use — restart opencode once after logging in, or run `cursor_refresh_models` to
341
382
  force a refresh.
@@ -4,10 +4,9 @@ import { AgentModeOption, McpServerConfig, SettingSource, AgentDefinition } from
4
4
  /**
5
5
  * How Cursor's internal tool activity (shell/read/edit/mcp/…) is surfaced to
6
6
  * opencode:
7
- * - `"reasoning"` (default): rendered as compact reasoning lines. Robust on
8
- * every host — no tool-call parts cross the execution boundary.
9
- * - `"blocks"`: emitted as provider-executed AI-SDK `tool-call`/`tool-result`
10
- * parts so opencode renders structured tool blocks. The parts must carry
7
+ * - `"blocks"` (default): emitted as provider-executed AI-SDK
8
+ * `tool-call`/`tool-result` parts so opencode renders structured tool
9
+ * blocks. Requires a V3-native opencode host (1.16+). The parts must carry
11
10
  * BOTH `providerExecuted: true` AND `dynamic: true` — ai's `parseToolCall`
12
11
  * (v6, `doParseToolCall`) only exempts that combination from registered-tool
13
12
  * validation; without `dynamic` an unknown name raises `NoSuchToolError`,
@@ -58,9 +57,11 @@ interface CursorProviderOptions {
58
57
  session?: boolean;
59
58
  /**
60
59
  * How Cursor's internal tool activity (shell/read/edit/mcp/…) is surfaced:
61
- * - `"reasoning"` (default): compact reasoning lines (works on every host).
62
- * - `"blocks"`: structured provider-executed `tool-call`/`tool-result` parts
63
- * so opencode renders proper tool blocks. Opt-in; requires a V3-native host.
60
+ * - `"blocks"` (default): structured provider-executed `tool-call`/
61
+ * `tool-result` parts so opencode renders proper tool blocks. Requires a
62
+ * V3-native opencode host (1.16+).
63
+ * - `"reasoning"`: compact reasoning lines; the fallback for older/non-V3
64
+ * hosts (works everywhere).
64
65
  */
65
66
  toolDisplay?: ToolDisplay;
66
67
  }
@@ -93,8 +93,14 @@ function latestUserMessage(prompt) {
93
93
  }
94
94
 
95
95
  // src/provider/stream-map.ts
96
- var FINISH_STOP = { unified: "stop", raw: void 0 };
97
- var FINISH_ERROR = { unified: "error", raw: void 0 };
96
+ var FINISH_STOP = {
97
+ unified: "stop",
98
+ raw: void 0
99
+ };
100
+ var FINISH_ERROR = {
101
+ unified: "error",
102
+ raw: void 0
103
+ };
98
104
  function safeJsonString(input) {
99
105
  try {
100
106
  return typeof input === "string" ? input : JSON.stringify(input ?? {});
@@ -105,7 +111,7 @@ function safeJsonString(input) {
105
111
  function blockToolName(name) {
106
112
  return `cursor_${name.replace(/[^A-Za-z0-9_-]/g, "_")}`;
107
113
  }
108
- function toolCallPart(id, name, input) {
114
+ function toolCallObj(id, name, input) {
109
115
  return {
110
116
  type: "tool-call",
111
117
  toolCallId: id,
@@ -115,7 +121,7 @@ function toolCallPart(id, name, input) {
115
121
  dynamic: true
116
122
  };
117
123
  }
118
- function toolResultPart(id, name, result, isError) {
124
+ function toolResultObj(id, name, result, isError) {
119
125
  return {
120
126
  type: "tool-result",
121
127
  toolCallId: id,
@@ -126,29 +132,112 @@ function toolResultPart(id, name, result, isError) {
126
132
  dynamic: true
127
133
  };
128
134
  }
129
- function toolCallContent(id, name, input) {
135
+ var EDIT_TOOL_NAME = "edit";
136
+ function isRecord(v) {
137
+ return typeof v === "object" && v !== null;
138
+ }
139
+ function editFilePath(input) {
140
+ return isRecord(input) && typeof input["path"] === "string" ? input["path"] : "";
141
+ }
142
+ function editDiffString(result) {
143
+ if (!isRecord(result) || result["status"] !== "success") return null;
144
+ const value = result["value"];
145
+ if (!isRecord(value)) return null;
146
+ const diff = value["diffString"];
147
+ return typeof diff === "string" && diff.length > 0 ? diff : null;
148
+ }
149
+ function reconstructEditStrings(diff) {
150
+ const oldLines = [];
151
+ const newLines = [];
152
+ for (const line of diff.split("\n")) {
153
+ if (line.startsWith("---") || line.startsWith("+++") || line.startsWith("@@") || line.startsWith("Index:") || line.startsWith("===")) {
154
+ continue;
155
+ }
156
+ if (line.startsWith("-")) oldLines.push(line.slice(1));
157
+ else if (line.startsWith("+")) newLines.push(line.slice(1));
158
+ }
159
+ return { oldString: oldLines.join("\n"), newString: newLines.join("\n") };
160
+ }
161
+ function editCallFields(id, filePath, diff) {
162
+ const { oldString, newString } = reconstructEditStrings(diff);
130
163
  return {
131
164
  type: "tool-call",
132
165
  toolCallId: id,
133
- toolName: blockToolName(name),
134
- input: safeJsonString(input),
166
+ toolName: EDIT_TOOL_NAME,
167
+ input: safeJsonString({ filePath, oldString, newString }),
135
168
  providerExecuted: true,
136
169
  dynamic: true
137
170
  };
138
171
  }
139
- function toolResultContent(id, name, result, isError) {
172
+ function editResultFields(id, filePath, diff, result) {
173
+ const value = isRecord(result) ? result["value"] : void 0;
174
+ const added = isRecord(value) && typeof value["linesAdded"] === "number" ? value["linesAdded"] : void 0;
175
+ const removed = isRecord(value) && typeof value["linesRemoved"] === "number" ? value["linesRemoved"] : void 0;
176
+ const counts = added !== void 0 || removed !== void 0 ? ` (+${added ?? 0}/-${removed ?? 0})` : "";
140
177
  return {
141
178
  type: "tool-result",
142
179
  toolCallId: id,
143
- toolName: blockToolName(name),
144
- result: result ?? null,
145
- isError,
180
+ toolName: EDIT_TOOL_NAME,
181
+ result: {
182
+ title: filePath,
183
+ metadata: { diff, diagnostics: {} },
184
+ output: `Edit applied${counts}.`
185
+ },
186
+ isError: false,
146
187
  providerExecuted: true,
147
188
  dynamic: true
148
189
  };
149
190
  }
191
+ function newBlockToolState() {
192
+ return { openToolCalls: /* @__PURE__ */ new Map(), pendingEdits: /* @__PURE__ */ new Map() };
193
+ }
194
+ function blockToolCallParts(id, name, input, state) {
195
+ if (name === EDIT_TOOL_NAME) {
196
+ state.pendingEdits.set(id, editFilePath(input));
197
+ return [];
198
+ }
199
+ state.openToolCalls.set(id, name);
200
+ return [toolCallObj(id, name, input)];
201
+ }
202
+ function blockToolResultParts(id, name, result, isError, state) {
203
+ if (state.pendingEdits.has(id)) {
204
+ const filePath = state.pendingEdits.get(id);
205
+ state.pendingEdits.delete(id);
206
+ const diff = isError ? null : editDiffString(result);
207
+ if (diff && filePath) {
208
+ return [
209
+ editCallFields(id, filePath, diff),
210
+ editResultFields(id, filePath, diff, result)
211
+ ];
212
+ }
213
+ return [
214
+ toolCallObj(id, EDIT_TOOL_NAME, { path: filePath }),
215
+ toolResultObj(id, EDIT_TOOL_NAME, result, isError)
216
+ ];
217
+ }
218
+ state.openToolCalls.delete(id);
219
+ return [toolResultObj(id, name, result, isError)];
220
+ }
221
+ function blockDanglingParts(state) {
222
+ const parts = [];
223
+ for (const [id, name] of state.openToolCalls) {
224
+ parts.push(toolResultObj(id, name, DANGLING_TOOL_RESULT, true));
225
+ }
226
+ state.openToolCalls.clear();
227
+ for (const [id, filePath] of state.pendingEdits) {
228
+ parts.push(toolCallObj(id, EDIT_TOOL_NAME, { path: filePath }));
229
+ parts.push(toolResultObj(id, EDIT_TOOL_NAME, DANGLING_TOOL_RESULT, true));
230
+ }
231
+ state.pendingEdits.clear();
232
+ return parts;
233
+ }
150
234
  var EMPTY_USAGE = {
151
- inputTokens: { total: void 0, noCache: void 0, cacheRead: void 0, cacheWrite: void 0 },
235
+ inputTokens: {
236
+ total: void 0,
237
+ noCache: void 0,
238
+ cacheRead: void 0,
239
+ cacheWrite: void 0
240
+ },
152
241
  outputTokens: { total: void 0, text: void 0, reasoning: void 0 }
153
242
  };
154
243
  function mapUsage(usage) {
@@ -159,14 +248,19 @@ function mapUsage(usage) {
159
248
  cacheRead: usage.cacheReadTokens,
160
249
  cacheWrite: usage.cacheWriteTokens
161
250
  },
162
- outputTokens: { total: usage.outputTokens, text: void 0, reasoning: void 0 }
251
+ outputTokens: {
252
+ total: usage.outputTokens,
253
+ text: void 0,
254
+ reasoning: void 0
255
+ }
163
256
  };
164
257
  }
165
258
  function formatToolCall(name, input) {
166
259
  let arg = "";
167
260
  try {
168
261
  const s = typeof input === "string" ? input : JSON.stringify(input);
169
- if (s && s !== "{}" && s !== '""') arg = ` ${s.length > 120 ? `${s.slice(0, 120)}\u2026` : s}`;
262
+ if (s && s !== "{}" && s !== '""')
263
+ arg = ` ${s.length > 120 ? `${s.slice(0, 120)}\u2026` : s}`;
170
264
  } catch {
171
265
  }
172
266
  return `[tool] ${name}${arg}`;
@@ -175,7 +269,7 @@ var DANGLING_TOOL_RESULT = {
175
269
  status: "error",
176
270
  error: "Cursor run ended before this tool call completed."
177
271
  };
178
- function cursorEventsToStream(events, toolDisplay = "reasoning") {
272
+ function cursorEventsToStream(events, toolDisplay = "blocks") {
179
273
  return new ReadableStream({
180
274
  async start(controller) {
181
275
  controller.enqueue({ type: "stream-start", warnings: [] });
@@ -185,12 +279,11 @@ function cursorEventsToStream(events, toolDisplay = "reasoning") {
185
279
  let reasoningCount = 0;
186
280
  let usage;
187
281
  let streamedText = false;
188
- const openToolCalls = /* @__PURE__ */ new Map();
282
+ const toolState = newBlockToolState();
189
283
  const closeDanglingToolCalls = () => {
190
- for (const [id, name] of openToolCalls) {
191
- controller.enqueue(toolResultPart(id, name, DANGLING_TOOL_RESULT, true));
284
+ for (const part of blockDanglingParts(toolState)) {
285
+ controller.enqueue(part);
192
286
  }
193
- openToolCalls.clear();
194
287
  };
195
288
  const closeReasoning = () => {
196
289
  if (reasoningId) {
@@ -221,22 +314,36 @@ function cursorEventsToStream(events, toolDisplay = "reasoning") {
221
314
  return reasoningId;
222
315
  };
223
316
  const reasoningLine = (text) => {
224
- controller.enqueue({ type: "reasoning-delta", id: ensureReasoning(), delta: text });
317
+ controller.enqueue({
318
+ type: "reasoning-delta",
319
+ id: ensureReasoning(),
320
+ delta: text
321
+ });
225
322
  };
226
323
  try {
227
324
  for await (const event of events) {
228
325
  switch (event.type) {
229
326
  case "text-delta":
230
327
  streamedText = true;
231
- controller.enqueue({ type: "text-delta", id: ensureText(), delta: event.text });
328
+ controller.enqueue({
329
+ type: "text-delta",
330
+ id: ensureText(),
331
+ delta: event.text
332
+ });
232
333
  break;
233
334
  case "reasoning-delta":
234
335
  reasoningLine(event.text);
235
336
  break;
236
337
  case "tool-call":
237
338
  if (toolDisplay === "blocks") {
238
- openToolCalls.set(event.id, event.name);
239
- controller.enqueue(toolCallPart(event.id, event.name, event.input));
339
+ for (const part of blockToolCallParts(
340
+ event.id,
341
+ event.name,
342
+ event.input,
343
+ toolState
344
+ )) {
345
+ controller.enqueue(part);
346
+ }
240
347
  } else {
241
348
  reasoningLine(`
242
349
  ${formatToolCall(event.name, event.input)}
@@ -245,10 +352,15 @@ ${formatToolCall(event.name, event.input)}
245
352
  break;
246
353
  case "tool-result":
247
354
  if (toolDisplay === "blocks") {
248
- openToolCalls.delete(event.id);
249
- controller.enqueue(
250
- toolResultPart(event.id, event.name, event.result, event.isError)
251
- );
355
+ for (const part of blockToolResultParts(
356
+ event.id,
357
+ event.name,
358
+ event.result,
359
+ event.isError,
360
+ toolState
361
+ )) {
362
+ controller.enqueue(part);
363
+ }
252
364
  } else if (event.isError) {
253
365
  reasoningLine(`[tool] ${event.name} failed
254
366
  `);
@@ -259,7 +371,11 @@ ${formatToolCall(event.name, event.input)}
259
371
  break;
260
372
  case "finish":
261
373
  if (!streamedText && event.text) {
262
- controller.enqueue({ type: "text-delta", id: ensureText(), delta: event.text });
374
+ controller.enqueue({
375
+ type: "text-delta",
376
+ id: ensureText(),
377
+ delta: event.text
378
+ });
263
379
  }
264
380
  break;
265
381
  }
@@ -267,23 +383,31 @@ ${formatToolCall(event.name, event.input)}
267
383
  closeDanglingToolCalls();
268
384
  closeReasoning();
269
385
  closeText();
270
- controller.enqueue({ type: "finish", usage: usage ?? EMPTY_USAGE, finishReason: FINISH_STOP });
386
+ controller.enqueue({
387
+ type: "finish",
388
+ usage: usage ?? EMPTY_USAGE,
389
+ finishReason: FINISH_STOP
390
+ });
271
391
  controller.close();
272
392
  } catch (err) {
273
393
  controller.enqueue({ type: "error", error: err });
274
394
  closeDanglingToolCalls();
275
395
  closeReasoning();
276
396
  closeText();
277
- controller.enqueue({ type: "finish", usage: usage ?? EMPTY_USAGE, finishReason: FINISH_ERROR });
397
+ controller.enqueue({
398
+ type: "finish",
399
+ usage: usage ?? EMPTY_USAGE,
400
+ finishReason: FINISH_ERROR
401
+ });
278
402
  controller.close();
279
403
  }
280
404
  }
281
405
  });
282
406
  }
283
- async function cursorEventsToContent(events, toolDisplay = "reasoning") {
407
+ async function cursorEventsToContent(events, toolDisplay = "blocks") {
284
408
  const content = [];
285
409
  const toolParts = [];
286
- const openToolCalls = /* @__PURE__ */ new Map();
410
+ const toolState = newBlockToolState();
287
411
  let text = "";
288
412
  let reasoning = "";
289
413
  let usage = EMPTY_USAGE;
@@ -299,8 +423,14 @@ async function cursorEventsToContent(events, toolDisplay = "reasoning") {
299
423
  break;
300
424
  case "tool-call":
301
425
  if (toolDisplay === "blocks") {
302
- openToolCalls.set(event.id, event.name);
303
- toolParts.push(toolCallContent(event.id, event.name, event.input));
426
+ for (const part of blockToolCallParts(
427
+ event.id,
428
+ event.name,
429
+ event.input,
430
+ toolState
431
+ )) {
432
+ toolParts.push(part);
433
+ }
304
434
  } else {
305
435
  reasoning += `
306
436
  ${formatToolCall(event.name, event.input)}
@@ -309,8 +439,15 @@ ${formatToolCall(event.name, event.input)}
309
439
  break;
310
440
  case "tool-result":
311
441
  if (toolDisplay === "blocks") {
312
- openToolCalls.delete(event.id);
313
- toolParts.push(toolResultContent(event.id, event.name, event.result, event.isError));
442
+ for (const part of blockToolResultParts(
443
+ event.id,
444
+ event.name,
445
+ event.result,
446
+ event.isError,
447
+ toolState
448
+ )) {
449
+ toolParts.push(part);
450
+ }
314
451
  } else if (event.isError) {
315
452
  reasoning += `[tool] ${event.name} failed
316
453
  `;
@@ -327,10 +464,9 @@ ${formatToolCall(event.name, event.input)}
327
464
  } catch {
328
465
  finishReason = FINISH_ERROR;
329
466
  }
330
- for (const [id, name] of openToolCalls) {
331
- toolParts.push(toolResultContent(id, name, DANGLING_TOOL_RESULT, true));
467
+ for (const part of blockDanglingParts(toolState)) {
468
+ toolParts.push(part);
332
469
  }
333
- openToolCalls.clear();
334
470
  if (reasoning) content.push({ type: "reasoning", text: reasoning });
335
471
  content.push(...toolParts);
336
472
  if (text) content.push({ type: "text", text });
@@ -385,16 +521,27 @@ var CursorLanguageModel = class {
385
521
  });
386
522
  const message = acquired.resumed ? latestUserMessage(options.prompt) ?? promptToCursorMessage(options.prompt) : promptToCursorMessage(options.prompt);
387
523
  try {
388
- yield* streamAgentTurn(acquired.agent, message, { mode, abortSignal: options.abortSignal });
524
+ yield* streamAgentTurn(acquired.agent, message, {
525
+ mode,
526
+ abortSignal: options.abortSignal
527
+ });
389
528
  } finally {
390
529
  acquired.release();
391
530
  }
392
531
  }
393
532
  async doStream(options) {
394
- return { stream: cursorEventsToStream(this.agentRun(options), this.config.toolDisplay) };
533
+ return {
534
+ stream: cursorEventsToStream(
535
+ this.agentRun(options),
536
+ this.config.toolDisplay
537
+ )
538
+ };
395
539
  }
396
540
  async doGenerate(options) {
397
- const result = await cursorEventsToContent(this.agentRun(options), this.config.toolDisplay);
541
+ const result = await cursorEventsToContent(
542
+ this.agentRun(options),
543
+ this.config.toolDisplay
544
+ );
398
545
  return { ...result, warnings: [] };
399
546
  }
400
547
  };
@@ -413,7 +560,7 @@ function createCursor(options = {}) {
413
560
  ...options.sandbox !== void 0 ? { sandbox: options.sandbox } : {},
414
561
  ...options.agents ? { agents: options.agents } : {},
415
562
  ...options.session !== void 0 ? { session: options.session } : {},
416
- ...options.toolDisplay ? { toolDisplay: options.toolDisplay } : {}
563
+ toolDisplay: options.toolDisplay ?? "blocks"
417
564
  };
418
565
  const notImplemented = (kind, modelId) => {
419
566
  throw new NoSuchModelError({
@@ -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 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":[]}
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 {\n\tEmbeddingModelV3,\n\tImageModelV3,\n\tProviderV3,\n} from \"@ai-sdk/provider\";\nimport { NoSuchModelError } from \"@ai-sdk/provider\";\nimport type {\n\tAgentDefinition,\n\tAgentModeOption,\n\tMcpServerConfig,\n\tSettingSource,\n} from \"@cursor/sdk\";\nimport { resolveCursorApiKey } from \"../api-key.js\";\nimport {\n\tCursorLanguageModel,\n\ttype CursorModelConfig,\n} from \"./language-model.js\";\nimport type { ToolDisplay } from \"./stream-map.js\";\n\nexport interface CursorProviderOptions {\n\t/**\n\t * Cursor API key. opencode passes this from the provider's resolved auth /\n\t * options. When omitted, falls back to the CURSOR_API_KEY environment\n\t * variable at call time.\n\t */\n\tapiKey?: string;\n\t/** Provider id, supplied by opencode as `name`. Defaults to \"cursor\". */\n\tname?: string;\n\t/** Working directory for the local Cursor agent. Defaults to process.cwd(). */\n\tcwd?: string;\n\t/** Default conversation mode: \"agent\" (default) or \"plan\". Overridable per-request. */\n\tmode?: AgentModeOption;\n\t/** Default Cursor model params (id -> value), e.g. { thinking: \"high\" }. */\n\tparams?: Record<string, string>;\n\t/**\n\t * MCP servers to make available to the Cursor agent, keyed by name. The\n\t * plugin's `config` hook populates this by translating opencode's configured\n\t * `config.mcp` servers, so the agent can use the same MCP servers (e.g.\n\t * Serena) that opencode does.\n\t */\n\tmcpServers?: Record<string, McpServerConfig>;\n\t/**\n\t * Cursor settings layers to load from the local filesystem (\"project\",\n\t * \"user\", \"all\", ...). Enables the agent to pick up your Cursor skills,\n\t * rules, and `.cursor/mcp.json` servers.\n\t */\n\tsettingSources?: SettingSource[];\n\t/** Run the agent's tools inside Cursor's sandbox. */\n\tsandbox?: boolean;\n\t/** Cursor subagent definitions (`{ description, prompt, model?, mcpServers? }`). */\n\tagents?: Record<string, AgentDefinition>;\n\t/**\n\t * Reuse one Cursor agent per opencode session (resume across turns instead of\n\t * creating a fresh agent each turn). Off by default.\n\t */\n\tsession?: boolean;\n\t/**\n\t * How Cursor's internal tool activity (shell/read/edit/mcp/…) is surfaced:\n\t * - `\"blocks\"` (default): structured provider-executed `tool-call`/\n\t * `tool-result` parts so opencode renders proper tool blocks. Requires a\n\t * V3-native opencode host (1.16+).\n\t * - `\"reasoning\"`: compact reasoning lines; the fallback for older/non-V3\n\t * hosts (works everywhere).\n\t */\n\ttoolDisplay?: 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\tconst mcpServers =\n\t\toptions.mcpServers && Object.keys(options.mcpServers).length > 0\n\t\t\t? options.mcpServers\n\t\t\t: undefined;\n\tconst config: CursorModelConfig = {\n\t\tproviderName: options.name ?? \"cursor\",\n\t\tapiKey: resolveCursorApiKey(options.apiKey),\n\t\tcwd: options.cwd ?? process.cwd(),\n\t\tmode: options.mode ?? \"agent\",\n\t\t...(options.params ? { params: options.params } : {}),\n\t\t...(mcpServers ? { mcpServers } : {}),\n\t\t...(options.settingSources\n\t\t\t? { settingSources: options.settingSources }\n\t\t\t: {}),\n\t\t...(options.sandbox !== undefined ? { sandbox: options.sandbox } : {}),\n\t\t...(options.agents ? { agents: options.agents } : {}),\n\t\t...(options.session !== undefined ? { session: options.session } : {}),\n\t\ttoolDisplay: options.toolDisplay ?? \"blocks\",\n\t};\n\n\tconst notImplemented = (kind: string, modelId: string): never => {\n\t\tthrow new NoSuchModelError({\n\t\t\tmodelId,\n\t\t\tmodelType: kind as \"languageModel\",\n\t\t\tmessage: `The Cursor provider does not support ${kind} models.`,\n\t\t});\n\t};\n\n\treturn {\n\t\tspecificationVersion: \"v3\",\n\t\tlanguageModel: (modelId: string) =>\n\t\t\tnew CursorLanguageModel(modelId, config),\n\t\tembeddingModel: (modelId: string): EmbeddingModelV3 =>\n\t\t\tnotImplemented(\"embeddingModel\", modelId),\n\t\timageModel: (modelId: string): ImageModelV3 =>\n\t\t\tnotImplemented(\"imageModel\", modelId),\n\t};\n}\n","import type {\n\tLanguageModelV3,\n\tLanguageModelV3CallOptions,\n\tLanguageModelV3Content,\n\tLanguageModelV3FinishReason,\n\tLanguageModelV3StreamPart,\n\tLanguageModelV3Usage,\n} from \"@ai-sdk/provider\";\nimport { LoadAPIKeyError } from \"@ai-sdk/provider\";\nimport type {\n\tAgentDefinition,\n\tMcpServerConfig,\n\tSettingSource,\n\tAgentModeOption,\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 {\n\tcursorEventsToContent,\n\tcursorEventsToStream,\n\ttype ToolDisplay,\n} from \"./stream-map.js\";\nimport { resolveControls } from \"./controls.js\";\nimport { acquireAgent } from \"./session-pool.js\";\n\nexport interface CursorModelConfig {\n\t/** Provider id used for logging and the providerOptions key (e.g. \"cursor\"). */\n\tproviderName: string;\n\t/** Explicit API key; re-resolved against the env at call time when absent. */\n\tapiKey?: string;\n\t/** Working directory the local Cursor agent operates in. */\n\tcwd: string;\n\t/** Default conversation mode; overridable per-request via providerOptions. */\n\tmode: AgentModeOption;\n\t/** Default Cursor model params (id -> value); overridable per-request. */\n\tparams?: Record<string, string>;\n\t/** MCP servers forwarded to the Cursor agent (e.g. opencode's Serena). */\n\tmcpServers?: Record<string, McpServerConfig>;\n\t/** Cursor settings layers to load from disk (skills, rules, .cursor/mcp.json). */\n\tsettingSources?: SettingSource[];\n\t/** Run the agent's tools inside Cursor's sandbox. */\n\tsandbox?: boolean;\n\t/** Cursor subagent definitions made available to the agent. */\n\tagents?: Record<string, AgentDefinition>;\n\t/**\n\t * Reuse one Cursor agent per opencode session (resume across turns, sending\n\t * only the new message). Off by default; the default per-turn-fresh path\n\t * re-sends the full transcript and is robust to opencode's non-chat calls.\n\t */\n\tsession?: boolean;\n\t/**\n\t * How Cursor's internal tool activity is surfaced (see {@link ToolDisplay}).\n\t * Defaults to `\"blocks\"`.\n\t */\n\ttoolDisplay?: 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\treadonly specificationVersion = \"v3\" as const;\n\treadonly modelId: string;\n\treadonly provider: string;\n\t// Images are passed inline as base64 data, so no URLs are fetched natively.\n\treadonly supportedUrls: Record<string, RegExp[]> = {};\n\n\tconstructor(\n\t\tmodelId: string,\n\t\tprivate readonly config: CursorModelConfig,\n\t) {\n\t\tthis.modelId = modelId;\n\t\tthis.provider = config.providerName;\n\t}\n\n\tprivate requireApiKey(): string {\n\t\tconst apiKey = resolveCursorApiKey(this.config.apiKey);\n\t\tif (!apiKey) {\n\t\t\tthrow new LoadAPIKeyError({\n\t\t\t\tmessage:\n\t\t\t\t\t\"Cursor API key missing. Run `opencode auth login` and choose Cursor, or set CURSOR_API_KEY.\",\n\t\t\t});\n\t\t}\n\t\treturn apiKey;\n\t}\n\n\tprivate async *agentRun(\n\t\toptions: LanguageModelV3CallOptions,\n\t): AsyncGenerator<CursorEvent> {\n\t\t// opencode delivers per-request controls (merged model options + selected\n\t\t// variant) under providerOptions keyed by our provider id. The session id is\n\t\t// injected there by the plugin's chat.params hook.\n\t\tconst providerOptions = options.providerOptions?.[this.provider] as\n\t\t\t| Record<string, unknown>\n\t\t\t| undefined;\n\t\tconst { mode, modelSelection } = resolveControls(\n\t\t\tthis.modelId,\n\t\t\t{ mode: this.config.mode, params: this.config.params },\n\t\t\tproviderOptions,\n\t\t);\n\t\tconst sessionID =\n\t\t\ttypeof providerOptions?.[\"sessionID\"] === \"string\"\n\t\t\t\t? (providerOptions[\"sessionID\"] as string)\n\t\t\t\t: undefined;\n\t\tconst useSession = this.config.session === true && Boolean(sessionID);\n\t\t// Power users can resume a specific Cursor agent via\n\t\t// `providerOptions.cursor.agentId`; it takes precedence over session pooling.\n\t\tconst explicitAgentId =\n\t\t\ttypeof providerOptions?.[\"agentId\"] === \"string\"\n\t\t\t\t? (providerOptions[\"agentId\"] as string)\n\t\t\t\t: undefined;\n\n\t\tconst acquired = await acquireAgent({\n\t\t\tapiKey: this.requireApiKey(),\n\t\t\tmodelSelection,\n\t\t\tmode,\n\t\t\tcwd: this.config.cwd,\n\t\t\t...(this.config.settingSources\n\t\t\t\t? { settingSources: this.config.settingSources }\n\t\t\t\t: {}),\n\t\t\t...(this.config.sandbox !== undefined\n\t\t\t\t? { sandbox: this.config.sandbox }\n\t\t\t\t: {}),\n\t\t\t...(this.config.mcpServers ? { mcpServers: this.config.mcpServers } : {}),\n\t\t\t...(this.config.agents ? { agents: this.config.agents } : {}),\n\t\t\t...(useSession ? { name: `opencode/${sessionID!.slice(-8)}` } : {}),\n\t\t\t...(explicitAgentId ? { agentId: explicitAgentId } : {}),\n\t\t\tsessionID,\n\t\t\tsession: useSession,\n\t\t});\n\n\t\t// A resumed agent already remembers the prior conversation, so send only the\n\t\t// new turn; otherwise send the full transcript.\n\t\tconst message = acquired.resumed\n\t\t\t? (latestUserMessage(options.prompt) ??\n\t\t\t\tpromptToCursorMessage(options.prompt))\n\t\t\t: promptToCursorMessage(options.prompt);\n\n\t\ttry {\n\t\t\tyield* streamAgentTurn(acquired.agent, message, {\n\t\t\t\tmode,\n\t\t\t\tabortSignal: options.abortSignal,\n\t\t\t});\n\t\t} finally {\n\t\t\tacquired.release();\n\t\t}\n\t}\n\n\tasync doStream(options: LanguageModelV3CallOptions): Promise<{\n\t\tstream: ReadableStream<LanguageModelV3StreamPart>;\n\t}> {\n\t\treturn {\n\t\t\tstream: cursorEventsToStream(\n\t\t\t\tthis.agentRun(options),\n\t\t\t\tthis.config.toolDisplay,\n\t\t\t),\n\t\t};\n\t}\n\n\tasync doGenerate(options: LanguageModelV3CallOptions): Promise<{\n\t\tcontent: Array<LanguageModelV3Content>;\n\t\tfinishReason: LanguageModelV3FinishReason;\n\t\tusage: LanguageModelV3Usage;\n\t\twarnings: Array<never>;\n\t}> {\n\t\tconst result = await cursorEventsToContent(\n\t\t\tthis.agentRun(options),\n\t\t\tthis.config.toolDisplay,\n\t\t);\n\t\treturn { ...result, warnings: [] };\n\t}\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\tLanguageModelV3Content,\n\tLanguageModelV3FinishReason,\n\tLanguageModelV3StreamPart,\n\tLanguageModelV3Usage,\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 * - `\"blocks\"` (default): emitted as provider-executed AI-SDK\n * `tool-call`/`tool-result` parts so opencode renders structured tool\n * blocks. Requires a V3-native opencode host (1.16+). 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 = {\n\tunified: \"stop\",\n\traw: undefined,\n};\nconst FINISH_ERROR: LanguageModelV3FinishReason = {\n\tunified: \"error\",\n\traw: undefined,\n};\n\nfunction safeJsonString(input: unknown): string {\n\ttry {\n\t\treturn typeof input === \"string\" ? input : JSON.stringify(input ?? {});\n\t} catch {\n\t\treturn \"{}\";\n\t}\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\treturn `cursor_${name.replace(/[^A-Za-z0-9_-]/g, \"_\")}`;\n}\n\n/**\n * A blocks-mode tool part. `tool-call` / `tool-result` are structurally\n * identical in `LanguageModelV3StreamPart` (streaming) and\n * `LanguageModelV3Content` (`doGenerate`), so the builders below produce one\n * shape that both consumers cast to their respective union.\n */\ntype BlockToolPart = LanguageModelV3StreamPart;\n\n/**\n * Build a provider-executed dynamic `tool-call`. The name is `cursor_`-prefixed\n * so it can't collide with a tool opencode has registered; `input` is a\n * stringified JSON object per the V3 spec.\n */\nfunction toolCallObj(id: string, name: string, input: unknown): BlockToolPart {\n\treturn {\n\t\ttype: \"tool-call\",\n\t\ttoolCallId: id,\n\t\ttoolName: blockToolName(name),\n\t\tinput: safeJsonString(input),\n\t\tproviderExecuted: true,\n\t\tdynamic: true,\n\t} as BlockToolPart;\n}\n\n/**\n * Build a provider-executed dynamic `tool-result`. Per the V3 spec (and ai v6's\n * `runToolsTransformation`, which reads `chunk.result` / `chunk.isError`) the\n * payload goes in `result`; `result` is typed `NonNullable<JSONValue>` so a\n * missing Cursor result is coalesced to `null` and cast.\n */\nfunction toolResultObj(\n\tid: string,\n\tname: string,\n\tresult: unknown,\n\tisError: boolean,\n): BlockToolPart {\n\treturn {\n\t\ttype: \"tool-result\",\n\t\ttoolCallId: id,\n\t\ttoolName: blockToolName(name),\n\t\tresult: (result ?? null) as never,\n\t\tisError,\n\t\tproviderExecuted: true,\n\t\tdynamic: true,\n\t} as BlockToolPart;\n}\n\n/** Cursor's file-edit tool surfaces with this name (its `toolCall.type`). */\nconst EDIT_TOOL_NAME = \"edit\";\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n\treturn typeof v === \"object\" && v !== null;\n}\n\n/** Extract the edit target path from Cursor's edit tool-call args (`{ path }`). */\nfunction editFilePath(input: unknown): string {\n\treturn isRecord(input) && typeof input[\"path\"] === \"string\"\n\t\t? input[\"path\"]\n\t\t: \"\";\n}\n\n/**\n * If `result` is a successful Cursor edit result carrying a unified diff, return\n * its `diffString`; otherwise null (caller falls back to a safe generic block).\n * Cursor shape: `{ status:\"success\", value:{ diffString?, linesAdded?, linesRemoved? } }`.\n */\nfunction editDiffString(result: unknown): string | null {\n\tif (!isRecord(result) || result[\"status\"] !== \"success\") return null;\n\tconst value = result[\"value\"];\n\tif (!isRecord(value)) return null;\n\tconst diff = value[\"diffString\"];\n\treturn typeof diff === \"string\" && diff.length > 0 ? diff : null;\n}\n\n/**\n * Reconstruct opencode `edit` `{oldString,newString}` from a unified diff:\n * removed (`-`) lines → oldString, added (`+`) lines → newString (file/hunk\n * headers skipped). Faithful for a single hunk; approximate (concatenated)\n * across multiple hunks. Used only to satisfy opencode's edit input schema — the\n * call is provider-executed, so these strings are never applied to disk; the\n * rendered diff comes from `metadata.diff`.\n */\nfunction reconstructEditStrings(diff: string): {\n\toldString: string;\n\tnewString: string;\n} {\n\tconst oldLines: string[] = [];\n\tconst newLines: string[] = [];\n\tfor (const line of diff.split(\"\\n\")) {\n\t\tif (\n\t\t\tline.startsWith(\"---\") ||\n\t\t\tline.startsWith(\"+++\") ||\n\t\t\tline.startsWith(\"@@\") ||\n\t\t\tline.startsWith(\"Index:\") ||\n\t\t\tline.startsWith(\"===\")\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (line.startsWith(\"-\")) oldLines.push(line.slice(1));\n\t\telse if (line.startsWith(\"+\")) newLines.push(line.slice(1));\n\t}\n\treturn { oldString: oldLines.join(\"\\n\"), newString: newLines.join(\"\\n\") };\n}\n\n/**\n * Build the opencode-native `edit` `tool-call` payload for a completed Cursor\n * edit. Emitted under the registered name `edit` (so opencode's diff viewer\n * renders) with a schema-valid `{filePath, oldString, newString}` input. Still\n * carries `providerExecuted` + `dynamic` so a host without a registered `edit`\n * tool degrades to a dynamic generic block instead of erroring.\n */\nfunction editCallFields(\n\tid: string,\n\tfilePath: string,\n\tdiff: string,\n): BlockToolPart {\n\tconst { oldString, newString } = reconstructEditStrings(diff);\n\treturn {\n\t\ttype: \"tool-call\",\n\t\ttoolCallId: id,\n\t\ttoolName: EDIT_TOOL_NAME,\n\t\tinput: safeJsonString({ filePath, oldString, newString }),\n\t\tproviderExecuted: true,\n\t\tdynamic: true,\n\t} as BlockToolPart;\n}\n\n/**\n * Build the opencode-native `edit` `tool-result` payload. opencode's processor\n * folds a tool-result's payload into `state.{title,metadata,output}`; the Edit\n * renderer's diff viewer keys on `metadata.diff`.\n */\nfunction editResultFields(\n\tid: string,\n\tfilePath: string,\n\tdiff: string,\n\tresult: unknown,\n): BlockToolPart {\n\tconst value = isRecord(result) ? result[\"value\"] : undefined;\n\tconst added =\n\t\tisRecord(value) && typeof value[\"linesAdded\"] === \"number\"\n\t\t\t? value[\"linesAdded\"]\n\t\t\t: undefined;\n\tconst removed =\n\t\tisRecord(value) && typeof value[\"linesRemoved\"] === \"number\"\n\t\t\t? value[\"linesRemoved\"]\n\t\t\t: undefined;\n\tconst counts =\n\t\tadded !== undefined || removed !== undefined\n\t\t\t? ` (+${added ?? 0}/-${removed ?? 0})`\n\t\t\t: \"\";\n\treturn {\n\t\ttype: \"tool-result\" as const,\n\t\ttoolCallId: id,\n\t\ttoolName: EDIT_TOOL_NAME,\n\t\tresult: {\n\t\t\ttitle: filePath,\n\t\t\tmetadata: { diff, diagnostics: {} },\n\t\t\toutput: `Edit applied${counts}.`,\n\t\t} as never,\n\t\tisError: false,\n\t\tproviderExecuted: true,\n\t\tdynamic: true,\n\t} as BlockToolPart;\n}\n\n/**\n * Per-turn blocks-mode tool bookkeeping, shared by the streaming and\n * `doGenerate` paths:\n * - `openToolCalls`: non-edit calls awaiting their result (id → original name).\n * - `pendingEdits`: edit calls held until their result, which carries the diff\n * needed to emit a schema-valid native `edit` call (id → filePath).\n */\ninterface BlockToolState {\n\topenToolCalls: Map<string, string>;\n\tpendingEdits: Map<string, string>;\n}\n\nfunction newBlockToolState(): BlockToolState {\n\treturn { openToolCalls: new Map(), pendingEdits: new Map() };\n}\n\n/** Parts to emit for a blocks-mode `tool-call` event (edits are buffered). */\nfunction blockToolCallParts(\n\tid: string,\n\tname: string,\n\tinput: unknown,\n\tstate: BlockToolState,\n): BlockToolPart[] {\n\tif (name === EDIT_TOOL_NAME) {\n\t\t// Hold the edit call until its result (which carries the diff).\n\t\tstate.pendingEdits.set(id, editFilePath(input));\n\t\treturn [];\n\t}\n\tstate.openToolCalls.set(id, name);\n\treturn [toolCallObj(id, name, input)];\n}\n\n/** Parts to emit for a blocks-mode `tool-result` event. */\nfunction blockToolResultParts(\n\tid: string,\n\tname: string,\n\tresult: unknown,\n\tisError: boolean,\n\tstate: BlockToolState,\n): BlockToolPart[] {\n\tif (state.pendingEdits.has(id)) {\n\t\tconst filePath = state.pendingEdits.get(id)!;\n\t\tstate.pendingEdits.delete(id);\n\t\tconst diff = isError ? null : editDiffString(result);\n\t\tif (diff && filePath) {\n\t\t\t// Native edit: opencode renders its built-in diff viewer.\n\t\t\treturn [\n\t\t\t\teditCallFields(id, filePath, diff),\n\t\t\t\teditResultFields(id, filePath, diff, result),\n\t\t\t];\n\t\t}\n\t\t// No usable diff (error / unexpected shape): safe generic fallback.\n\t\treturn [\n\t\t\ttoolCallObj(id, EDIT_TOOL_NAME, { path: filePath }),\n\t\t\ttoolResultObj(id, EDIT_TOOL_NAME, result, isError),\n\t\t];\n\t}\n\tstate.openToolCalls.delete(id);\n\treturn [toolResultObj(id, name, result, isError)];\n}\n\n/**\n * Parts that close out any tool call whose completion never arrived (run\n * errored/cancelled mid-tool) so blocks never dangle as \"Tool execution\n * aborted\". Clears the state.\n */\nfunction blockDanglingParts(state: BlockToolState): BlockToolPart[] {\n\tconst parts: BlockToolPart[] = [];\n\tfor (const [id, name] of state.openToolCalls) {\n\t\tparts.push(toolResultObj(id, name, DANGLING_TOOL_RESULT, true));\n\t}\n\tstate.openToolCalls.clear();\n\t// Edits whose result never arrived: safe generic block + synthetic error\n\t// (no diff available to build a native edit).\n\tfor (const [id, filePath] of state.pendingEdits) {\n\t\tparts.push(toolCallObj(id, EDIT_TOOL_NAME, { path: filePath }));\n\t\tparts.push(toolResultObj(id, EDIT_TOOL_NAME, DANGLING_TOOL_RESULT, true));\n\t}\n\tstate.pendingEdits.clear();\n\treturn parts;\n}\n\nexport const EMPTY_USAGE: LanguageModelV3Usage = {\n\tinputTokens: {\n\t\ttotal: undefined,\n\t\tnoCache: undefined,\n\t\tcacheRead: undefined,\n\t\tcacheWrite: undefined,\n\t},\n\toutputTokens: { total: undefined, text: undefined, reasoning: undefined },\n};\n\nexport function mapUsage(usage: CursorUsage): LanguageModelV3Usage {\n\treturn {\n\t\tinputTokens: {\n\t\t\ttotal: usage.inputTokens,\n\t\t\tnoCache: undefined,\n\t\t\tcacheRead: usage.cacheReadTokens,\n\t\t\tcacheWrite: usage.cacheWriteTokens,\n\t\t},\n\t\toutputTokens: {\n\t\t\ttotal: usage.outputTokens,\n\t\t\ttext: undefined,\n\t\t\treasoning: undefined,\n\t\t},\n\t};\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\tlet arg = \"\";\n\ttry {\n\t\tconst s = typeof input === \"string\" ? input : JSON.stringify(input);\n\t\tif (s && s !== \"{}\" && s !== '\"\"')\n\t\t\targ = ` ${s.length > 120 ? `${s.slice(0, 120)}…` : s}`;\n\t} catch {\n\t\t// Non-serializable input; show the name only.\n\t}\n\treturn `[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\tstatus: \"error\",\n\terror: \"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 * surfaced per {@link ToolDisplay} (default `\"blocks\"`): structured tool parts,\n * or reasoning lines when `\"reasoning\"` (see {@link formatToolCall}).\n */\nexport function cursorEventsToStream(\n\tevents: AsyncIterable<CursorEvent>,\n\ttoolDisplay: ToolDisplay = \"blocks\",\n): ReadableStream<LanguageModelV3StreamPart> {\n\treturn new ReadableStream<LanguageModelV3StreamPart>({\n\t\tasync start(controller) {\n\t\t\tcontroller.enqueue({ type: \"stream-start\", warnings: [] });\n\n\t\t\tlet textId: string | undefined;\n\t\t\tlet textCount = 0;\n\t\t\tlet reasoningId: string | undefined;\n\t\t\tlet reasoningCount = 0;\n\t\t\tlet usage: LanguageModelV3Usage | undefined;\n\t\t\tlet streamedText = false;\n\t\t\t// Blocks-mode tool bookkeeping (open non-edit calls + buffered edits).\n\t\t\tconst toolState = newBlockToolState();\n\t\t\tconst closeDanglingToolCalls = () => {\n\t\t\t\tfor (const part of blockDanglingParts(toolState)) {\n\t\t\t\t\tcontroller.enqueue(part);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst closeReasoning = () => {\n\t\t\t\tif (reasoningId) {\n\t\t\t\t\tcontroller.enqueue({ type: \"reasoning-end\", id: reasoningId });\n\t\t\t\t\treasoningId = undefined;\n\t\t\t\t}\n\t\t\t};\n\t\t\t// Close the open text part when reasoning resumes: hosts position a part\n\t\t\t// where it STARTED, so appending later text to an earlier part would\n\t\t\t// render the final answer above the reasoning that preceded it.\n\t\t\tconst closeText = () => {\n\t\t\t\tif (textId) {\n\t\t\t\t\tcontroller.enqueue({ type: \"text-end\", id: textId });\n\t\t\t\t\ttextId = undefined;\n\t\t\t\t}\n\t\t\t};\n\t\t\tconst ensureText = () => {\n\t\t\t\tcloseReasoning();\n\t\t\t\tif (!textId) {\n\t\t\t\t\ttextId = `text-${textCount++}`;\n\t\t\t\t\tcontroller.enqueue({ type: \"text-start\", id: textId });\n\t\t\t\t}\n\t\t\t\treturn textId;\n\t\t\t};\n\t\t\tconst ensureReasoning = () => {\n\t\t\t\tcloseText();\n\t\t\t\tif (!reasoningId) {\n\t\t\t\t\treasoningId = `reasoning-${reasoningCount++}`;\n\t\t\t\t\tcontroller.enqueue({ type: \"reasoning-start\", id: reasoningId });\n\t\t\t\t}\n\t\t\t\treturn reasoningId;\n\t\t\t};\n\t\t\tconst reasoningLine = (text: string) => {\n\t\t\t\tcontroller.enqueue({\n\t\t\t\t\ttype: \"reasoning-delta\",\n\t\t\t\t\tid: ensureReasoning(),\n\t\t\t\t\tdelta: text,\n\t\t\t\t});\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\tfor await (const event of events) {\n\t\t\t\t\tswitch (event.type) {\n\t\t\t\t\t\tcase \"text-delta\":\n\t\t\t\t\t\t\tstreamedText = true;\n\t\t\t\t\t\t\tcontroller.enqueue({\n\t\t\t\t\t\t\t\ttype: \"text-delta\",\n\t\t\t\t\t\t\t\tid: ensureText(),\n\t\t\t\t\t\t\t\tdelta: event.text,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"reasoning-delta\":\n\t\t\t\t\t\t\treasoningLine(event.text);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"tool-call\":\n\t\t\t\t\t\t\tif (toolDisplay === \"blocks\") {\n\t\t\t\t\t\t\t\tfor (const part of blockToolCallParts(\n\t\t\t\t\t\t\t\t\tevent.id,\n\t\t\t\t\t\t\t\t\tevent.name,\n\t\t\t\t\t\t\t\t\tevent.input,\n\t\t\t\t\t\t\t\t\ttoolState,\n\t\t\t\t\t\t\t\t)) {\n\t\t\t\t\t\t\t\t\tcontroller.enqueue(part);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treasoningLine(`\\n${formatToolCall(event.name, event.input)}\\n`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"tool-result\":\n\t\t\t\t\t\t\tif (toolDisplay === \"blocks\") {\n\t\t\t\t\t\t\t\tfor (const part of blockToolResultParts(\n\t\t\t\t\t\t\t\t\tevent.id,\n\t\t\t\t\t\t\t\t\tevent.name,\n\t\t\t\t\t\t\t\t\tevent.result,\n\t\t\t\t\t\t\t\t\tevent.isError,\n\t\t\t\t\t\t\t\t\ttoolState,\n\t\t\t\t\t\t\t\t)) {\n\t\t\t\t\t\t\t\t\tcontroller.enqueue(part);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (event.isError) {\n\t\t\t\t\t\t\t\treasoningLine(`[tool] ${event.name} failed\\n`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"usage\":\n\t\t\t\t\t\t\tusage = mapUsage(event.usage);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"finish\":\n\t\t\t\t\t\t\tif (!streamedText && event.text) {\n\t\t\t\t\t\t\t\tcontroller.enqueue({\n\t\t\t\t\t\t\t\t\ttype: \"text-delta\",\n\t\t\t\t\t\t\t\t\tid: ensureText(),\n\t\t\t\t\t\t\t\t\tdelta: event.text,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tcloseDanglingToolCalls();\n\t\t\t\tcloseReasoning();\n\t\t\t\tcloseText();\n\t\t\t\tcontroller.enqueue({\n\t\t\t\t\ttype: \"finish\",\n\t\t\t\t\tusage: usage ?? EMPTY_USAGE,\n\t\t\t\t\tfinishReason: FINISH_STOP,\n\t\t\t\t});\n\t\t\t\tcontroller.close();\n\t\t\t} catch (err) {\n\t\t\t\tcontroller.enqueue({ type: \"error\", error: err });\n\t\t\t\tcloseDanglingToolCalls();\n\t\t\t\tcloseReasoning();\n\t\t\t\tcloseText();\n\t\t\t\tcontroller.enqueue({\n\t\t\t\t\ttype: \"finish\",\n\t\t\t\t\tusage: usage ?? EMPTY_USAGE,\n\t\t\t\t\tfinishReason: FINISH_ERROR,\n\t\t\t\t});\n\t\t\t\tcontroller.close();\n\t\t\t}\n\t\t},\n\t});\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 * (consumed via `for await`). Tool activity is surfaced per {@link ToolDisplay}\n * (default `\"blocks\"`): structured tool parts (see {@link blockToolCallParts} /\n * {@link blockToolResultParts}), or folded into the reasoning text when\n * `\"reasoning\"`.\n */\nexport async function cursorEventsToContent(\n\tevents: AsyncIterable<CursorEvent>,\n\ttoolDisplay: ToolDisplay = \"blocks\",\n): Promise<{\n\tcontent: Array<LanguageModelV3Content>;\n\tfinishReason: LanguageModelV3FinishReason;\n\tusage: LanguageModelV3Usage;\n}> {\n\tconst content: Array<LanguageModelV3Content> = [];\n\tconst toolParts: Array<LanguageModelV3Content> = [];\n\t// Blocks-mode tool bookkeeping (open non-edit calls + buffered edits).\n\tconst toolState = newBlockToolState();\n\tlet text = \"\";\n\tlet reasoning = \"\";\n\tlet usage: LanguageModelV3Usage = EMPTY_USAGE;\n\tlet finishReason: LanguageModelV3FinishReason = FINISH_STOP;\n\n\ttry {\n\t\tfor await (const event of events) {\n\t\t\tswitch (event.type) {\n\t\t\t\tcase \"text-delta\":\n\t\t\t\t\ttext += event.text;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"reasoning-delta\":\n\t\t\t\t\treasoning += event.text;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tool-call\":\n\t\t\t\t\tif (toolDisplay === \"blocks\") {\n\t\t\t\t\t\tfor (const part of blockToolCallParts(\n\t\t\t\t\t\t\tevent.id,\n\t\t\t\t\t\t\tevent.name,\n\t\t\t\t\t\t\tevent.input,\n\t\t\t\t\t\t\ttoolState,\n\t\t\t\t\t\t)) {\n\t\t\t\t\t\t\ttoolParts.push(part as LanguageModelV3Content);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treasoning += `\\n${formatToolCall(event.name, event.input)}\\n`;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tool-result\":\n\t\t\t\t\tif (toolDisplay === \"blocks\") {\n\t\t\t\t\t\tfor (const part of blockToolResultParts(\n\t\t\t\t\t\t\tevent.id,\n\t\t\t\t\t\t\tevent.name,\n\t\t\t\t\t\t\tevent.result,\n\t\t\t\t\t\t\tevent.isError,\n\t\t\t\t\t\t\ttoolState,\n\t\t\t\t\t\t)) {\n\t\t\t\t\t\t\ttoolParts.push(part as LanguageModelV3Content);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (event.isError) {\n\t\t\t\t\t\treasoning += `[tool] ${event.name} failed\\n`;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"usage\":\n\t\t\t\t\tusage = mapUsage(event.usage);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"finish\":\n\t\t\t\t\tif (!text && event.text) text = event.text;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t} catch {\n\t\tfinishReason = FINISH_ERROR;\n\t}\n\n\t// Close out any tool call whose completion never arrived (see DANGLING_TOOL_RESULT).\n\tfor (const part of blockDanglingParts(toolState)) {\n\t\ttoolParts.push(part as LanguageModelV3Content);\n\t}\n\n\tif (reasoning) content.push({ type: \"reasoning\", text: reasoning });\n\tcontent.push(...toolParts);\n\tif (text) content.push({ type: \"text\", text });\n\n\treturn { 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.2",
3
+ "version": "0.1.0",
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",