@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 +23 -11
- package/README.md +58 -17
- package/dist/provider/index.d.ts +8 -7
- package/dist/provider/index.js +190 -43
- package/dist/provider/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
8
|
-
npm `next` dist-tag for validation ahead of the stable `0.1.0`.
|
|
7
|
+
## [0.1.0] — 2026-06-10
|
|
9
8
|
|
|
10
|
-
|
|
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:
|
|
25
|
-
|
|
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
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
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
|
|
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` | `"
|
|
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
|
|
160
|
-
|
|
161
|
-
(
|
|
162
|
-
|
|
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
|
-
- **`"
|
|
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.
|
|
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
|
-
|
|
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": "
|
|
350
|
+
"options": { "toolDisplay": "reasoning" }
|
|
322
351
|
}
|
|
323
352
|
}
|
|
324
353
|
}
|
|
325
354
|
```
|
|
326
355
|
|
|
327
|
-
> Why
|
|
328
|
-
>
|
|
329
|
-
>
|
|
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.
|
package/dist/provider/index.d.ts
CHANGED
|
@@ -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
|
-
* - `"
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
* - `"
|
|
62
|
-
*
|
|
63
|
-
*
|
|
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
|
}
|
package/dist/provider/index.js
CHANGED
|
@@ -93,8 +93,14 @@ function latestUserMessage(prompt) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
// src/provider/stream-map.ts
|
|
96
|
-
var FINISH_STOP = {
|
|
97
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|
|
134
|
-
input: safeJsonString(
|
|
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
|
|
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:
|
|
144
|
-
result:
|
|
145
|
-
|
|
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: {
|
|
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: {
|
|
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 !== '""')
|
|
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 = "
|
|
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
|
|
282
|
+
const toolState = newBlockToolState();
|
|
189
283
|
const closeDanglingToolCalls = () => {
|
|
190
|
-
for (const
|
|
191
|
-
controller.enqueue(
|
|
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({
|
|
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({
|
|
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
|
-
|
|
239
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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({
|
|
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({
|
|
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({
|
|
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 = "
|
|
407
|
+
async function cursorEventsToContent(events, toolDisplay = "blocks") {
|
|
284
408
|
const content = [];
|
|
285
409
|
const toolParts = [];
|
|
286
|
-
const
|
|
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
|
-
|
|
303
|
-
|
|
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
|
-
|
|
313
|
-
|
|
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
|
|
331
|
-
toolParts.push(
|
|
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, {
|
|
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 {
|
|
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(
|
|
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
|
-
|
|
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
|
|
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",
|