cursor-opencode-provider 0.2.8 → 0.3.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/README.md +143 -11
- package/dist/auth.d.ts +7 -0
- package/dist/auth.js +9 -0
- package/dist/compaction-marker.d.ts +14 -0
- package/dist/compaction-marker.js +36 -0
- package/dist/context/agents.d.ts +5 -1
- package/dist/context/agents.js +90 -30
- package/dist/context/build.js +57 -8
- package/dist/context/env.js +6 -2
- package/dist/context/paths.d.ts +10 -0
- package/dist/context/paths.js +24 -0
- package/dist/context/plugins.js +16 -12
- package/dist/context/rules.js +26 -23
- package/dist/context/skills.js +8 -3
- package/dist/index.d.ts +1 -3
- package/dist/index.js +2 -8
- package/dist/language-model.js +121 -35
- package/dist/model-config.d.ts +15 -0
- package/dist/model-config.js +188 -0
- package/dist/opencode2/catalog.d.ts +33 -0
- package/dist/opencode2/catalog.js +85 -0
- package/dist/opencode2/integration.d.ts +23 -0
- package/dist/opencode2/integration.js +124 -0
- package/dist/opencode2/types.d.ts +316 -0
- package/dist/opencode2/types.js +18 -0
- package/dist/plugin-core.d.ts +27 -0
- package/dist/plugin-core.js +31 -0
- package/dist/plugin-opencode2.d.ts +3 -0
- package/dist/plugin-opencode2.js +293 -0
- package/dist/plugin-v2.d.ts +7 -0
- package/dist/plugin-v2.js +1 -15
- package/dist/plugin.d.ts +1 -15
- package/dist/plugin.js +11 -191
- package/dist/protocol/messages.d.ts +10 -0
- package/dist/protocol/messages.js +76 -1
- package/dist/protocol/tool-call-bridge.js +22 -2
- package/dist/protocol/tools.d.ts +57 -1
- package/dist/protocol/tools.js +277 -17
- package/dist/session-directory.d.ts +17 -0
- package/dist/session-directory.js +35 -0
- package/dist/session.d.ts +16 -2
- package/dist/session.js +70 -1
- package/dist/shell-timeout.d.ts +3 -1
- package/dist/shell-timeout.js +6 -2
- package/dist/usage.d.ts +30 -0
- package/dist/usage.js +77 -0
- package/dist/web-search-tool.d.ts +33 -0
- package/dist/web-search-tool.js +22 -0
- package/dist/web-tools.d.ts +9 -25
- package/dist/web-tools.js +34 -36
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ OpenCode driving a Cursor-routed Grok model through this provider:
|
|
|
18
18
|
- **Authentication** — browser OAuth (PKCE), or API key from [cursor.com/settings](https://cursor.com/settings)
|
|
19
19
|
- **Model discovery** — fetches available models from Cursor's API and caches them locally
|
|
20
20
|
- **Streaming** — bidirectional Connect-RPC Runs with stale-session rotation, health checks, semantic/read-idle deadlines, bounded replay-safe recovery, and activity-aware held tool continuations
|
|
21
|
-
- **Tool calls** — maps Cursor exec-server messages to AI SDK / OpenCode tool-call parts, including native subagent
|
|
21
|
+
- **Tool calls** — maps Cursor exec-server messages to AI SDK / OpenCode tool-call parts, including catalog-aware native subagent execution: exact advertised custom agents win, `unspecified` / `generalPurpose` select `general`, read-oriented `bugbot` / `security-review` select `explore`, and `cursor-guide` selects enabled Kilo `scout` before `explore`; MiMo `actor` is used instead of its work-item `task` tool when advertised. The bridge also covers the Pi read/bash/edit/write/grep/find/ls request/result range, enforces the exact current OpenCode tool catalog, mirrors finalized display-only todo/plan state, and strips OpenCode's `read` XML envelope before returning content to Cursor.
|
|
22
22
|
- **Thinking / reasoning** — surfaces extended-thinking deltas where the model supports it
|
|
23
23
|
|
|
24
24
|
## Requirements
|
|
@@ -29,9 +29,9 @@ OpenCode driving a Cursor-routed Grok model through this provider:
|
|
|
29
29
|
|
|
30
30
|
## Installation
|
|
31
31
|
|
|
32
|
-
### From npm
|
|
32
|
+
### From npm
|
|
33
33
|
|
|
34
|
-
Add the package
|
|
34
|
+
Add the package to OpenCode config. OpenCode installs npm plugins with Bun at startup (cached under `~/.cache/opencode/node_modules/`):
|
|
35
35
|
|
|
36
36
|
```json
|
|
37
37
|
{
|
|
@@ -47,7 +47,14 @@ Add the package name to OpenCode config. OpenCode installs npm plugins with Bun
|
|
|
47
47
|
}
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
Pin a version if you want: `"cursor-opencode-provider@0.2.
|
|
50
|
+
Pin a version if you want: `"cursor-opencode-provider@0.2.9"`.
|
|
51
|
+
|
|
52
|
+
You can also install it yourself first:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install cursor-opencode-provider
|
|
56
|
+
# or: bun add cursor-opencode-provider
|
|
57
|
+
```
|
|
51
58
|
|
|
52
59
|
### From a local clone
|
|
53
60
|
|
|
@@ -58,7 +65,7 @@ bun install
|
|
|
58
65
|
bun run build
|
|
59
66
|
```
|
|
60
67
|
|
|
61
|
-
Point config at the built files with absolute `file://` URLs:
|
|
68
|
+
Point classic OpenCode config at the built files with absolute `file://` URLs:
|
|
62
69
|
|
|
63
70
|
```json
|
|
64
71
|
{
|
|
@@ -73,6 +80,8 @@ Point config at the built files with absolute `file://` URLs:
|
|
|
73
80
|
}
|
|
74
81
|
```
|
|
75
82
|
|
|
83
|
+
For **OpenCode 2.0** local development, prefer the plugin path plus `CURSOR_OPENCODE2_DEV_ENTRY` — see [OpenCode 2.0 beta](#opencode-20-beta-opencode2).
|
|
84
|
+
|
|
76
85
|
## OpenCode setup
|
|
77
86
|
|
|
78
87
|
If the `cursor` provider block is omitted, the classic plugin auto-registers it on startup (as **Cursor Integration**) using this package's entry. Model entries come from the local cache, which is filled after auth and again on startup when the cache is empty but credentials remain.
|
|
@@ -89,6 +98,108 @@ Local clone equivalent: `"file:///absolute/path/to/cursor-opencode-provider/dist
|
|
|
89
98
|
|
|
90
99
|
That entry registers the provider via `ctx.aisdk.sdk` / `ctx.aisdk.language`. Keep the classic `plugin` entry for auth.
|
|
91
100
|
|
|
101
|
+
### OpenCode 2.0 beta (`opencode2`)
|
|
102
|
+
|
|
103
|
+
OpenCode 2.0 uses a different plugin API from the 1.18 `/v2/promise` one above — they
|
|
104
|
+
are source-incompatible, so this package ships a **separate** entrypoint for it.
|
|
105
|
+
Everything is registered by that one plugin. You do **not** need a `providers` entry,
|
|
106
|
+
and there is no `enabled` flag. Do not also load `./plugin/v2` or the classic `plugin`
|
|
107
|
+
entry under `opencode2`; they target the older APIs.
|
|
108
|
+
|
|
109
|
+
Authenticate with `/connect` inside `opencode2`, choose **Cursor**, then either
|
|
110
|
+
browser login or an API key. `CURSOR_API_KEY` is also picked up automatically.
|
|
111
|
+
|
|
112
|
+
#### From npm
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"plugins": ["cursor-opencode-provider/plugin/opencode2"]
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Pin a version if you want: `"cursor-opencode-provider@0.2.9/plugin/opencode2"`.
|
|
121
|
+
|
|
122
|
+
OpenCode 2.0 installs the published package into its host cache and loads the AI SDK
|
|
123
|
+
entry from there (`aisdk:cursor-opencode-provider`). No extra env vars are required.
|
|
124
|
+
|
|
125
|
+
#### From a local clone (`CURSOR_OPENCODE2_DEV_ENTRY`)
|
|
126
|
+
|
|
127
|
+
Loading only the local plugin file is not enough for end-to-end local testing: OpenCode
|
|
128
|
+
2.0's catalog still resolves the AI SDK package through `npm install` into
|
|
129
|
+
`<host-cache>/packages/…` unless you override it. Point
|
|
130
|
+
`CURSOR_OPENCODE2_DEV_ENTRY` at the built provider entry (`dist/index.js`, which exports
|
|
131
|
+
`createCursor`) so the host imports that file directly.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
cd /absolute/path/to/cursor-opencode-provider
|
|
135
|
+
bun install && bun run build
|
|
136
|
+
|
|
137
|
+
export CURSOR_OPENCODE2_DEV_ENTRY=/absolute/path/to/cursor-opencode-provider/dist/index.js
|
|
138
|
+
# optional while debugging:
|
|
139
|
+
# export CURSOR_PROVIDER_DEBUG=1
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Example OpenCode 2.0 config (for example `~/.config/opencode/opencode.json` or a
|
|
143
|
+
project `opencode.json`) that loads the built local plugin:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"plugins": [
|
|
148
|
+
"/absolute/path/to/cursor-opencode-provider/dist/plugin-opencode2.js"
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Equivalent forms for the same plugin entry:
|
|
154
|
+
|
|
155
|
+
- `"file:///absolute/path/to/cursor-opencode-provider/dist/plugin-opencode2.js"`
|
|
156
|
+
- `"../cursor-opencode-provider/dist/plugin-opencode2.js"` (relative to the config file; must start with `./` or `../`)
|
|
157
|
+
|
|
158
|
+
You can also drop a symlink into `.opencode/plugins/` (or `~/.config/opencode/plugins/`),
|
|
159
|
+
which OpenCode scans automatically:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
mkdir -p .opencode/plugins
|
|
163
|
+
ln -s /absolute/path/to/cursor-opencode-provider/dist/plugin-opencode2.js \
|
|
164
|
+
.opencode/plugins/cursor.js
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Important for local 2.0 development:
|
|
168
|
+
|
|
169
|
+
- **Export the env var before starting the daemon.** `opencode2 serve --service` inherits
|
|
170
|
+
env only at start time. After changing `CURSOR_OPENCODE2_DEV_ENTRY` or rebuilding
|
|
171
|
+
`dist/`, restart the service:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
opencode2 service stop
|
|
175
|
+
export CURSOR_OPENCODE2_DEV_ENTRY=/absolute/path/to/cursor-opencode-provider/dist/index.js
|
|
176
|
+
opencode2 service start
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`opencode2 service set` does **not** accept arbitrary env var names.
|
|
180
|
+
- **Unset `CURSOR_OPENCODE2_DEV_ENTRY` in production** so the catalog uses the published
|
|
181
|
+
`aisdk:cursor-opencode-provider` package again.
|
|
182
|
+
- **OpenCode does not install dependencies for local plugin files.** Keep the clone's
|
|
183
|
+
`bun install` intact and load the plugin from inside the clone (a copied-out file will
|
|
184
|
+
not resolve `protobufjs`).
|
|
185
|
+
- **Rebuild after every change** (`bun run build`). The daemon reads `dist/`, not `src/`.
|
|
186
|
+
|
|
187
|
+
Feature parity with the classic plugin, and the two places 2.0 differs:
|
|
188
|
+
|
|
189
|
+
| Classic plugin | OpenCode 2.0 plugin |
|
|
190
|
+
|---|---|
|
|
191
|
+
| `config` hook registers provider + models | `ctx.catalog.transform` |
|
|
192
|
+
| `auth` hook (OAuth + API key) | `ctx.integration.transform` + `/connect` |
|
|
193
|
+
| `tool` hook (`custom_websearch`) | `ctx.tool.transform` |
|
|
194
|
+
| `event` hook (session activity) | `ctx.event.subscribe` |
|
|
195
|
+
| `tool.execute.before` / `.after` | `ctx.tool.hook(...)` |
|
|
196
|
+
| `shell.env` injects the timeout wrapper | **No `shell.env` in 2.0** — the bash command is rewritten to call the wrapper file instead (same behavior, different mechanism) |
|
|
197
|
+
| `chat.params` flags compaction turns | **No `chat.params` in 2.0** — the compaction agent is detected in `ctx.session.hook("context")` and correlated by session id |
|
|
198
|
+
|
|
199
|
+
> **Beta.** OpenCode 2.0 and its plugin API are both beta and still changing. This
|
|
200
|
+
> entrypoint is built against `@opencode-ai/plugin@next` (`0.0.0-next-16281`) and is
|
|
201
|
+
> guarded by a compile-time conformance check, but expect churn.
|
|
202
|
+
|
|
92
203
|
### Authenticate
|
|
93
204
|
|
|
94
205
|
```bash
|
|
@@ -100,7 +211,7 @@ Choose the **cursor** provider, then one of:
|
|
|
100
211
|
| Method | Description |
|
|
101
212
|
|--------|-------------|
|
|
102
213
|
| **Cursor account (browser login)** | PKCE OAuth — opens cursor.com to sign in |
|
|
103
|
-
| **API key** | Paste a key from [cursor.com/settings](https://cursor.com/settings) (`
|
|
214
|
+
| **API key** | Paste a key from [cursor.com/settings](https://cursor.com/settings) (`crsr_...`) |
|
|
104
215
|
|
|
105
216
|
After login, the plugin fetches your available models and writes them to `<host-cache>/cursor-models.json` (default `~/.cache/opencode/`). On later startups, a missing, empty, expired, or old-schema cache is refreshed during config load when Cursor auth is available; an existing stale cache remains usable if refresh fails.
|
|
106
217
|
|
|
@@ -177,7 +288,7 @@ const model = cursor.languageModel("composer-2.5")
|
|
|
177
288
|
// model implements AI SDK LanguageModelV3 (doStream / doGenerate)
|
|
178
289
|
```
|
|
179
290
|
|
|
180
|
-
Pass either `accessToken` (JWT from OAuth or key exchange) or `apiKey` (raw `
|
|
291
|
+
Pass either `accessToken` (JWT from OAuth or key exchange) or `apiKey` (raw `crsr_...` key). Optional: `apiBaseURL`, `agentBaseURL`, `cacheDir`, `headers`, `telemetryEnabled`, `retry`, and `continuation`. `cacheDir` pins the host cache root for model/version caches and Cursor project metadata; when omitted, the provider uses OCP `@opencode-compat/profile` detect (if installed) or the local MiMo/Kilo/OpenCode heuristic described in [Paths](#paths-host-cache). Transient failures resume from the latest checkpoint produced by that Run, matching Cursor CLI; without an eligible checkpoint, retries remain limited to replay-safe attempts so completed text or tool work is not duplicated. Pending-tool inactivity is renewed by OpenCode activity from the session or its descendants. The older `baseURL` option is still accepted as a legacy alias for `agentBaseURL`.
|
|
181
292
|
|
|
182
293
|
## Environment variables
|
|
183
294
|
|
|
@@ -188,6 +299,7 @@ Pass either `accessToken` (JWT from OAuth or key exchange) or `apiKey` (raw `sk-
|
|
|
188
299
|
| `CURSOR_GET_SERVER_CONFIG_TELEMETRY` | Set to `1` or `true` to opt the `GetServerConfig` lookup into telemetry in OpenCode/plugin usage |
|
|
189
300
|
| `CURSOR_PROVIDER_DEBUG` | Set to `1` or `true` to enable wire-level debug logging |
|
|
190
301
|
| `CURSOR_PROVIDER_DEBUG_FILE` | Debug log path (default: `debug-<pid>.log` under `$TMPDIR/cursor-provider-logs-<uid>/`) |
|
|
302
|
+
| `CURSOR_OPENCODE2_DEV_ENTRY` | **Local OpenCode 2.0 only.** Absolute path to a built entry file (usually `dist/index.js`). Rewrites the catalog's AI SDK package to `aisdk:file://…` so the daemon imports your local build instead of `npm install`-ing the published package. Export it **before** `opencode2 service start`, then restart after rebuilds. Unset in production. |
|
|
191
303
|
| `XDG_CACHE_HOME` | Base for host cache dirs (`$XDG_CACHE_HOME/opencode/`, `…/mimocode/`, or `…/kilo/`) when no explicit `cacheDir` / OCP detect override |
|
|
192
304
|
| `MIMOCODE_HOME` | When set, host cache is `$MIMOCODE_HOME/cache` (MiMo) |
|
|
193
305
|
| `KILO_CONFIG_DIR` | When set, host cache is `$XDG_CACHE_HOME/kilo` |
|
|
@@ -220,7 +332,11 @@ OpenCode
|
|
|
220
332
|
| Module | Role |
|
|
221
333
|
|--------|------|
|
|
222
334
|
| `src/plugin.ts` | Classic OpenCode hooks: provider registration, OAuth, API key exchange, token refresh |
|
|
223
|
-
| `src/plugin-v2.ts` | OpenCode Effect/Promise v2 plugin (`ctx.aisdk.*`); load via `./plugin/v2` only |
|
|
335
|
+
| `src/plugin-v2.ts` | OpenCode 1.18 Effect/Promise v2 plugin (`ctx.aisdk.*`); load via `./plugin/v2` only |
|
|
336
|
+
| `src/plugin-opencode2.ts` | OpenCode 2.0 beta plugin (catalog, integration, tools, aisdk); load via `./plugin/opencode2` only |
|
|
337
|
+
| `src/opencode2/` | 2.0-only catalog mapping, integration/auth, and local API types |
|
|
338
|
+
| `src/plugin-core.ts` | Host-neutral SDK factory, package matching, API base/telemetry resolution |
|
|
339
|
+
| `src/model-config.ts` | Cursor model → OpenCode model mapping shared by every plugin surface |
|
|
224
340
|
| `src/index.ts` | `createCursor` factory; default export is `CursorPlugin` |
|
|
225
341
|
| `src/language-model.ts` | AI SDK `LanguageModelV3` adapter (`doStream`, `doGenerate`) |
|
|
226
342
|
| `src/session.ts` | Held-open agent Run session and pending exec correlation |
|
|
@@ -238,13 +354,26 @@ The provider adds OpenCode-specific system guidance to normal tool-capable conve
|
|
|
238
354
|
|
|
239
355
|
If this guidance causes issues, update `buildOpenCodeInteractionGuidance` in [`src/language-model.ts`](src/language-model.ts) and its focused coverage in [`test/prompt-history.test.ts`](test/prompt-history.test.ts).
|
|
240
356
|
|
|
357
|
+
### Native subagent routing
|
|
358
|
+
|
|
359
|
+
The provider reads the permission-filtered subagent catalog from the current host `task` or `actor` definition and advertises those recipients to Cursor as custom subagents. Exact configured names are preserved; unknown future Cursor subtype strings fall back to `general`. If a complete catalog omits every compatible recipient, the request fails on Cursor's typed subagent channel instead of selecting an arbitrary specialist.
|
|
360
|
+
|
|
361
|
+
| Host | Default recipients | Optional recipients |
|
|
362
|
+
|------|--------------------|---------------------|
|
|
363
|
+
| OpenCode | `general`, `explore` | Add `agent/*.md` or `agents/*.md` with `mode: subagent` or `all` |
|
|
364
|
+
| Kilo | `general`, `explore` | Enable `scout` with `KILO_EXPERIMENTAL_SCOUT=true` (or `KILO_EXPERIMENTAL=true`); add `.kilo/agent/*.md` custom agents with `mode: subagent` or `all` |
|
|
365
|
+
| MiMo | `general`, `explore` | Add `.mimocode/agent/*.md` with `mode: subagent` |
|
|
366
|
+
|
|
367
|
+
Kilo `scout` is reserved for external documentation, dependency repositories, and upstream source. Local workspace discovery continues to use `explore`. Primary modes and hidden/internal agents are never selected unless the host explicitly exposes them as spawnable.
|
|
368
|
+
|
|
241
369
|
## Package exports
|
|
242
370
|
|
|
243
371
|
| Import path | Export |
|
|
244
372
|
|-------------|--------|
|
|
245
373
|
| `cursor-opencode-provider` | `createCursor`, `CursorPlugin` (named + default) |
|
|
246
374
|
| `cursor-opencode-provider/plugin` | `CursorPlugin` (classic Hooks — auth) |
|
|
247
|
-
| `cursor-opencode-provider/plugin/v2` | OpenCode Effect/Promise v2 plugin (`ctx.aisdk.*`) |
|
|
375
|
+
| `cursor-opencode-provider/plugin/v2` | OpenCode 1.18 Effect/Promise v2 plugin (`ctx.aisdk.*`) |
|
|
376
|
+
| `cursor-opencode-provider/plugin/opencode2` | OpenCode 2.0 beta plugin (self-registering: catalog + auth + tools) |
|
|
248
377
|
| `cursor-opencode-provider/errors` | Structured provider error classes |
|
|
249
378
|
|
|
250
379
|
The package root intentionally stays plugin-safe for OpenCode's classic loader. `CursorPluginV2` and non-plugin runtime APIs are **not** re-exported from the package root; load them through their dedicated subpaths.
|
|
@@ -253,8 +382,9 @@ The package root intentionally stays plugin-safe for OpenCode's classic loader.
|
|
|
253
382
|
|
|
254
383
|
| Problem | What to try |
|
|
255
384
|
|---------|-------------|
|
|
256
|
-
| No Cursor models in the picker | Confirm Cursor auth (`opencode auth login` → **cursor
|
|
385
|
+
| No Cursor models in the picker | Confirm Cursor auth (`opencode auth login` → **cursor**, or `/connect` in `opencode2`). Restart OpenCode — if auth is present and the cache is empty, models are fetched on startup. Confirm `provider.cursor.npm` is the package name (or a built `file://…/dist/index.js`). |
|
|
257
386
|
| Auth / 401 errors mid-session | Re-login. OAuth and exchanged API-key JWTs refresh automatically when near expiry; a revoked refresh token needs a fresh login. |
|
|
387
|
+
| Local OpenCode 2.0 still runs the published package | Set `CURSOR_OPENCODE2_DEV_ENTRY` to an absolute `…/dist/index.js` path **before** starting the daemon, rebuild (`bun run build`), then `opencode2 service stop && opencode2 service start`. Loading only `dist/plugin-opencode2.js` is not enough — without the env var, 2.0 still `npm install`s the published package into the host cache. |
|
|
258
388
|
| “Too many connections from different devices” | Device IDs are derived from stable OS identifiers (same approach as the Cursor CLI). Avoid running multiple clients that invent different machine fingerprints for the same account. |
|
|
259
389
|
| Empty or stale model list | Delete `<host-cache>/cursor-models.json` (default `~/.cache/opencode/`, or MiMo/Kilo host cache) and restart OpenCode. Existing Cursor auth is enough to refill the cache; re-login only if auth itself is broken. Cache TTL is 24h; a failed background refresh keeps serving the previous cache. |
|
|
260
390
|
| Stream hangs or HTTP/2 errors | The provider keeps Cursor's Run open across OpenCode tool calls, rotates aged shared connections, resumes transient interruptions from the latest eligible Cursor checkpoint, and falls back to a fresh-history rebase only before stateful output when no checkpoint exists. Repeated interruption is surfaced as an error instead of a false successful stop; retry the turn after checking connectivity. With debug logging enabled, look for `Run interrupted`, `resuming … checkpoint`, or `rebasing fresh Run`. Restart OpenCode after rebuilding a local `file://` install. |
|
|
@@ -262,6 +392,7 @@ The package root intentionally stays plugin-safe for OpenCode's classic loader.
|
|
|
262
392
|
| Visible `<shell_metadata>` timeout text | Rebuild and restart a local install. Cursor's shell timeout is carried on its exec request; the provider now removes OpenCode's internal timeout envelope before it is rendered or stored, then returns Cursor's typed timeout or background-handoff event instead of treating the text as successful stdout. |
|
|
263
393
|
| `Unsupported Cursor exec variant …` | The error names the canonical Cursor CLI request field, its expected result field, and this provider's handling classification. `handling=unsupported` is a known Cursor-native capability without a safe OpenCode AI SDK bridge; `unknown request field` indicates new protocol drift; `handling=opencode-tool` or `provider-control` indicates a provider decoder/dispatch regression. Enable the debug log and report the full named error. |
|
|
264
394
|
| Need wire-level logs | Set `CURSOR_PROVIDER_DEBUG=1` (optional `CURSOR_PROVIDER_DEBUG_FILE`; the default is `debug-<pid>.log` under `$TMPDIR/cursor-provider-logs-<uid>/`) and reproduce the issue. |
|
|
395
|
+
| Cache **write** tokens always show as `0` | Not a mapping bug in this provider. Cursor's `TurnEnded` protobuf includes `cache_write` (field 4) and this package forwards it as AI SDK `inputTokens.cacheWrite` / OpenCode `cache.write`. Real agent sessions send field 4 as `0` even when `cache_read` is large on later turns. With debug logging, confirm `turn_ended raw wire fields: … f4:wt0=0` and `v3CacheWrite=0 source=turn_ended`. Cache **read** counts from the same message do populate correctly. |
|
|
265
396
|
|
|
266
397
|
## Security
|
|
267
398
|
|
|
@@ -270,6 +401,7 @@ Project `instructions` may reference absolute or `~/` paths (OpenCode parity). S
|
|
|
270
401
|
## Known limitations
|
|
271
402
|
|
|
272
403
|
- **Personal use / ToS** — this provider speaks Cursor’s private agent protocol (CLI-shaped client identity). Use only with an account you own; Cursor may change or restrict the API without notice.
|
|
404
|
+
- **`TurnEnded.cache_write` is unused by Cursor's agent** — usage comes from `interaction_update.turn_ended` (`input_tokens`, `output_tokens`, `cache_read`, `cache_write`, `reasoning_tokens`). The provider maps those 1:1 into AI SDK V3 nested usage. Captured live Runs consistently set `cache_write=0` on the wire while still reporting non-zero `cache_read` and `reasoning_tokens`; there is no alternate agent-stream field today that supplies write counts, so OpenCode/Kilo cannot show cache-write tokens for this provider until Cursor populates that counter.
|
|
273
405
|
- **`request_context` from OpenCode** — each Run sends Cursor `RequestContext` built from OpenCode project context (workspace env, `AGENTS.md` / `instructions`, `.opencode` agents/skills/plugins, git, layout, plus `.claude`/`.agents` skill fallbacks). Its canonical root is also used by the [injected system guidance](#injected-system-guidance). Same discovery as OpenCode — including `.cursor/` paths only when listed in `instructions`. Cursor-only cloud/sandbox marketplace surfaces are omitted. `env.workspace_paths` / `process_working_directory` stay on the real git workspace; Cursor's metadata root (`project_folder`, MCP `workspace_project_dir`, terminals/transcripts) is advertised under `<host-cache>/projects/<slug>/` (default `~/.cache/opencode/projects/…`) so dumps like `agent-tools/` do not land in the repo. OpenCode remains the permission authority: its coarse allow/ask/deny configuration is not fabricated into Cursor's unrelated allow/block instruction-list messages.
|
|
274
406
|
- **Configured MCP tools keep their upstream server id** — OpenCode builtins and plugin/custom tools are advertised under a synthetic `opencode` MCP server. Tools whose flattened name matches an MCP server in merged `opencode.json` configuration (`github_create_pull_request`, …) are grouped into that server's `mcp_descriptors` / `provider_identifier` (`github`, …). Unknown underscore-containing names stay under `opencode` rather than being guessed incorrectly. Cursor's MCP-state exec probe is answered from the same advertised descriptors before the actual tool request, using the full canonical tool-definition identity required by native `get_mcp_tools`; exec still reconstructs the full OpenCode tool id.
|
|
275
407
|
- **Display completions are notifications, not execution requests** — Cursor `tool_call_*` frames use a typed `ToolCall` oneof. The provider decodes them for diagnostics but only mirrors finalized todo/plan state (`update_todos_tool_call` / `create_plan_tool_call`) into advertised OpenCode `todowrite`; the completed payload already contains the authoritative final list. Interactive, data-returning, and side-effecting completions are never replayed as new tools because their result could not be returned to Cursor. Exec-backed native subagent/Task and Pi read/bash/edit/write/grep/find/ls calls use their typed request/result fields instead. Unknown display variants are logged. All 37 Cursor CLI exec request/result pairs are inventoried by field and name; known-but-unsupported and future unknown exec variants fail explicitly rather than receiving a guessed response that could deadlock the Run.
|
|
@@ -284,4 +416,4 @@ Project `instructions` may reference absolute or `~/` paths (OpenCode parity). S
|
|
|
284
416
|
|
|
285
417
|
## License
|
|
286
418
|
|
|
287
|
-
MIT
|
|
419
|
+
MIT
|
package/dist/auth.d.ts
CHANGED
|
@@ -32,6 +32,13 @@ export declare function clearBearerTokenCache(): void;
|
|
|
32
32
|
* Resolve a Bearer JWT for Cursor API calls. Prefer an already-exchanged
|
|
33
33
|
* `accessToken`; otherwise exchange (and cache) from `apiKey`, refreshing
|
|
34
34
|
* when the cached JWT is near expiry.
|
|
35
|
+
*
|
|
36
|
+
* `apiKey` is only ever a raw exchangeable secret when it has Cursor's
|
|
37
|
+
* `crsr_` prefix. Callers that generically forward whatever credential value
|
|
38
|
+
* they hold — e.g. a host's package-agnostic "aisdk:" SDK loader, which
|
|
39
|
+
* doesn't distinguish our OAuth vs. API-key connection methods and may pass
|
|
40
|
+
* an already-issued JWT through the `apiKey` field — hand us a token that's
|
|
41
|
+
* already good to use as-is; POSTing it to the exchange endpoint 401s.
|
|
35
42
|
*/
|
|
36
43
|
export declare function resolveBearerToken(input: {
|
|
37
44
|
accessToken?: string;
|
package/dist/auth.js
CHANGED
|
@@ -147,6 +147,13 @@ export function clearBearerTokenCache() {
|
|
|
147
147
|
* Resolve a Bearer JWT for Cursor API calls. Prefer an already-exchanged
|
|
148
148
|
* `accessToken`; otherwise exchange (and cache) from `apiKey`, refreshing
|
|
149
149
|
* when the cached JWT is near expiry.
|
|
150
|
+
*
|
|
151
|
+
* `apiKey` is only ever a raw exchangeable secret when it has Cursor's
|
|
152
|
+
* `crsr_` prefix. Callers that generically forward whatever credential value
|
|
153
|
+
* they hold — e.g. a host's package-agnostic "aisdk:" SDK loader, which
|
|
154
|
+
* doesn't distinguish our OAuth vs. API-key connection methods and may pass
|
|
155
|
+
* an already-issued JWT through the `apiKey` field — hand us a token that's
|
|
156
|
+
* already good to use as-is; POSTing it to the exchange endpoint 401s.
|
|
150
157
|
*/
|
|
151
158
|
export async function resolveBearerToken(input) {
|
|
152
159
|
if (input.accessToken)
|
|
@@ -154,6 +161,8 @@ export async function resolveBearerToken(input) {
|
|
|
154
161
|
if (!input.apiKey) {
|
|
155
162
|
throw new Error("Cursor provider: no access token or API key provided");
|
|
156
163
|
}
|
|
164
|
+
if (!input.apiKey.startsWith("crsr_"))
|
|
165
|
+
return input.apiKey;
|
|
157
166
|
const baseUrl = input.baseUrl ?? API_BASE;
|
|
158
167
|
const cached = _apiKeyTokenCache.get(input.apiKey);
|
|
159
168
|
if (cached && !isExpiringSoon(cached.accessToken)) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-scoped compaction marker for the OpenCode 2.0 plugin.
|
|
3
|
+
*
|
|
4
|
+
* The classic plugin learns that a turn belongs to OpenCode's compaction agent
|
|
5
|
+
* via the `chat.params` hook and forwards it as a provider option. OpenCode 2.0
|
|
6
|
+
* removed `chat.params`; the only place the runtime names the owning agent is
|
|
7
|
+
* `session.hook("context")`, which fires before dispatch. We record the fact
|
|
8
|
+
* there and let the provider read it back by session id.
|
|
9
|
+
*
|
|
10
|
+
* Bounded so a long-lived server cannot accumulate ids for dead sessions.
|
|
11
|
+
*/
|
|
12
|
+
export declare function markCompactionSession(sessionID: string, isCompaction: boolean): void;
|
|
13
|
+
export declare function isCompactionSession(sessionID: string | undefined): boolean;
|
|
14
|
+
export declare function clearCompactionSessions(): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-scoped compaction marker for the OpenCode 2.0 plugin.
|
|
3
|
+
*
|
|
4
|
+
* The classic plugin learns that a turn belongs to OpenCode's compaction agent
|
|
5
|
+
* via the `chat.params` hook and forwards it as a provider option. OpenCode 2.0
|
|
6
|
+
* removed `chat.params`; the only place the runtime names the owning agent is
|
|
7
|
+
* `session.hook("context")`, which fires before dispatch. We record the fact
|
|
8
|
+
* there and let the provider read it back by session id.
|
|
9
|
+
*
|
|
10
|
+
* Bounded so a long-lived server cannot accumulate ids for dead sessions.
|
|
11
|
+
*/
|
|
12
|
+
const MAX_TRACKED_SESSIONS = 256;
|
|
13
|
+
const compactionSessions = new Set();
|
|
14
|
+
export function markCompactionSession(sessionID, isCompaction) {
|
|
15
|
+
if (!sessionID)
|
|
16
|
+
return;
|
|
17
|
+
if (!isCompaction) {
|
|
18
|
+
compactionSessions.delete(sessionID);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Re-insert to keep insertion order meaningful for the eviction below.
|
|
22
|
+
compactionSessions.delete(sessionID);
|
|
23
|
+
compactionSessions.add(sessionID);
|
|
24
|
+
while (compactionSessions.size > MAX_TRACKED_SESSIONS) {
|
|
25
|
+
const oldest = compactionSessions.values().next().value;
|
|
26
|
+
if (oldest === undefined)
|
|
27
|
+
break;
|
|
28
|
+
compactionSessions.delete(oldest);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function isCompactionSession(sessionID) {
|
|
32
|
+
return typeof sessionID === "string" && compactionSessions.has(sessionID);
|
|
33
|
+
}
|
|
34
|
+
export function clearCompactionSessions() {
|
|
35
|
+
compactionSessions.clear();
|
|
36
|
+
}
|
package/dist/context/agents.d.ts
CHANGED
|
@@ -4,5 +4,9 @@ export type CollectedAgent = {
|
|
|
4
4
|
description: string;
|
|
5
5
|
prompt: string;
|
|
6
6
|
};
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Custom agents from the active OpenCode-compatible config roots. OCP may
|
|
9
|
+
* install a host-neutral bridge before loading this module; without one this
|
|
10
|
+
* remains the direct OpenCode discovery path.
|
|
11
|
+
*/
|
|
8
12
|
export declare function collectAgents(workspaceRoot: string): Promise<CollectedAgent[]>;
|
package/dist/context/agents.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { readdir, readFile, stat } from "node:fs/promises";
|
|
1
|
+
import { readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { opencodeGlobalConfigDirs, opencodeProjectConfigDirs } from "./paths.js";
|
|
4
4
|
async function exists(p) {
|
|
5
5
|
try {
|
|
6
6
|
await stat(p);
|
|
@@ -36,41 +36,101 @@ function parseAgentMarkdown(raw, fallbackName) {
|
|
|
36
36
|
}
|
|
37
37
|
return { name, description, prompt: body.slice(0, 20_000) };
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
function uniquePaths(paths) {
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
return paths.filter((value) => {
|
|
42
|
+
const normalized = path.resolve(value);
|
|
43
|
+
if (seen.has(normalized))
|
|
44
|
+
return false;
|
|
45
|
+
seen.add(normalized);
|
|
46
|
+
return true;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Read one host config root's `agent/` and `agents/` trees. Host loaders use
|
|
51
|
+
* both spellings and recurse through nested markdown files. Directory symlinks
|
|
52
|
+
* are followed with realpath cycle detection, matching host behavior without
|
|
53
|
+
* allowing a symlink loop to hang context construction.
|
|
54
|
+
*/
|
|
55
|
+
async function scanAgentRoot(root, out) {
|
|
56
|
+
if (!(await exists(root)))
|
|
47
57
|
return;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
continue;
|
|
52
|
-
const full = path.join(dir, name);
|
|
58
|
+
const visitedDirs = new Set();
|
|
59
|
+
const scanDir = async (dir, relativePrefix) => {
|
|
60
|
+
let canonical;
|
|
53
61
|
try {
|
|
54
|
-
|
|
55
|
-
const parsed = parseAgentMarkdown(raw, name.replace(/\.md$/, ""));
|
|
56
|
-
if (!out.has(parsed.name)) {
|
|
57
|
-
out.set(parsed.name, {
|
|
58
|
-
fullPath: path.resolve(full),
|
|
59
|
-
name: parsed.name,
|
|
60
|
-
description: parsed.description,
|
|
61
|
-
prompt: parsed.prompt,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
62
|
+
canonical = await realpath(dir);
|
|
64
63
|
}
|
|
65
64
|
catch {
|
|
66
|
-
|
|
65
|
+
return;
|
|
67
66
|
}
|
|
68
|
-
|
|
67
|
+
if (visitedDirs.has(canonical))
|
|
68
|
+
return;
|
|
69
|
+
visitedDirs.add(canonical);
|
|
70
|
+
let entries;
|
|
71
|
+
try {
|
|
72
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
73
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
for (const entry of entries) {
|
|
79
|
+
const full = path.join(dir, entry.name);
|
|
80
|
+
const relative = path.join(relativePrefix, entry.name);
|
|
81
|
+
let isDirectory = entry.isDirectory();
|
|
82
|
+
let isFile = entry.isFile();
|
|
83
|
+
if (entry.isSymbolicLink()) {
|
|
84
|
+
try {
|
|
85
|
+
const target = await stat(full);
|
|
86
|
+
isDirectory = target.isDirectory();
|
|
87
|
+
isFile = target.isFile();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (isDirectory) {
|
|
94
|
+
await scanDir(full, relative);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (!isFile || !entry.name.endsWith(".md"))
|
|
98
|
+
continue;
|
|
99
|
+
try {
|
|
100
|
+
const raw = await readFile(full, "utf-8");
|
|
101
|
+
const fallbackName = relative.replace(/\\/g, "/").replace(/\.md$/, "");
|
|
102
|
+
const parsed = parseAgentMarkdown(raw, fallbackName);
|
|
103
|
+
if (!out.has(parsed.name)) {
|
|
104
|
+
out.set(parsed.name, {
|
|
105
|
+
fullPath: path.resolve(full),
|
|
106
|
+
name: parsed.name,
|
|
107
|
+
description: parsed.description,
|
|
108
|
+
prompt: parsed.prompt,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
/* skip unreadable or malformed files without failing context build */
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
// Scan singular before plural, then let root ordering establish host
|
|
118
|
+
// precedence. This is deterministic and mirrors the hosts' brace glob.
|
|
119
|
+
await scanDir(path.join(root, "agent"), "agent");
|
|
120
|
+
await scanDir(path.join(root, "agents"), "agents");
|
|
69
121
|
}
|
|
70
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Custom agents from the active OpenCode-compatible config roots. OCP may
|
|
124
|
+
* install a host-neutral bridge before loading this module; without one this
|
|
125
|
+
* remains the direct OpenCode discovery path.
|
|
126
|
+
*/
|
|
71
127
|
export async function collectAgents(workspaceRoot) {
|
|
72
128
|
const out = new Map();
|
|
73
|
-
|
|
74
|
-
|
|
129
|
+
const roots = uniquePaths([
|
|
130
|
+
...opencodeProjectConfigDirs(workspaceRoot),
|
|
131
|
+
...opencodeGlobalConfigDirs(),
|
|
132
|
+
]);
|
|
133
|
+
for (const root of roots)
|
|
134
|
+
await scanAgentRoot(root, out);
|
|
75
135
|
return [...out.values()];
|
|
76
136
|
}
|
package/dist/context/build.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { toolsToDescriptors, toolsToMcpDescriptors } from "../protocol/tools.js";
|
|
2
|
+
import { extractHostSubagentCatalog, toolsToDescriptors, toolsToMcpDescriptors, } from "../protocol/tools.js";
|
|
3
3
|
import { collectRules } from "./rules.js";
|
|
4
4
|
import { collectSkills } from "./skills.js";
|
|
5
5
|
import { collectAgents } from "./agents.js";
|
|
@@ -9,6 +9,40 @@ import { collectProjectLayout } from "./layout.js";
|
|
|
9
9
|
import { buildEnv } from "./env.js";
|
|
10
10
|
import { ensureOpencodeProjectDir } from "./paths.js";
|
|
11
11
|
import { traceRequestContextPaths } from "../debug.js";
|
|
12
|
+
const DEFAULT_HOST_SUBAGENTS = [
|
|
13
|
+
{
|
|
14
|
+
name: "general",
|
|
15
|
+
description: "General-purpose agent for complex research and multi-step tasks.",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "explore",
|
|
19
|
+
description: "Read-only agent for searching and understanding the local codebase.",
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Convert the raw host executor catalog into the exact subagent list advertised
|
|
24
|
+
* to Cursor. `hostSubagents.complete` answers a narrow question: did the host's
|
|
25
|
+
* task/actor tool itself expose an exhaustive recipient list (schema enum or
|
|
26
|
+
* catalog marker)? OpenCode's task tool intentionally uses a plain string, so
|
|
27
|
+
* that raw flag is false even though we can still advertise a complete Cursor
|
|
28
|
+
* catalog by adding built-in defaults and discovered agent files.
|
|
29
|
+
*
|
|
30
|
+
* `custom_subagents_info_complete` must describe the final advertised list, not
|
|
31
|
+
* the raw extraction source. If there is no executor, the empty list is
|
|
32
|
+
* complete. If the host catalog is complete, use it verbatim. Otherwise we make
|
|
33
|
+
* the advertised set complete by augmenting with default host agents and all
|
|
34
|
+
* locally discovered agents.
|
|
35
|
+
*/
|
|
36
|
+
function buildAdvertisedSubagentCatalog(hostSubagents, discoveredAgents) {
|
|
37
|
+
if (!hostSubagents.executor)
|
|
38
|
+
return { agents: [], complete: true };
|
|
39
|
+
if (hostSubagents.complete)
|
|
40
|
+
return { agents: hostSubagents.agents, complete: true };
|
|
41
|
+
return {
|
|
42
|
+
agents: [...DEFAULT_HOST_SUBAGENTS, ...hostSubagents.agents, ...discoveredAgents],
|
|
43
|
+
complete: true,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
12
46
|
/**
|
|
13
47
|
* Full RequestContext payload for live UMA + exec #10 reply.
|
|
14
48
|
* Sourced from OpenCode discovery (and .claude/.agents skill fallbacks).
|
|
@@ -30,6 +64,26 @@ export async function buildRequestContext(input) {
|
|
|
30
64
|
const flat = toolsToDescriptors(tools, providerIdentifier, mcpServerNames);
|
|
31
65
|
const nested = toolsToMcpDescriptors(tools, providerIdentifier, mcpServerNames);
|
|
32
66
|
const projectDir = ensureOpencodeProjectDir(workspaceRoot);
|
|
67
|
+
const hostSubagents = extractHostSubagentCatalog(tools);
|
|
68
|
+
const advertisedSubagents = buildAdvertisedSubagentCatalog(hostSubagents, agents);
|
|
69
|
+
const discoveredByName = new Map(agents.map((agent) => [agent.name, agent]));
|
|
70
|
+
const advertisedByName = new Map();
|
|
71
|
+
for (const agent of advertisedSubagents.agents) {
|
|
72
|
+
if (!advertisedByName.has(agent.name))
|
|
73
|
+
advertisedByName.set(agent.name, agent);
|
|
74
|
+
}
|
|
75
|
+
const customSubagents = [...advertisedByName.values()].map((agent) => {
|
|
76
|
+
const discovered = discoveredByName.get(agent.name);
|
|
77
|
+
return {
|
|
78
|
+
full_path: discovered?.fullPath ?? "",
|
|
79
|
+
name: agent.name,
|
|
80
|
+
description: discovered?.description || agent.description || "Host-configured subagent.",
|
|
81
|
+
// The host applies the real configured prompt when Task/Actor executes.
|
|
82
|
+
// Cursor only needs enough context to select the recipient intentionally.
|
|
83
|
+
prompt: discovered?.prompt ||
|
|
84
|
+
`Delegate to the host-configured ${agent.name} subagent; its host instructions and tools apply.`,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
33
87
|
const ctx = {
|
|
34
88
|
env: buildEnv(workspaceRoot),
|
|
35
89
|
tools: flat,
|
|
@@ -45,12 +99,7 @@ export async function buildRequestContext(input) {
|
|
|
45
99
|
content: s.content,
|
|
46
100
|
description: s.description,
|
|
47
101
|
})),
|
|
48
|
-
custom_subagents:
|
|
49
|
-
full_path: a.fullPath,
|
|
50
|
-
name: a.name,
|
|
51
|
-
description: a.description,
|
|
52
|
-
prompt: a.prompt,
|
|
53
|
-
})),
|
|
102
|
+
custom_subagents: customSubagents,
|
|
54
103
|
mcp_file_system_options: {
|
|
55
104
|
enabled: true,
|
|
56
105
|
// Cursor metadata root (mcps / agent-tools), not the git workspace.
|
|
@@ -74,7 +123,7 @@ export async function buildRequestContext(input) {
|
|
|
74
123
|
git_repo_info_complete: true,
|
|
75
124
|
git_status_info_complete: true,
|
|
76
125
|
agent_skills_info_complete: true,
|
|
77
|
-
custom_subagents_info_complete:
|
|
126
|
+
custom_subagents_info_complete: advertisedSubagents.complete,
|
|
78
127
|
mcp_file_system_info_complete: true,
|
|
79
128
|
mcp_info_complete: true,
|
|
80
129
|
};
|
package/dist/context/env.js
CHANGED
|
@@ -20,6 +20,10 @@ export function buildEnv(workspaceRoot) {
|
|
|
20
20
|
// Cursor's project_folder is a metadata root (agent-tools, terminals, …),
|
|
21
21
|
// not the git workspace. Keep dumps under OpenCode cache.
|
|
22
22
|
const projectFolder = ensureOpencodeProjectDir(cwd);
|
|
23
|
+
// process_working_directory must match the workspace, not the host process
|
|
24
|
+
// cwd. OpenCode 2.0's daemon is long-lived and often started from $HOME, so
|
|
25
|
+
// process.cwd() would advertise the home folder to Cursor and the model would
|
|
26
|
+
// treat that as its shell cwd. workspace_paths already uses `cwd` above.
|
|
23
27
|
const env = {
|
|
24
28
|
os_version: osVersion,
|
|
25
29
|
workspace_paths: [cwd],
|
|
@@ -30,8 +34,8 @@ export function buildEnv(workspaceRoot) {
|
|
|
30
34
|
project_folder: projectFolder,
|
|
31
35
|
terminals_folder: path.join(projectFolder, "terminals"),
|
|
32
36
|
agent_transcripts_folder: path.join(projectFolder, "agent-transcripts"),
|
|
33
|
-
process_working_directory:
|
|
34
|
-
is_working_dir_home_dir:
|
|
37
|
+
process_working_directory: cwd,
|
|
38
|
+
is_working_dir_home_dir: cwd === path.resolve(home),
|
|
35
39
|
};
|
|
36
40
|
trace(`buildEnv: workspace_paths=${JSON.stringify(env.workspace_paths)} ` +
|
|
37
41
|
`project_folder=${env.project_folder} ` +
|
package/dist/context/paths.d.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
export type HostPathEnv = NodeJS.ProcessEnv;
|
|
2
|
+
/** Host-neutral bridge installed by OCP before an unchanged provider is loaded. */
|
|
3
|
+
export declare const OPENCODE_PATH_BRIDGE: unique symbol;
|
|
4
|
+
export type OpenCodePathBridge = {
|
|
5
|
+
projectConfigDirs: (workspaceRoot: string) => string[];
|
|
6
|
+
globalConfigDirs: () => string[];
|
|
7
|
+
configFileNames?: string[];
|
|
8
|
+
};
|
|
9
|
+
export declare function opencodeProjectConfigDirs(workspaceRoot: string): string[];
|
|
10
|
+
export declare function opencodeGlobalConfigDirs(): string[];
|
|
11
|
+
export declare function opencodeConfigFileNames(): string[];
|
|
2
12
|
type CompatDetectResult = {
|
|
3
13
|
id: string;
|
|
4
14
|
supported: boolean;
|