cursor-opencode-provider 0.2.3 → 0.2.4
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 +27 -12
- package/dist/activity.d.ts +15 -0
- package/dist/activity.js +76 -0
- package/dist/context/build.js +0 -7
- package/dist/errors.d.ts +59 -0
- package/dist/errors.js +199 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +4 -0
- package/dist/language-model.d.ts +47 -4
- package/dist/language-model.js +617 -183
- package/dist/models.d.ts +7 -0
- package/dist/models.js +259 -79
- package/dist/plugin.js +35 -2
- package/dist/protocol/exec-variants.d.ts +24 -0
- package/dist/protocol/exec-variants.js +55 -0
- package/dist/protocol/messages.js +105 -25
- package/dist/protocol/request.d.ts +0 -2
- package/dist/protocol/request.js +0 -8
- package/dist/protocol/struct.js +1 -1
- package/dist/protocol/tool-call-bridge.js +2 -0
- package/dist/protocol/tools.d.ts +8 -1
- package/dist/protocol/tools.js +258 -32
- package/dist/session.d.ts +115 -8
- package/dist/session.js +362 -31
- package/dist/shell-timeout.d.ts +50 -0
- package/dist/shell-timeout.js +179 -0
- package/dist/transport/connect.d.ts +37 -1
- package/dist/transport/connect.js +679 -115
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -17,8 +17,8 @@ OpenCode driving a Cursor-routed Grok model through this provider:
|
|
|
17
17
|
- **OpenCode integration** — registers a `cursor` provider with auth hooks and cached model list
|
|
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
|
-
- **Streaming** — bidirectional Connect-RPC
|
|
21
|
-
- **Tool calls** — maps Cursor exec-server messages to AI SDK / OpenCode tool-call parts, including the Pi read/bash/edit/write/grep/find/ls request/result field range; mirrors finalized display-only todo/plan state into OpenCode; strips OpenCode's `read` XML envelope (`<path>`/`<content>` + `N:` prefixes) before returning content to Cursor so the model cannot echo the wrapper into writes
|
|
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/Task execution (Cursor's `generalPurpose` maps to OpenCode `general`; read-oriented `bugbot` reviews map to `explore`) and the Pi read/bash/edit/write/grep/find/ls request/result field range; enforces the exact current OpenCode agent catalog before emitting any tool call; mirrors finalized display-only todo/plan state into OpenCode; strips OpenCode's `read` XML envelope (`<path>`/`<content>` + `N:` prefixes) before returning content to Cursor so the model cannot echo the wrapper into writes
|
|
22
22
|
- **Thinking / reasoning** — surfaces extended-thinking deltas where the model supports it
|
|
23
23
|
|
|
24
24
|
## Requirements
|
|
@@ -47,7 +47,7 @@ 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.4"`.
|
|
51
51
|
|
|
52
52
|
### From a local clone
|
|
53
53
|
|
|
@@ -124,7 +124,7 @@ opencode run --model cursor/composer-2.5 "Hello from Cursor via OpenCode"
|
|
|
124
124
|
|
|
125
125
|
Cursor models often expose parameterized variants (effort, thinking, fast, context tier, …). The plugin materializes those as OpenCode **model variants**. In the TUI, pick one from the variant dialog or cycle with OpenCode’s `variant_cycle` keybind (default `ctrl+t`).
|
|
126
126
|
|
|
127
|
-
The selected variant’s Cursor parameter map is forwarded on the Run as `requested_model.parameters` (isolated under `providerOptions.cursor.cursorVariantParameters` so unrelated OpenCode options are not leaked onto the wire).
|
|
127
|
+
The selected variant’s Cursor parameter map is forwarded on the Run as `requested_model.parameters` (isolated under `providerOptions.cursor.cursorVariantParameters` so unrelated OpenCode options are not leaked onto the wire). The provider validates that explicit selection against the current cached tuple; malformed, reordered, or stale selections fail clearly instead of silently falling back to another variant.
|
|
128
128
|
|
|
129
129
|
#### 1M / long context
|
|
130
130
|
|
|
@@ -157,13 +157,15 @@ const cursor = createCursor({
|
|
|
157
157
|
// apiBaseURL: "https://api2.cursor.sh",
|
|
158
158
|
// agentBaseURL: "https://agentn.us.api5.cursor.sh", // explicit Run host override
|
|
159
159
|
// telemetryEnabled: true, // opt in to GetServerConfig telemetry
|
|
160
|
+
// retry: { maxAttempts: 3, baseDelayMs: 500, maxDelayMs: 8_000 },
|
|
161
|
+
// continuation: { heartbeatMs: 5_000, semanticIdleMs: 120_000, hardCapMs: 600_000 },
|
|
160
162
|
})
|
|
161
163
|
|
|
162
164
|
const model = cursor.languageModel("composer-2.5")
|
|
163
165
|
// model implements AI SDK LanguageModelV3 (doStream / doGenerate)
|
|
164
166
|
```
|
|
165
167
|
|
|
166
|
-
Pass either `accessToken` (JWT from OAuth or key exchange) or `apiKey` (raw `sk-...` key). Optional: `apiBaseURL`, `agentBaseURL`, `headers`, `telemetryEnabled`. The older `baseURL` option is still accepted as a legacy alias for `agentBaseURL`.
|
|
168
|
+
Pass either `accessToken` (JWT from OAuth or key exchange) or `apiKey` (raw `sk-...` key). Optional: `apiBaseURL`, `agentBaseURL`, `headers`, `telemetryEnabled`, `retry`, and `continuation`. Retries occur only before visible output or stateful server activity; unsafe replay is surfaced instead of risking duplicate text or tool work. 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`.
|
|
167
169
|
|
|
168
170
|
## Environment variables
|
|
169
171
|
|
|
@@ -177,7 +179,7 @@ Pass either `accessToken` (JWT from OAuth or key exchange) or `apiKey` (raw `sk-
|
|
|
177
179
|
| `XDG_CACHE_HOME` | When set, model/version caches go under `$XDG_CACHE_HOME/opencode/` instead of `~/.cache/opencode/` |
|
|
178
180
|
| `XDG_DATA_HOME` | When set, OpenCode `auth.json` is read from `$XDG_DATA_HOME/opencode/` instead of `~/.local/share/opencode/` |
|
|
179
181
|
|
|
180
|
-
`createCursor({ agentBaseURL })` overrides the agent Run host. When unset, the provider resolves the host from Cursor's `GetServerConfig` API (`agentUrlConfig.agentnUrl`, region-specific — e.g. `agentn.us.api5.cursor.sh`, `agent-gcpp-uswest.api5.cursor.sh`) once per process and holds it in memory (never written to disk), so a held-open Run stream is never repointed mid-session. Explicit agent overrides and GetServerConfig results are validated as HTTPS `*.cursor.sh` hosts (Cursor's agent hostnames vary and may change); non-`cursor.sh` hosts are rejected. The lookup sends `{ "telem_enabled": false }` by default; set `telemetryEnabled: true` in provider config, or `CURSOR_GET_SERVER_CONFIG_TELEMETRY=1` for OpenCode/plugin usage, to opt in. If the lookup fails or does not return a valid Cursor agent host, the model call fails clearly instead of falling back to `agentn.global.api5.cursor.sh`.
|
|
182
|
+
`createCursor({ agentBaseURL })` overrides the agent Run host. When unset, the provider resolves the host from Cursor's `GetServerConfig` API (`agentUrlConfig.agentnUrl`, region-specific — e.g. `agentn.us.api5.cursor.sh`, `agent-gcpp-uswest.api5.cursor.sh`) once per process and holds it in memory (never written to disk), so a held-open Run stream is never repointed mid-session. Explicit agent overrides and GetServerConfig results are validated as HTTPS `*.cursor.sh` hosts (Cursor's agent hostnames vary and may change); non-`cursor.sh` hosts are rejected. Shared HTTP/2 connections are rotated before they become server-aged, while existing Runs may finish on their original connection. The lookup sends `{ "telem_enabled": false }` by default; set `telemetryEnabled: true` in provider config, or `CURSOR_GET_SERVER_CONFIG_TELEMETRY=1` for OpenCode/plugin usage, to opt in. If the lookup fails or does not return a valid Cursor agent host, the model call fails clearly instead of falling back to `agentn.global.api5.cursor.sh`.
|
|
181
183
|
|
|
182
184
|
## Development
|
|
183
185
|
|
|
@@ -186,6 +188,7 @@ bun install # install dependencies
|
|
|
186
188
|
bun run build # compile TypeScript → dist/
|
|
187
189
|
bun run typecheck # type-check without emit
|
|
188
190
|
bun test # run unit tests
|
|
191
|
+
bun run test:node-http2 # Node-specific HTTP/2 detach regression
|
|
189
192
|
bun run test:watch # watch mode
|
|
190
193
|
```
|
|
191
194
|
|
|
@@ -214,6 +217,12 @@ OpenCode
|
|
|
214
217
|
| `src/transport/connect.ts` | HTTP/2 bidi stream and unary RPC calls |
|
|
215
218
|
| `src/protocol/` | Protobuf encode/decode, checksum/device ids, exec + display tool-call mapping (`tool-call-bridge.ts`) |
|
|
216
219
|
|
|
220
|
+
### Injected system guidance
|
|
221
|
+
|
|
222
|
+
The provider adds OpenCode-specific system guidance to normal tool-capable conversations, including tool availability and canonical workspace-path grounding. Compaction keeps its dedicated prompt unchanged.
|
|
223
|
+
|
|
224
|
+
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).
|
|
225
|
+
|
|
217
226
|
## Package exports
|
|
218
227
|
|
|
219
228
|
| Import path | Export |
|
|
@@ -221,8 +230,9 @@ OpenCode
|
|
|
221
230
|
| `cursor-opencode-provider` | `createCursor`, `CursorPlugin` (named + default) |
|
|
222
231
|
| `cursor-opencode-provider/plugin` | `CursorPlugin` (classic Hooks — auth) |
|
|
223
232
|
| `cursor-opencode-provider/plugin/v2` | OpenCode Effect/Promise v2 plugin (`ctx.aisdk.*`) |
|
|
233
|
+
| `cursor-opencode-provider/errors` | Structured provider error classes |
|
|
224
234
|
|
|
225
|
-
`CursorPluginV2`
|
|
235
|
+
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.
|
|
226
236
|
|
|
227
237
|
## Troubleshooting
|
|
228
238
|
|
|
@@ -232,19 +242,24 @@ OpenCode
|
|
|
232
242
|
| 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. |
|
|
233
243
|
| “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. |
|
|
234
244
|
| Empty or stale model list | Delete `~/.cache/opencode/cursor-models.json` (or under `$XDG_CACHE_HOME/opencode/`) 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. |
|
|
235
|
-
| Stream hangs or HTTP/2 errors |
|
|
236
|
-
| No response / silent 200 + close |
|
|
245
|
+
| Stream hangs or HTTP/2 errors | The provider keeps Cursor's Run open across OpenCode tool calls, rotates aged shared connections, and transparently rebases once from full OpenCode history if the Run ends before `turn_ended`. 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` and `rebasing fresh Run`. Restart OpenCode after rebuilding a local `file://` install. |
|
|
246
|
+
| No response / silent 200 + close | HTTP 200 alone is not a successful agent turn: the provider now requires Cursor's explicit `turn_ended`, captures HTTP/2 trailers/GOAWAY, and recovers once from bare EOF. The Run host still comes from in-memory `GetServerConfig` resolution; set `CURSOR_PROVIDER_DEBUG=1` to confirm the host and termination reason. |
|
|
247
|
+
| 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. |
|
|
248
|
+
| `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. |
|
|
237
249
|
| Need wire-level logs | Set `CURSOR_PROVIDER_DEBUG=1` (optional `CURSOR_PROVIDER_DEBUG_FILE`, default `/tmp/cursor-provider-debug.log`) and reproduce the issue. |
|
|
238
250
|
|
|
239
251
|
## Known limitations
|
|
240
252
|
|
|
241
253
|
- **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.
|
|
242
|
-
- **`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). Same discovery as OpenCode — including `.cursor/` paths only when listed in `instructions`. Cursor-only cloud/sandbox marketplace surfaces are omitted.
|
|
243
|
-
- **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,
|
|
244
|
-
- **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 Pi read/bash/edit/write/grep/find/ls calls use their typed request/result fields instead. Unknown display variants are logged; unknown exec variants fail
|
|
254
|
+
- **`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. OpenCode remains the permission authority: its coarse allow/ask/deny configuration is not fabricated into Cursor's unrelated allow/block instruction-list messages.
|
|
255
|
+
- **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.
|
|
256
|
+
- **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.
|
|
257
|
+
- **Tool availability is per OpenCode agent** — Cursor can request native capabilities such as Task even when a child or restricted OpenCode agent did not advertise the corresponding host tool. The provider prompts Cursor with the exact current catalog and checks every decoded host-tool exec request against it. An unavailable request is answered on Cursor's correlated typed result channel and is never emitted as OpenCode's `invalid` tool.
|
|
258
|
+
- **Background shells are non-interactive** — Cursor's native background-shell spawn is bridged through OpenCode's foreground-only `bash` tool by detaching the requested command, redirecting its output to `${TMPDIR:-/tmp}/cursor-opencode-bg.*`, and returning the real PID in Cursor's typed field-16 result. Shell-stream requests also preserve Cursor's foreground timeout, `timeout_behavior`, and `hard_timeout`: cancellation becomes a typed aborted exit, while a soft timeout requested with background behavior becomes a typed background handoff. OpenCode's internal `<shell_metadata>` envelope and the bridge's private markers are stripped before the result reaches the UI. Requests that require `write_shell_stdin` are rejected explicitly because OpenCode does not expose an interactive background-process lifecycle through its AI SDK tool interface.
|
|
245
259
|
- **Cursor-native interaction queries remain headless** — Cursor UI/approval *queries* (as distinct from display tool calls) still cannot be surfaced through the AI SDK provider interface. The normal system prompt redirects questions, planning, plan-mode transitions, and known-URL fetching to equivalent OpenCode tools only when they are advertised (`question`, `todowrite`, `plan_enter` / `plan_exit`, `webfetch`); native web/PR/MCP/image/SCM requests are declined so they remain behind OpenCode's tools and permissions. Compaction prompts are unchanged. Unknown future interaction variants fail the turn explicitly instead of hanging the Run stream.
|
|
246
260
|
- **Compaction resets Cursor conversation state** — the classic plugin marks OpenCode's `compaction` agent explicitly. On those turns the provider mints an isolated Cursor `conversation_id`, drops the prior checkpoint + KV blobs, preserves real tool-result text in the seed history, and re-advertises the session's last tool catalog while refusing execution during the summary itself. The first normal turn then rebases once more onto a fresh conversation seeded with OpenCode's compacted prompt and normal system instructions, so the summary-agent checkpoint cannot suppress later tool calls. Ordinary no-tool / `toolChoice:none` calls do not reset conversation state.
|
|
247
261
|
- **Conversation bindings and compaction catalogs are bounded** — process-global per-session bindings, prior tool catalogs, and pending post-compaction rebases use a 256-session LRU bound. Evicting a conversation binding also drops its checkpoint and KV blobs.
|
|
262
|
+
- **Interrupted Runs rebase once** — a remote EOF, Connect end-stream, trailer error, failed continuation write, or closed pending-tool session is never emitted as a successful `stop`. The provider starts one fresh Cursor conversation seeded from the complete OpenCode prompt (including trailing tool results and the live user request), re-advertises the same tools, and preserves compaction's no-execution behavior. A second interruption is returned as an explicit error. Recovery cannot see tokens already streamed to the UI on the interrupted attempt, so mid-generation recovery may briefly duplicate visible text/reasoning even though the rebase prompt asks the model not to repeat completed work.
|
|
248
263
|
- **No fallback models** — if Cursor’s `AvailableModels` API is unreachable and there is no local cache, the provider exposes no models.
|
|
249
264
|
|
|
250
265
|
## License
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type SessionActivitySource = {
|
|
2
|
+
lastActivityAt(sessionId: string): number | undefined;
|
|
3
|
+
};
|
|
4
|
+
/** Tracks OpenCode message progress and propagates it through subagent ancestry. */
|
|
5
|
+
export declare class SessionActivityTracker implements SessionActivitySource {
|
|
6
|
+
private readonly parentBySession;
|
|
7
|
+
private readonly lastActivityBySession;
|
|
8
|
+
linkSession(sessionId: string, parentId?: string): void;
|
|
9
|
+
recordActivity(sessionId: string, at?: number): void;
|
|
10
|
+
lastActivityAt(sessionId: string): number | undefined;
|
|
11
|
+
removeSession(sessionId: string): void;
|
|
12
|
+
clear(): void;
|
|
13
|
+
private prune;
|
|
14
|
+
}
|
|
15
|
+
export declare const sessionActivity: SessionActivityTracker;
|
package/dist/activity.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const MAX_ANCESTRY_DEPTH = 64;
|
|
2
|
+
const MAX_TRACKED_SESSIONS = 1_024;
|
|
3
|
+
const ACTIVITY_RETENTION_MS = 24 * 60 * 60 * 1_000;
|
|
4
|
+
/** Tracks OpenCode message progress and propagates it through subagent ancestry. */
|
|
5
|
+
export class SessionActivityTracker {
|
|
6
|
+
parentBySession = new Map();
|
|
7
|
+
lastActivityBySession = new Map();
|
|
8
|
+
linkSession(sessionId, parentId) {
|
|
9
|
+
if (!sessionId)
|
|
10
|
+
return;
|
|
11
|
+
this.prune(Date.now());
|
|
12
|
+
if (parentId && parentId !== sessionId)
|
|
13
|
+
this.parentBySession.set(sessionId, parentId);
|
|
14
|
+
else
|
|
15
|
+
this.parentBySession.delete(sessionId);
|
|
16
|
+
const existing = this.lastActivityBySession.get(sessionId);
|
|
17
|
+
if (existing !== undefined)
|
|
18
|
+
this.recordActivity(sessionId, existing);
|
|
19
|
+
this.prune(Date.now());
|
|
20
|
+
}
|
|
21
|
+
recordActivity(sessionId, at = Date.now()) {
|
|
22
|
+
if (!sessionId || !Number.isFinite(at))
|
|
23
|
+
return;
|
|
24
|
+
this.prune(at);
|
|
25
|
+
const visited = new Set();
|
|
26
|
+
let current = sessionId;
|
|
27
|
+
for (let depth = 0; current && depth < MAX_ANCESTRY_DEPTH; depth++) {
|
|
28
|
+
if (visited.has(current))
|
|
29
|
+
return;
|
|
30
|
+
visited.add(current);
|
|
31
|
+
const previous = this.lastActivityBySession.get(current);
|
|
32
|
+
if (previous === undefined || at > previous) {
|
|
33
|
+
// Map insertion order is our least-recently-active eviction order.
|
|
34
|
+
this.lastActivityBySession.delete(current);
|
|
35
|
+
this.lastActivityBySession.set(current, at);
|
|
36
|
+
}
|
|
37
|
+
current = this.parentBySession.get(current);
|
|
38
|
+
}
|
|
39
|
+
this.prune(at);
|
|
40
|
+
}
|
|
41
|
+
lastActivityAt(sessionId) {
|
|
42
|
+
this.prune(Date.now());
|
|
43
|
+
return this.lastActivityBySession.get(sessionId);
|
|
44
|
+
}
|
|
45
|
+
removeSession(sessionId) {
|
|
46
|
+
this.parentBySession.delete(sessionId);
|
|
47
|
+
this.lastActivityBySession.delete(sessionId);
|
|
48
|
+
}
|
|
49
|
+
clear() {
|
|
50
|
+
this.parentBySession.clear();
|
|
51
|
+
this.lastActivityBySession.clear();
|
|
52
|
+
}
|
|
53
|
+
prune(now) {
|
|
54
|
+
const oldestAllowed = now - ACTIVITY_RETENTION_MS;
|
|
55
|
+
for (const [sessionId, activityAt] of this.lastActivityBySession) {
|
|
56
|
+
if (activityAt >= oldestAllowed)
|
|
57
|
+
break;
|
|
58
|
+
this.lastActivityBySession.delete(sessionId);
|
|
59
|
+
this.parentBySession.delete(sessionId);
|
|
60
|
+
}
|
|
61
|
+
while (this.lastActivityBySession.size > MAX_TRACKED_SESSIONS) {
|
|
62
|
+
const oldest = this.lastActivityBySession.keys().next().value;
|
|
63
|
+
if (!oldest)
|
|
64
|
+
break;
|
|
65
|
+
this.lastActivityBySession.delete(oldest);
|
|
66
|
+
this.parentBySession.delete(oldest);
|
|
67
|
+
}
|
|
68
|
+
while (this.parentBySession.size > MAX_TRACKED_SESSIONS) {
|
|
69
|
+
const oldest = this.parentBySession.keys().next().value;
|
|
70
|
+
if (!oldest)
|
|
71
|
+
break;
|
|
72
|
+
this.parentBySession.delete(oldest);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export const sessionActivity = new SessionActivityTracker();
|
package/dist/context/build.js
CHANGED
|
@@ -27,11 +27,6 @@ export async function buildRequestContext(input) {
|
|
|
27
27
|
const mcpServerNames = Object.keys(config.mcp ?? {});
|
|
28
28
|
const flat = toolsToDescriptors(tools, providerIdentifier, mcpServerNames);
|
|
29
29
|
const nested = toolsToMcpDescriptors(tools, providerIdentifier, mcpServerNames);
|
|
30
|
-
const permission = config.permission;
|
|
31
|
-
const autoRun = permission === "allow" ||
|
|
32
|
-
(typeof permission === "object" &&
|
|
33
|
-
permission !== null &&
|
|
34
|
-
permission["*"] === "allow");
|
|
35
30
|
const ctx = {
|
|
36
31
|
env: buildEnv(workspaceRoot),
|
|
37
32
|
tools: flat,
|
|
@@ -72,8 +67,6 @@ export async function buildRequestContext(input) {
|
|
|
72
67
|
custom_subagents_info_complete: true,
|
|
73
68
|
mcp_file_system_info_complete: true,
|
|
74
69
|
mcp_info_complete: true,
|
|
75
|
-
user_permissions_auto_run: autoRun,
|
|
76
|
-
project_permissions_auto_run: autoRun,
|
|
77
70
|
};
|
|
78
71
|
if (plugins.length > 0) {
|
|
79
72
|
ctx.hooks_additional_context = plugins
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type CursorErrorOrigin = "local-cancel" | "transport" | "server" | "protocol" | "auth";
|
|
2
|
+
export type CursorErrorDiagnostics = {
|
|
3
|
+
statusCode?: number;
|
|
4
|
+
grpcStatus?: number | string;
|
|
5
|
+
rstCode?: number;
|
|
6
|
+
code?: string;
|
|
7
|
+
retryAfterMs?: number;
|
|
8
|
+
};
|
|
9
|
+
export type CursorProviderErrorOptions = CursorErrorDiagnostics & {
|
|
10
|
+
origin: CursorErrorOrigin;
|
|
11
|
+
transient: boolean;
|
|
12
|
+
replaySafe: boolean;
|
|
13
|
+
cause?: unknown;
|
|
14
|
+
};
|
|
15
|
+
/** Structured provider failure with retry and transport diagnostics. */
|
|
16
|
+
export declare class CursorProviderError extends Error {
|
|
17
|
+
readonly origin: CursorErrorOrigin;
|
|
18
|
+
readonly transient: boolean;
|
|
19
|
+
replaySafe: boolean;
|
|
20
|
+
readonly statusCode?: number;
|
|
21
|
+
readonly grpcStatus?: number | string;
|
|
22
|
+
readonly rstCode?: number;
|
|
23
|
+
readonly code?: string;
|
|
24
|
+
readonly retryAfterMs?: number;
|
|
25
|
+
constructor(message: string, options: CursorProviderErrorOptions);
|
|
26
|
+
}
|
|
27
|
+
export declare class CursorLocalCancellationError extends CursorProviderError {
|
|
28
|
+
constructor(message?: string, cause?: unknown);
|
|
29
|
+
}
|
|
30
|
+
export declare class CursorTransportError extends CursorProviderError {
|
|
31
|
+
constructor(message: string, options?: Omit<CursorProviderErrorOptions, "origin">);
|
|
32
|
+
}
|
|
33
|
+
export declare class CursorServerError extends CursorProviderError {
|
|
34
|
+
constructor(message: string, options: Omit<CursorProviderErrorOptions, "origin">);
|
|
35
|
+
}
|
|
36
|
+
export declare class CursorProtocolError extends CursorProviderError {
|
|
37
|
+
constructor(message: string, options?: Partial<CursorErrorDiagnostics> & {
|
|
38
|
+
cause?: unknown;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export declare class CursorAuthError extends CursorProviderError {
|
|
42
|
+
constructor(message?: string, options?: Partial<CursorErrorDiagnostics> & {
|
|
43
|
+
cause?: unknown;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export declare class CursorRetryExhaustedError extends CursorProviderError {
|
|
47
|
+
readonly attempts: number;
|
|
48
|
+
constructor(attempts: number, last: CursorProviderError);
|
|
49
|
+
}
|
|
50
|
+
export declare function isTransientGrpcStatus(status: number | string): boolean;
|
|
51
|
+
export declare function isAuthGrpcStatus(status: number | string): boolean;
|
|
52
|
+
export declare function cursorHttpError(operation: string, statusCode: number, diagnostics?: Omit<CursorErrorDiagnostics, "statusCode">): CursorProviderError;
|
|
53
|
+
export declare function cursorGrpcError(operation: string, grpcStatus: number | string, diagnostics?: Omit<CursorErrorDiagnostics, "grpcStatus">): CursorProviderError;
|
|
54
|
+
export declare function errorCode(error: unknown): string | undefined;
|
|
55
|
+
export declare function toCursorProviderError(error: unknown, options?: {
|
|
56
|
+
replaySafe: boolean;
|
|
57
|
+
fallback?: string;
|
|
58
|
+
}): CursorProviderError;
|
|
59
|
+
export declare function retrySuppressedError(cause: CursorProviderError, reason: string, attempt: number, maxAttempts: number): CursorProviderError;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/** Structured provider failure with retry and transport diagnostics. */
|
|
2
|
+
export class CursorProviderError extends Error {
|
|
3
|
+
origin;
|
|
4
|
+
transient;
|
|
5
|
+
replaySafe;
|
|
6
|
+
statusCode;
|
|
7
|
+
grpcStatus;
|
|
8
|
+
rstCode;
|
|
9
|
+
code;
|
|
10
|
+
retryAfterMs;
|
|
11
|
+
constructor(message, options) {
|
|
12
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
13
|
+
this.name = "CursorProviderError";
|
|
14
|
+
this.origin = options.origin;
|
|
15
|
+
this.transient = options.transient;
|
|
16
|
+
this.replaySafe = options.replaySafe;
|
|
17
|
+
this.statusCode = options.statusCode;
|
|
18
|
+
this.grpcStatus = options.grpcStatus;
|
|
19
|
+
this.rstCode = options.rstCode;
|
|
20
|
+
this.code = options.code;
|
|
21
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export class CursorLocalCancellationError extends CursorProviderError {
|
|
25
|
+
constructor(message = "Cursor request cancelled locally", cause) {
|
|
26
|
+
super(message, {
|
|
27
|
+
origin: "local-cancel",
|
|
28
|
+
transient: false,
|
|
29
|
+
replaySafe: false,
|
|
30
|
+
cause,
|
|
31
|
+
});
|
|
32
|
+
this.name = "CursorLocalCancellationError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export class CursorTransportError extends CursorProviderError {
|
|
36
|
+
constructor(message, options = {
|
|
37
|
+
transient: true,
|
|
38
|
+
replaySafe: true,
|
|
39
|
+
}) {
|
|
40
|
+
super(message, { ...options, origin: "transport" });
|
|
41
|
+
this.name = "CursorTransportError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export class CursorServerError extends CursorProviderError {
|
|
45
|
+
constructor(message, options) {
|
|
46
|
+
super(message, { ...options, origin: "server" });
|
|
47
|
+
this.name = "CursorServerError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export class CursorProtocolError extends CursorProviderError {
|
|
51
|
+
constructor(message, options = {}) {
|
|
52
|
+
super(message, {
|
|
53
|
+
...options,
|
|
54
|
+
origin: "protocol",
|
|
55
|
+
transient: false,
|
|
56
|
+
replaySafe: false,
|
|
57
|
+
});
|
|
58
|
+
this.name = "CursorProtocolError";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export class CursorAuthError extends CursorProviderError {
|
|
62
|
+
constructor(message = "Cursor authentication failed; reauthenticate with Cursor", options = {}) {
|
|
63
|
+
super(message, {
|
|
64
|
+
...options,
|
|
65
|
+
origin: "auth",
|
|
66
|
+
transient: false,
|
|
67
|
+
replaySafe: false,
|
|
68
|
+
});
|
|
69
|
+
this.name = "CursorAuthError";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export class CursorRetryExhaustedError extends CursorProviderError {
|
|
73
|
+
attempts;
|
|
74
|
+
constructor(attempts, last) {
|
|
75
|
+
super(`Cursor retry exhausted after ${attempts} attempts: ${last.message}`, {
|
|
76
|
+
origin: last.origin,
|
|
77
|
+
transient: false,
|
|
78
|
+
replaySafe: false,
|
|
79
|
+
statusCode: last.statusCode,
|
|
80
|
+
grpcStatus: last.grpcStatus,
|
|
81
|
+
rstCode: last.rstCode,
|
|
82
|
+
code: last.code,
|
|
83
|
+
retryAfterMs: last.retryAfterMs,
|
|
84
|
+
cause: last,
|
|
85
|
+
});
|
|
86
|
+
this.name = "CursorRetryExhaustedError";
|
|
87
|
+
this.attempts = attempts;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const TRANSIENT_NETWORK_CODES = new Set([
|
|
91
|
+
"ECONNABORTED",
|
|
92
|
+
"ECONNREFUSED",
|
|
93
|
+
"ECONNRESET",
|
|
94
|
+
"EHOSTUNREACH",
|
|
95
|
+
"ENETDOWN",
|
|
96
|
+
"ENETRESET",
|
|
97
|
+
"ENETUNREACH",
|
|
98
|
+
"ENOTFOUND",
|
|
99
|
+
"EPIPE",
|
|
100
|
+
"ERR_HTTP2_GOAWAY_SESSION",
|
|
101
|
+
"ERR_HTTP2_SESSION_ERROR",
|
|
102
|
+
"ERR_HTTP2_STREAM_CANCEL",
|
|
103
|
+
"ERR_HTTP2_STREAM_ERROR",
|
|
104
|
+
"ETIMEDOUT",
|
|
105
|
+
]);
|
|
106
|
+
export function isTransientGrpcStatus(status) {
|
|
107
|
+
const normalized = String(status).toLowerCase().replaceAll("-", "_");
|
|
108
|
+
return (normalized === "8" ||
|
|
109
|
+
normalized === "13" ||
|
|
110
|
+
normalized === "14" ||
|
|
111
|
+
normalized === "internal" ||
|
|
112
|
+
normalized === "resource_exhausted" ||
|
|
113
|
+
normalized === "unavailable");
|
|
114
|
+
}
|
|
115
|
+
export function isAuthGrpcStatus(status) {
|
|
116
|
+
const normalized = String(status).toLowerCase().replaceAll("-", "_");
|
|
117
|
+
return (normalized === "7" ||
|
|
118
|
+
normalized === "16" ||
|
|
119
|
+
normalized === "permission_denied" ||
|
|
120
|
+
normalized === "unauthenticated");
|
|
121
|
+
}
|
|
122
|
+
export function cursorHttpError(operation, statusCode, diagnostics = {}) {
|
|
123
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
124
|
+
return new CursorAuthError(`Cursor authentication failed (HTTP ${statusCode}); reauthenticate with Cursor`, { ...diagnostics, statusCode });
|
|
125
|
+
}
|
|
126
|
+
return new CursorServerError(`${operation} HTTP ${statusCode}`, {
|
|
127
|
+
...diagnostics,
|
|
128
|
+
statusCode,
|
|
129
|
+
transient: statusCode === 429 || statusCode >= 500,
|
|
130
|
+
replaySafe: true,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
export function cursorGrpcError(operation, grpcStatus, diagnostics = {}) {
|
|
134
|
+
if (isAuthGrpcStatus(grpcStatus)) {
|
|
135
|
+
return new CursorAuthError(`Cursor authentication failed (gRPC ${grpcStatus}); reauthenticate with Cursor`, { ...diagnostics, grpcStatus });
|
|
136
|
+
}
|
|
137
|
+
return new CursorServerError(`${operation} gRPC status ${grpcStatus}`, {
|
|
138
|
+
...diagnostics,
|
|
139
|
+
grpcStatus,
|
|
140
|
+
transient: isTransientGrpcStatus(grpcStatus),
|
|
141
|
+
replaySafe: true,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
export function errorCode(error) {
|
|
145
|
+
if (!error || typeof error !== "object")
|
|
146
|
+
return undefined;
|
|
147
|
+
const code = error.code;
|
|
148
|
+
return typeof code === "string" ? code : undefined;
|
|
149
|
+
}
|
|
150
|
+
export function toCursorProviderError(error, options = { replaySafe: false }) {
|
|
151
|
+
if (error instanceof CursorProviderError) {
|
|
152
|
+
error.replaySafe = options.replaySafe && error.replaySafe;
|
|
153
|
+
return error;
|
|
154
|
+
}
|
|
155
|
+
const code = errorCode(error);
|
|
156
|
+
const name = error instanceof Error ? error.name : "Error";
|
|
157
|
+
const message = error instanceof Error ? error.message : "";
|
|
158
|
+
if (name.startsWith("Auth") || /access token|api key|authentication/i.test(message)) {
|
|
159
|
+
return new CursorAuthError(undefined, { code, cause: error });
|
|
160
|
+
}
|
|
161
|
+
const httpStatus = /\bHTTP\s+(\d{3})\b/i.exec(message)?.[1];
|
|
162
|
+
if (httpStatus) {
|
|
163
|
+
const failure = cursorHttpError("Cursor request failed with", Number(httpStatus), { code });
|
|
164
|
+
failure.replaySafe = options.replaySafe && failure.replaySafe;
|
|
165
|
+
return failure;
|
|
166
|
+
}
|
|
167
|
+
if (code && TRANSIENT_NETWORK_CODES.has(code)) {
|
|
168
|
+
return new CursorTransportError(`Cursor transport failure (${code})`, {
|
|
169
|
+
transient: true,
|
|
170
|
+
replaySafe: options.replaySafe,
|
|
171
|
+
code,
|
|
172
|
+
cause: error,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (error instanceof TypeError) {
|
|
176
|
+
return new CursorProtocolError(options.fallback ?? "Invalid Cursor provider configuration", {
|
|
177
|
+
code,
|
|
178
|
+
cause: error,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return new CursorProtocolError(options.fallback ?? `Cursor provider failure (${name})`, {
|
|
182
|
+
code,
|
|
183
|
+
cause: error,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
export function retrySuppressedError(cause, reason, attempt, maxAttempts) {
|
|
187
|
+
const subject = cause.message.replace(/^Cursor Run stream /, "Cursor stream ");
|
|
188
|
+
return new CursorProviderError(`${subject} ${reason}; automatic retry unsafe (attempt ${attempt}/${maxAttempts})`, {
|
|
189
|
+
origin: cause.origin,
|
|
190
|
+
transient: false,
|
|
191
|
+
replaySafe: false,
|
|
192
|
+
statusCode: cause.statusCode,
|
|
193
|
+
grpcStatus: cause.grpcStatus,
|
|
194
|
+
rstCode: cause.rstCode,
|
|
195
|
+
code: cause.code,
|
|
196
|
+
retryAfterMs: cause.retryAfterMs,
|
|
197
|
+
cause,
|
|
198
|
+
});
|
|
199
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { CursorPlugin } from "./plugin.js";
|
|
2
|
+
import type { CursorContinuationOptions } from "./session.js";
|
|
3
|
+
export type CursorRetryOptions = {
|
|
4
|
+
/** Total attempts including the initial request. Default: 3. */
|
|
5
|
+
maxAttempts?: number;
|
|
6
|
+
/** Initial full-jitter backoff ceiling. Default: 500ms. */
|
|
7
|
+
baseDelayMs?: number;
|
|
8
|
+
/** Exponential backoff ceiling. Default: 8000ms. */
|
|
9
|
+
maxDelayMs?: number;
|
|
10
|
+
};
|
|
2
11
|
export type CreateCursorOptions = {
|
|
3
12
|
name: string;
|
|
4
13
|
accessToken?: string;
|
|
@@ -14,9 +23,14 @@ export type CreateCursorOptions = {
|
|
|
14
23
|
telemetryEnabled?: boolean;
|
|
15
24
|
/** OpenCode project / worktree directory for request_context collectors. */
|
|
16
25
|
workspaceRoot?: string;
|
|
26
|
+
/** Held-stream policy. Defaults: heartbeat 5s, semantic idle 120s, tool inactivity 10m. */
|
|
27
|
+
continuation?: CursorContinuationOptions;
|
|
28
|
+
/** Fresh-turn retry policy. Defaults: 3 attempts, 500ms base, 8000ms cap. */
|
|
29
|
+
retry?: CursorRetryOptions;
|
|
17
30
|
};
|
|
18
31
|
export declare function createCursor(options: CreateCursorOptions): {
|
|
19
32
|
languageModel(modelId: string): import("@ai-sdk/provider").LanguageModelV3;
|
|
20
33
|
};
|
|
21
34
|
export { CursorPlugin };
|
|
35
|
+
export type { CursorContinuationOptions, CursorContinuationPolicy } from "./session.js";
|
|
22
36
|
export default CursorPlugin;
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,10 @@ export function createCursor(options) {
|
|
|
11
11
|
}
|
|
12
12
|
export { CursorPlugin };
|
|
13
13
|
export default CursorPlugin;
|
|
14
|
+
// Keep root runtime exports plugin-safe. OpenCode's legacy plugin loader
|
|
15
|
+
// treats package-root exports as potential plugins, so extra public runtime
|
|
16
|
+
// APIs belong on subpaths such as "cursor-opencode-provider/errors".
|
|
17
|
+
//
|
|
14
18
|
// CursorPluginV2 is NOT re-exported here — see plugin-v2.ts.
|
|
15
19
|
// OpenCode's legacy plugin loader (getLegacyPlugins) iterates all exports
|
|
16
20
|
// and calls getServerPlugin on each; the v2 define() return is not a
|
package/dist/language-model.d.ts
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
import type { LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3StreamPart } from "@ai-sdk/provider";
|
|
2
|
-
import type { CreateCursorOptions } from "./index.js";
|
|
2
|
+
import type { CreateCursorOptions, CursorRetryOptions } from "./index.js";
|
|
3
3
|
import { type OpencodeToolDef } from "./protocol/tools.js";
|
|
4
4
|
import { type CursorSession } from "./session.js";
|
|
5
|
+
import { CursorProviderError } from "./errors.js";
|
|
5
6
|
import type { SeedHistoryMessage } from "./protocol/request.js";
|
|
6
7
|
export declare const MAX_TURN_STATE_SESSIONS = 256;
|
|
8
|
+
export type CursorRetryPolicy = {
|
|
9
|
+
maxAttempts: number;
|
|
10
|
+
baseDelayMs: number;
|
|
11
|
+
maxDelayMs: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function resolveRetryPolicy(options: CursorRetryOptions | undefined): CursorRetryPolicy;
|
|
14
|
+
export declare function connectFrameError(payload: string): CursorProviderError;
|
|
7
15
|
type V3Part = LanguageModelV3StreamPart;
|
|
8
16
|
export declare function createCursorLanguageModel(modelId: string, providerId: string, options: CreateCursorOptions): LanguageModelV3;
|
|
17
|
+
export declare function pumpWithRecovery(input: {
|
|
18
|
+
initialSession: CursorSession;
|
|
19
|
+
controller: ReadableStreamDefaultController<V3Part>;
|
|
20
|
+
abortSignal?: AbortSignal;
|
|
21
|
+
promptTokens?: number;
|
|
22
|
+
retryPolicy?: CursorRetryPolicy;
|
|
23
|
+
recover: () => Promise<CursorSession>;
|
|
24
|
+
onSession?: (session: CursorSession) => void;
|
|
25
|
+
maxRecoveries?: number;
|
|
26
|
+
}): Promise<CursorSession>;
|
|
9
27
|
/**
|
|
10
28
|
* OpenCode re-sends the full tool-result history on every continuation. Prefer
|
|
11
29
|
* the newest result that still has a live pending exec on its tagged session.
|
|
@@ -14,17 +32,27 @@ export declare function findContinuationSession(toolResults: Array<{
|
|
|
14
32
|
sessionId: string;
|
|
15
33
|
execId: number;
|
|
16
34
|
}>): CursorSession | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Deliver trailing tool results onto a live continuation session.
|
|
37
|
+
* Returns the same session when writes succeed (or only bridged results were
|
|
38
|
+
* cleared). Returns undefined after closing the session when a write fails, so
|
|
39
|
+
* the caller can rebase onto a fresh Run instead of pumping a dead stream.
|
|
40
|
+
*/
|
|
41
|
+
export declare function deliverContinuationResults(session: CursorSession, trailingToolResults: ExtractedToolResult[]): CursorSession | undefined;
|
|
17
42
|
/**
|
|
18
43
|
* Read the held-open stream, emitting stream parts, until the turn boundary:
|
|
19
44
|
* - a tool call (exec_server_message) → emit tool-call, finish "tool-calls",
|
|
20
45
|
* and KEEP the session open for the result on the next doStream call;
|
|
21
|
-
* - turn_ended
|
|
46
|
+
* - turn_ended → finish "stop" and close the session;
|
|
47
|
+
* - transport EOF before turn_ended → throw for one fresh-Run recovery.
|
|
22
48
|
*/
|
|
23
49
|
export declare function pump(session: CursorSession, controller: ReadableStreamDefaultController<V3Part>, ids: {
|
|
24
50
|
textId: string;
|
|
25
51
|
reasoningId: string;
|
|
52
|
+
promptTokens?: number;
|
|
26
53
|
}, abortSignal?: AbortSignal): Promise<void>;
|
|
27
54
|
type ExtractedToolResult = {
|
|
55
|
+
toolCallId: string;
|
|
28
56
|
sessionId: string;
|
|
29
57
|
execId: number;
|
|
30
58
|
toolName: string;
|
|
@@ -42,15 +70,30 @@ export declare function extractTrailingToolResults(prompt: LanguageModelV3CallOp
|
|
|
42
70
|
* Redirect only to OpenCode tools that are genuinely advertised this turn;
|
|
43
71
|
* compaction keeps its dedicated summary prompt unchanged.
|
|
44
72
|
*/
|
|
45
|
-
export declare function buildOpenCodeInteractionGuidance(tools: OpencodeToolDef[], isCompaction: boolean): string | undefined;
|
|
73
|
+
export declare function buildOpenCodeInteractionGuidance(tools: OpencodeToolDef[], isCompaction: boolean, workspaceRoot: string): string | undefined;
|
|
46
74
|
/** Rough char→token estimate for mid-turn usage before TurnEnded arrives. */
|
|
47
75
|
export declare function estimateTokens(chars: number): number;
|
|
76
|
+
/** Estimate current-request prompt tokens from the complete serialized V3 prompt. */
|
|
77
|
+
export declare function estimatePromptTokens(prompt: LanguageModelV3CallOptions["prompt"]): number;
|
|
78
|
+
/** Preserve cumulative Cursor counters as diagnostics, never as AI SDK request usage. */
|
|
79
|
+
export declare function cursorTurnEndedProviderMetadata(te: Record<string, unknown>): {
|
|
80
|
+
cursor: {
|
|
81
|
+
usageVersion: number;
|
|
82
|
+
inputTokensRaw: number;
|
|
83
|
+
outputTokensRaw: number;
|
|
84
|
+
cacheReadRaw: number;
|
|
85
|
+
cacheWriteRaw: number;
|
|
86
|
+
reasoningTokensRaw: number;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
48
89
|
/**
|
|
49
90
|
* Prior prompt turns for a seed ConversationStateStructure after compaction
|
|
50
91
|
* reset. Drops the trailing user message (live action), but preserves tool
|
|
51
92
|
* result payloads as labeled assistant observations for Cursor's text history.
|
|
52
93
|
*/
|
|
53
|
-
export declare function extractPromptHistory(prompt: LanguageModelV3CallOptions["prompt"]
|
|
94
|
+
export declare function extractPromptHistory(prompt: LanguageModelV3CallOptions["prompt"], options?: {
|
|
95
|
+
preserveTrailingUser?: boolean;
|
|
96
|
+
}): SeedHistoryMessage[];
|
|
54
97
|
/** OpenCode session id header, if present. */
|
|
55
98
|
export declare function opencodeSessionKey(callOptions: LanguageModelV3CallOptions): string | undefined;
|
|
56
99
|
/**
|