cursor-opencode-provider 0.7.1 → 0.7.3
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 +8 -5
- package/dist/context/agents.js +1 -1
- package/dist/context/build.d.ts +2 -0
- package/dist/context/build.js +22 -10
- package/dist/context/epoch.d.ts +59 -0
- package/dist/context/epoch.js +224 -0
- package/dist/context/frozen.d.ts +4 -2
- package/dist/context/frozen.js +31 -11
- package/dist/context/index.d.ts +2 -0
- package/dist/context/index.js +2 -0
- package/dist/context/overlay.d.ts +44 -0
- package/dist/context/overlay.js +96 -0
- package/dist/debug.d.ts +9 -1
- package/dist/debug.js +46 -5
- package/dist/host-event-bridge.d.ts +22 -0
- package/dist/host-event-bridge.js +22 -0
- package/dist/image-input.js +36 -11
- package/dist/index.d.ts +3 -0
- package/dist/language-model.d.ts +51 -3
- package/dist/language-model.js +569 -140
- package/dist/model-config.js +5 -1
- package/dist/plugin-opencode2.js +10 -4
- package/dist/plugin.js +26 -15
- package/dist/pricing-data.d.ts +40 -0
- package/dist/pricing-data.js +40 -0
- package/dist/protocol/ask-question.d.ts +8 -9
- package/dist/protocol/ask-question.js +8 -9
- package/dist/protocol/conversation-bind.js +18 -8
- package/dist/protocol/create-plan.d.ts +6 -7
- package/dist/protocol/create-plan.js +4 -5
- package/dist/protocol/messages.js +18 -1
- package/dist/protocol/struct.js +1 -1
- package/dist/protocol/switch-mode.d.ts +3 -4
- package/dist/protocol/switch-mode.js +21 -12
- package/dist/protocol/tool-call-bridge.d.ts +4 -1
- package/dist/protocol/tool-call-bridge.js +27 -5
- package/dist/protocol/tools.d.ts +4 -2
- package/dist/protocol/tools.js +346 -55
- package/dist/session-directory.d.ts +23 -4
- package/dist/session-directory.js +36 -4
- package/dist/session.d.ts +23 -3
- package/dist/session.js +68 -8
- package/dist/shared.d.ts +2 -0
- package/dist/shared.js +2 -0
- package/dist/transport/connect.js +78 -10
- package/dist/transport/https-proxy.d.ts +56 -0
- package/dist/transport/https-proxy.js +348 -0
- package/dist/usage.d.ts +11 -0
- package/dist/usage.js +33 -7
- package/package.json +1 -1
package/dist/model-config.js
CHANGED
|
@@ -154,9 +154,13 @@ export function modelInfoToConfig(mi, options = {}) {
|
|
|
154
154
|
// tier is a variant parameter. Long-context choices are therefore emitted as
|
|
155
155
|
// separate OpenCode entries by modelsToConfig.
|
|
156
156
|
const documentedContext = getDocumentedCursorModelContext(mi.id);
|
|
157
|
+
// Cursor Auto (`default`) has no static maxContext on the model list, but
|
|
158
|
+
// live checkpoints report 256k for the base tier — the previous 200k
|
|
159
|
+
// fallback made the host context meter disagree with Cursor.
|
|
160
|
+
const fallbackContext = mi.id === "default" ? 256_000 : 200_000;
|
|
157
161
|
const context = contextTier === "long"
|
|
158
162
|
? (mi.maxContextForMaxMode ?? documentedContext?.maxContextForMaxMode ?? 1_000_000)
|
|
159
|
-
: (mi.maxContext ?? documentedContext?.maxContext ??
|
|
163
|
+
: (mi.maxContext ?? documentedContext?.maxContext ?? fallbackContext);
|
|
160
164
|
// OpenCode's overflow/compaction/UI use limit.context; generation and
|
|
161
165
|
// thinking budgets use limit.output. models.dev 1M peers advertise
|
|
162
166
|
// 64k–128k output — a tiny cap makes long-context sessions feel broken
|
package/dist/plugin-opencode2.js
CHANGED
|
@@ -9,6 +9,7 @@ import { captureCursorShellResult, cursorShellEnvForCommand, prepareCursorShellA
|
|
|
9
9
|
import { applyCursorProviderInventory, CURSOR_INTEGRATION_ID } from "./opencode2/catalog.js";
|
|
10
10
|
import { applyCursorIntegration, resolveCursorAccessToken } from "./opencode2/integration.js";
|
|
11
11
|
import { registerTodoTools } from "./opencode2/todo-tools.js";
|
|
12
|
+
import { OPENCODE_2_TOOL_DIALECT } from "./protocol/tools.js";
|
|
12
13
|
import { clearSessionTodos } from "./todo-store.js";
|
|
13
14
|
import { markCompactionSession } from "./compaction-marker.js";
|
|
14
15
|
import { markSessionDirectory } from "./session-directory.js";
|
|
@@ -213,11 +214,14 @@ const plugin = {
|
|
|
213
214
|
...(token ? { accessToken: token } : {}),
|
|
214
215
|
// Static fallback only. This hook fires once per model/package, not
|
|
215
216
|
// per session, and 2.0 runs one daemon across many projects — the
|
|
216
|
-
// real per-request directory comes from
|
|
217
|
-
// below via `getSessionDirectory
|
|
217
|
+
// real per-request directory comes from `x-opencode-directory` and
|
|
218
|
+
// the session.context hook below via `getSessionDirectory`.
|
|
218
219
|
workspaceRoot,
|
|
219
220
|
cacheDir,
|
|
220
221
|
...event.options,
|
|
222
|
+
// Keep after `event.options` so the OC2 plugin always selects the
|
|
223
|
+
// `path`/`shell` dialect when advertised schemas are opaque.
|
|
224
|
+
defaultDialect: OPENCODE_2_TOOL_DIALECT,
|
|
221
225
|
});
|
|
222
226
|
}));
|
|
223
227
|
await track(ctx.aisdk.hook("language", (event) => {
|
|
@@ -324,8 +328,10 @@ const plugin = {
|
|
|
324
328
|
}
|
|
325
329
|
const rememberSessionDirectory = async (sessionID) => {
|
|
326
330
|
try {
|
|
327
|
-
const info = await ctx.session.get({ sessionID });
|
|
328
|
-
|
|
331
|
+
const info = (await ctx.session.get({ sessionID }));
|
|
332
|
+
// OpenCode 2.0 stable exposes a flat `directory`; older shapes nest it
|
|
333
|
+
// under `location.directory`. Prefer the flat field when both exist.
|
|
334
|
+
markSessionDirectory(sessionID, info.directory ?? info.location?.directory);
|
|
329
335
|
}
|
|
330
336
|
catch {
|
|
331
337
|
// Best effort — falls back to the static workspaceRoot above.
|
package/dist/plugin.js
CHANGED
|
@@ -17,6 +17,7 @@ import { sessionActivity } from "./activity.js";
|
|
|
17
17
|
import { createOpenCodeWebSearchTool, openCodeWebSearchTool } from "./web-search-tool.js";
|
|
18
18
|
import { createCursorImageSaveTool, cursorImageSaveTool } from "./image-save-tool.js";
|
|
19
19
|
import { createPlanExecutionKickoffText, setPlanExecutionKickoff, } from "./plan-execution-kickoff.js";
|
|
20
|
+
import { dispatchHostEventBridge } from "./host-event-bridge.js";
|
|
20
21
|
const MODULE_URL = new URL("./index.js", import.meta.url).href;
|
|
21
22
|
export async function loadClassicTools(options = {}) {
|
|
22
23
|
const configDir = options.configDirs?.[0] ?? opencodeGlobalConfigDirs()[0];
|
|
@@ -56,21 +57,25 @@ export async function CursorPlugin(input) {
|
|
|
56
57
|
const cacheDir = opencodeGlobalCacheDir();
|
|
57
58
|
const apiBaseURL = cursorApiBaseURL();
|
|
58
59
|
const classicTools = await loadClassicTools();
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
// Install the OpenCode plan-exit-shaped kickoff only when the OpenCode client
|
|
61
|
+
// exposes that structural API. Without it, no synthetic kickoff is registered.
|
|
62
|
+
const sessionClient = input.client?.session;
|
|
63
|
+
const promptAsync = sessionClient?.promptAsync;
|
|
64
|
+
setPlanExecutionKickoff(typeof promptAsync === "function"
|
|
65
|
+
? async ({ sessionID, planPath }) => {
|
|
66
|
+
await promptAsync.call(sessionClient, {
|
|
67
|
+
path: { id: sessionID },
|
|
68
|
+
body: {
|
|
69
|
+
agent: "build",
|
|
70
|
+
parts: [{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: createPlanExecutionKickoffText(planPath),
|
|
73
|
+
synthetic: true,
|
|
74
|
+
}],
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
: undefined);
|
|
74
79
|
// Last access token successfully resolved in this plugin instance. Config's
|
|
75
80
|
// loadModels can only read OpenCode's durable store (auth.json /
|
|
76
81
|
// OPENCODE_AUTH_CONTENT); auth.loader gets live credentials via getAuth().
|
|
@@ -217,6 +222,12 @@ export async function CursorPlugin(input) {
|
|
|
217
222
|
sessionActivity.recordActivity(event.properties.part.sessionID);
|
|
218
223
|
break;
|
|
219
224
|
}
|
|
225
|
+
await dispatchHostEventBridge({
|
|
226
|
+
event,
|
|
227
|
+
client: input.client,
|
|
228
|
+
directory: input.directory,
|
|
229
|
+
serverUrl: input.serverUrl,
|
|
230
|
+
});
|
|
220
231
|
},
|
|
221
232
|
async "tool.execute.before"(hookInput, output) {
|
|
222
233
|
if (hookInput.tool !== "bash")
|
package/dist/pricing-data.d.ts
CHANGED
|
@@ -55,6 +55,12 @@ export declare const CURSOR_MODEL_COSTS: {
|
|
|
55
55
|
readonly cache_read: 0.5;
|
|
56
56
|
readonly cache_write: 6.25;
|
|
57
57
|
};
|
|
58
|
+
readonly "claude-opus-5-5": {
|
|
59
|
+
readonly input: 4;
|
|
60
|
+
readonly output: 20;
|
|
61
|
+
readonly cache_read: 0.2;
|
|
62
|
+
readonly cache_write: 5;
|
|
63
|
+
};
|
|
58
64
|
readonly "claude-sonnet-4": {
|
|
59
65
|
readonly input: 3;
|
|
60
66
|
readonly output: 15;
|
|
@@ -241,6 +247,26 @@ export declare const CURSOR_MODEL_COSTS: {
|
|
|
241
247
|
readonly output: 12;
|
|
242
248
|
readonly cache_read: 1;
|
|
243
249
|
};
|
|
250
|
+
readonly "grok-4.7": {
|
|
251
|
+
readonly input: 2;
|
|
252
|
+
readonly output: 6;
|
|
253
|
+
readonly cache_read: 0.5;
|
|
254
|
+
readonly context_over_200k: {
|
|
255
|
+
readonly input: 4;
|
|
256
|
+
readonly output: 12;
|
|
257
|
+
readonly cache_read: 1;
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
readonly "grok-4.7-fast": {
|
|
261
|
+
readonly input: 4;
|
|
262
|
+
readonly output: 12;
|
|
263
|
+
readonly cache_read: 1;
|
|
264
|
+
readonly context_over_200k: {
|
|
265
|
+
readonly input: 6;
|
|
266
|
+
readonly output: 18;
|
|
267
|
+
readonly cache_read: 1.5;
|
|
268
|
+
};
|
|
269
|
+
};
|
|
244
270
|
readonly "kimi-k2.7-code": {
|
|
245
271
|
readonly input: 0.95;
|
|
246
272
|
readonly output: 4;
|
|
@@ -289,6 +315,10 @@ export declare const CURSOR_MODEL_CONTEXTS: {
|
|
|
289
315
|
readonly maxContext: 300000;
|
|
290
316
|
readonly maxContextForMaxMode: 1000000;
|
|
291
317
|
};
|
|
318
|
+
readonly "claude-opus-5-5": {
|
|
319
|
+
readonly maxContext: 300000;
|
|
320
|
+
readonly maxContextForMaxMode: 1000000;
|
|
321
|
+
};
|
|
292
322
|
readonly "claude-sonnet-4": {
|
|
293
323
|
readonly maxContext: 200000;
|
|
294
324
|
readonly maxContextForMaxMode: 1000000;
|
|
@@ -383,6 +413,10 @@ export declare const CURSOR_MODEL_CONTEXTS: {
|
|
|
383
413
|
readonly "grok-4.6": {
|
|
384
414
|
readonly maxContext: 256000;
|
|
385
415
|
};
|
|
416
|
+
readonly "grok-4.7": {
|
|
417
|
+
readonly maxContext: 256000;
|
|
418
|
+
readonly maxContextForMaxMode: 500000;
|
|
419
|
+
};
|
|
386
420
|
readonly "kimi-k2.7-code": {
|
|
387
421
|
readonly maxContext: 262000;
|
|
388
422
|
};
|
|
@@ -420,6 +454,9 @@ export declare const CURSOR_MODEL_CAPABILITIES: {
|
|
|
420
454
|
readonly "claude-opus-5": {
|
|
421
455
|
readonly supportsImages: true;
|
|
422
456
|
};
|
|
457
|
+
readonly "claude-opus-5-5": {
|
|
458
|
+
readonly supportsImages: true;
|
|
459
|
+
};
|
|
423
460
|
readonly "claude-sonnet-4": {
|
|
424
461
|
readonly supportsImages: true;
|
|
425
462
|
};
|
|
@@ -498,6 +535,9 @@ export declare const CURSOR_MODEL_CAPABILITIES: {
|
|
|
498
535
|
readonly "grok-4.6": {
|
|
499
536
|
readonly supportsImages: false;
|
|
500
537
|
};
|
|
538
|
+
readonly "grok-4.7": {
|
|
539
|
+
readonly supportsImages: false;
|
|
540
|
+
};
|
|
501
541
|
readonly "kimi-k2.7-code": {
|
|
502
542
|
readonly supportsImages: true;
|
|
503
543
|
};
|
package/dist/pricing-data.js
CHANGED
|
@@ -55,6 +55,12 @@ export const CURSOR_MODEL_COSTS = {
|
|
|
55
55
|
"cache_read": 0.5,
|
|
56
56
|
"cache_write": 6.25
|
|
57
57
|
},
|
|
58
|
+
"claude-opus-5-5": {
|
|
59
|
+
"input": 4,
|
|
60
|
+
"output": 20,
|
|
61
|
+
"cache_read": 0.2,
|
|
62
|
+
"cache_write": 5
|
|
63
|
+
},
|
|
58
64
|
"claude-sonnet-4": {
|
|
59
65
|
"input": 3,
|
|
60
66
|
"output": 15,
|
|
@@ -241,6 +247,26 @@ export const CURSOR_MODEL_COSTS = {
|
|
|
241
247
|
"output": 12,
|
|
242
248
|
"cache_read": 1
|
|
243
249
|
},
|
|
250
|
+
"grok-4.7": {
|
|
251
|
+
"input": 2,
|
|
252
|
+
"output": 6,
|
|
253
|
+
"cache_read": 0.5,
|
|
254
|
+
"context_over_200k": {
|
|
255
|
+
"input": 4,
|
|
256
|
+
"output": 12,
|
|
257
|
+
"cache_read": 1
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
"grok-4.7-fast": {
|
|
261
|
+
"input": 4,
|
|
262
|
+
"output": 12,
|
|
263
|
+
"cache_read": 1,
|
|
264
|
+
"context_over_200k": {
|
|
265
|
+
"input": 6,
|
|
266
|
+
"output": 18,
|
|
267
|
+
"cache_read": 1.5
|
|
268
|
+
}
|
|
269
|
+
},
|
|
244
270
|
"kimi-k2.7-code": {
|
|
245
271
|
"input": 0.95,
|
|
246
272
|
"output": 4,
|
|
@@ -289,6 +315,10 @@ export const CURSOR_MODEL_CONTEXTS = {
|
|
|
289
315
|
"maxContext": 300000,
|
|
290
316
|
"maxContextForMaxMode": 1000000
|
|
291
317
|
},
|
|
318
|
+
"claude-opus-5-5": {
|
|
319
|
+
"maxContext": 300000,
|
|
320
|
+
"maxContextForMaxMode": 1000000
|
|
321
|
+
},
|
|
292
322
|
"claude-sonnet-4": {
|
|
293
323
|
"maxContext": 200000,
|
|
294
324
|
"maxContextForMaxMode": 1000000
|
|
@@ -383,6 +413,10 @@ export const CURSOR_MODEL_CONTEXTS = {
|
|
|
383
413
|
"grok-4.6": {
|
|
384
414
|
"maxContext": 256000
|
|
385
415
|
},
|
|
416
|
+
"grok-4.7": {
|
|
417
|
+
"maxContext": 256000,
|
|
418
|
+
"maxContextForMaxMode": 500000
|
|
419
|
+
},
|
|
386
420
|
"kimi-k2.7-code": {
|
|
387
421
|
"maxContext": 262000
|
|
388
422
|
},
|
|
@@ -420,6 +454,9 @@ export const CURSOR_MODEL_CAPABILITIES = {
|
|
|
420
454
|
"claude-opus-5": {
|
|
421
455
|
"supportsImages": true
|
|
422
456
|
},
|
|
457
|
+
"claude-opus-5-5": {
|
|
458
|
+
"supportsImages": true
|
|
459
|
+
},
|
|
423
460
|
"claude-sonnet-4": {
|
|
424
461
|
"supportsImages": true
|
|
425
462
|
},
|
|
@@ -498,6 +535,9 @@ export const CURSOR_MODEL_CAPABILITIES = {
|
|
|
498
535
|
"grok-4.6": {
|
|
499
536
|
"supportsImages": false
|
|
500
537
|
},
|
|
538
|
+
"grok-4.7": {
|
|
539
|
+
"supportsImages": false
|
|
540
|
+
},
|
|
501
541
|
"kimi-k2.7-code": {
|
|
502
542
|
"supportsImages": true
|
|
503
543
|
},
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* and fall back to prose instead of ever calling the advertised host tool.
|
|
10
10
|
*
|
|
11
11
|
* Every rule below mirrors Cursor CLI, decompiled at
|
|
12
|
-
*
|
|
12
|
+
* `<cursor-cli-checkout>/cursor/cli{,-local}`:
|
|
13
13
|
*
|
|
14
14
|
* - `src/utils/interaction-utils.ts` — `BU` (freeform option id), `Q7` (display
|
|
15
15
|
* options), `iX` (selection → success), `N$` (rejection + default reason).
|
|
@@ -98,19 +98,18 @@ export declare function rejectedResult(reason?: string): AskQuestionResultMessag
|
|
|
98
98
|
/** `AskQuestionResult{async}` — the immediate reply for `run_async` queries. */
|
|
99
99
|
export declare function asyncResult(): AskQuestionResultMessage;
|
|
100
100
|
/**
|
|
101
|
-
* Pull each question's answer text out of
|
|
101
|
+
* Pull each question's answer text out of a host `question` tool output.
|
|
102
102
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
103
|
+
* OpenCode returns prose (`metadata.answers` does not cross the AI SDK
|
|
104
|
+
* boundary):
|
|
105
105
|
*
|
|
106
106
|
* User has answered your questions: "<q1>"="<a, b>", "<q2>"="Unanswered". You
|
|
107
107
|
* can now continue with the user's answers in mind.
|
|
108
108
|
*
|
|
109
|
-
* The
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* unanswered rather than guessed at.
|
|
109
|
+
* The prose is located by its `"<question>"="` anchor — robust
|
|
110
|
+
* against commas, quotes and `"="` inside question or answer text, and against
|
|
111
|
+
* duplicate questions. Returns undefined for a question whose anchor is
|
|
112
|
+
* absent, which is treated as unanswered rather than guessed at.
|
|
114
113
|
*/
|
|
115
114
|
export declare function parseAnswerSegments(questions: readonly CursorAskQuestionItem[], output: string): Array<string | undefined>;
|
|
116
115
|
/**
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* and fall back to prose instead of ever calling the advertised host tool.
|
|
10
10
|
*
|
|
11
11
|
* Every rule below mirrors Cursor CLI, decompiled at
|
|
12
|
-
*
|
|
12
|
+
* `<cursor-cli-checkout>/cursor/cli{,-local}`:
|
|
13
13
|
*
|
|
14
14
|
* - `src/utils/interaction-utils.ts` — `BU` (freeform option id), `Q7` (display
|
|
15
15
|
* options), `iX` (selection → success), `N$` (rejection + default reason).
|
|
@@ -218,19 +218,18 @@ export function asyncResult() {
|
|
|
218
218
|
return { async: {} };
|
|
219
219
|
}
|
|
220
220
|
/**
|
|
221
|
-
* Pull each question's answer text out of
|
|
221
|
+
* Pull each question's answer text out of a host `question` tool output.
|
|
222
222
|
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
223
|
+
* OpenCode returns prose (`metadata.answers` does not cross the AI SDK
|
|
224
|
+
* boundary):
|
|
225
225
|
*
|
|
226
226
|
* User has answered your questions: "<q1>"="<a, b>", "<q2>"="Unanswered". You
|
|
227
227
|
* can now continue with the user's answers in mind.
|
|
228
228
|
*
|
|
229
|
-
* The
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
* unanswered rather than guessed at.
|
|
229
|
+
* The prose is located by its `"<question>"="` anchor — robust
|
|
230
|
+
* against commas, quotes and `"="` inside question or answer text, and against
|
|
231
|
+
* duplicate questions. Returns undefined for a question whose anchor is
|
|
232
|
+
* absent, which is treated as unanswered rather than guessed at.
|
|
234
233
|
*/
|
|
235
234
|
export function parseAnswerSegments(questions, output) {
|
|
236
235
|
const answers = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { clearCheckpoint } from "./checkpoint.js";
|
|
3
3
|
import { clearConversationBlobs } from "./blob-store.js";
|
|
4
|
-
import {
|
|
4
|
+
import { transferFrozenRequestContext, } from "../context/frozen.js";
|
|
5
5
|
/**
|
|
6
6
|
* OpenCode session key → active Cursor conversation_id.
|
|
7
7
|
*
|
|
@@ -11,8 +11,15 @@ import { clearFrozenRequestContext, transferFrozenRequestContext, } from "../con
|
|
|
11
11
|
* cached conversation against OpenCode's newly compacted prompt.
|
|
12
12
|
*/
|
|
13
13
|
const activeBySession = new Map();
|
|
14
|
+
// A compaction id cannot be reconstructed from its host session key. Keep its
|
|
15
|
+
// tiny binding even when the recent-session index soft-evicts that session.
|
|
16
|
+
const remintedBySession = new Map();
|
|
14
17
|
export const MAX_ACTIVE_CONVERSATION_BINDINGS = 256;
|
|
15
18
|
function rememberConversation(sessionKey, conversationId) {
|
|
19
|
+
if (conversationId === sessionIdToUuid(sessionKey))
|
|
20
|
+
remintedBySession.delete(sessionKey);
|
|
21
|
+
else
|
|
22
|
+
remintedBySession.set(sessionKey, conversationId);
|
|
16
23
|
// Map insertion order is the LRU order. Refresh existing sessions on use.
|
|
17
24
|
activeBySession.delete(sessionKey);
|
|
18
25
|
activeBySession.set(sessionKey, conversationId);
|
|
@@ -21,19 +28,21 @@ function rememberConversation(sessionKey, conversationId) {
|
|
|
21
28
|
if (!oldest)
|
|
22
29
|
break;
|
|
23
30
|
activeBySession.delete(oldest[0]);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
// Evict only the lookup pointer. The checkpoint may still be needed by a
|
|
32
|
+
// held Run, and a returning session can hydrate its durable binding before
|
|
33
|
+
// resolving another turn. Compaction/reset owns deletion of opaque state.
|
|
27
34
|
}
|
|
28
35
|
}
|
|
29
36
|
export function resetConversationBindingsForTests() {
|
|
30
37
|
activeBySession.clear();
|
|
38
|
+
remintedBySession.clear();
|
|
31
39
|
}
|
|
32
40
|
export function hasConversationBinding(sessionKey) {
|
|
33
|
-
return activeBySession.has(sessionKey);
|
|
41
|
+
return activeBySession.has(sessionKey) || remintedBySession.has(sessionKey);
|
|
34
42
|
}
|
|
35
43
|
export function isActiveConversationBinding(sessionKey, conversationId) {
|
|
36
|
-
return activeBySession.get(sessionKey)
|
|
44
|
+
return (activeBySession.get(sessionKey) ?? remintedBySession.get(sessionKey)
|
|
45
|
+
?? sessionIdToUuid(sessionKey)) === conversationId;
|
|
37
46
|
}
|
|
38
47
|
/** Restore a validated durable binding before resolving the next Run. */
|
|
39
48
|
export function restoreConversationBinding(sessionKey, conversationId) {
|
|
@@ -63,7 +72,7 @@ export function resolveConversationGroupId(sessionKey, conversationId) {
|
|
|
63
72
|
}
|
|
64
73
|
/** Current Cursor conversation_id for an OpenCode session key, creating the default binding if needed. */
|
|
65
74
|
export function peekConversationId(sessionKey) {
|
|
66
|
-
let id = activeBySession.get(sessionKey);
|
|
75
|
+
let id = activeBySession.get(sessionKey) ?? remintedBySession.get(sessionKey);
|
|
67
76
|
if (!id) {
|
|
68
77
|
id = sessionIdToUuid(sessionKey);
|
|
69
78
|
}
|
|
@@ -83,7 +92,8 @@ export function bindConversationId(sessionKey, opts) {
|
|
|
83
92
|
return { conversationId: crypto.randomUUID(), reset: !!opts?.reset };
|
|
84
93
|
}
|
|
85
94
|
if (opts?.reset) {
|
|
86
|
-
const previousId = activeBySession.get(sessionKey) ??
|
|
95
|
+
const previousId = activeBySession.get(sessionKey) ?? remintedBySession.get(sessionKey)
|
|
96
|
+
?? sessionIdToUuid(sessionKey);
|
|
87
97
|
const conversationId = crypto.randomUUID();
|
|
88
98
|
clearCheckpoint(previousId);
|
|
89
99
|
clearConversationBlobs(previousId);
|
|
@@ -54,9 +54,10 @@ export type CursorPlanStageInput = {
|
|
|
54
54
|
* How a CreatePlan query is satisfied this turn. Keyed only on the advertised
|
|
55
55
|
* catalog and the provider's own plan-mode state — never on host identity.
|
|
56
56
|
*
|
|
57
|
-
* - `stage` a host plan-stage tool is advertised; the host writes the plan
|
|
58
|
-
*
|
|
59
|
-
*
|
|
57
|
+
* - `stage` a host plan-stage tool is advertised; the host writes the plan.
|
|
58
|
+
* Its tool result is the approval outcome (success = approved,
|
|
59
|
+
* error = not approved). The result may name the host's own
|
|
60
|
+
* follow-up approval call. This provider does not invent that prompt.
|
|
60
61
|
* - `approve` no such tool, but the host advertises `question`: the provider
|
|
61
62
|
* writes the plan itself and then asks the same thing with upstream
|
|
62
63
|
* `PlanExitTool`'s own prompt.
|
|
@@ -95,10 +96,8 @@ export declare function createPlanApprovalQuestionInput(planLabel: string): Open
|
|
|
95
96
|
/**
|
|
96
97
|
* True when the emulated approval prompt came back as an explicit "Yes".
|
|
97
98
|
* An unanswered, dismissed, or failed prompt keeps the model planning rather
|
|
98
|
-
* than silently starting execution.
|
|
99
|
-
*
|
|
100
|
-
* `question` must be the exact prompt that was asked — the host echoes it back
|
|
101
|
-
* verbatim, and it is the anchor the answer is parsed from.
|
|
99
|
+
* than silently starting execution. OpenCode prose is anchored on the exact
|
|
100
|
+
* prompt text. Every other result shape fails closed.
|
|
102
101
|
*/
|
|
103
102
|
export declare function createPlanApproved(output: string, isError: boolean, question: string): boolean;
|
|
104
103
|
/** Decode a `create_plan_request_query` body, or undefined when unusable. */
|
|
@@ -156,16 +156,15 @@ export function createPlanApprovalQuestionInput(planLabel) {
|
|
|
156
156
|
/**
|
|
157
157
|
* True when the emulated approval prompt came back as an explicit "Yes".
|
|
158
158
|
* An unanswered, dismissed, or failed prompt keeps the model planning rather
|
|
159
|
-
* than silently starting execution.
|
|
160
|
-
*
|
|
161
|
-
* `question` must be the exact prompt that was asked — the host echoes it back
|
|
162
|
-
* verbatim, and it is the anchor the answer is parsed from.
|
|
159
|
+
* than silently starting execution. OpenCode prose is anchored on the exact
|
|
160
|
+
* prompt text. Every other result shape fails closed.
|
|
163
161
|
*/
|
|
164
162
|
export function createPlanApproved(output, isError, question) {
|
|
165
163
|
if (isError)
|
|
166
164
|
return false;
|
|
167
165
|
const [segment] = parseAnswerSegments([createPlanApprovalItem(question)], output);
|
|
168
|
-
|
|
166
|
+
const answer = (segment ?? "").trim().toLowerCase();
|
|
167
|
+
return answer === CREATE_PLAN_APPROVAL_YES.toLowerCase();
|
|
169
168
|
}
|
|
170
169
|
function asRecord(value) {
|
|
171
170
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
@@ -287,7 +287,22 @@ export function createMessageTypes() {
|
|
|
287
287
|
{ id: 3, name: "pattern", type: "string" },
|
|
288
288
|
{ id: 4, name: "tool_call_id", type: "string" },
|
|
289
289
|
]);
|
|
290
|
-
|
|
290
|
+
// agent.v1 GetMcpToolsToolCall is args + result. Cursor's agent fills result
|
|
291
|
+
// locally (GetDynamicTools / GetMcpTools); large catalogs spill through
|
|
292
|
+
// write_args and put the spill path on GetMcpToolsSuccess.output_file_path.
|
|
293
|
+
addType(root, "GetMcpToolsSuccess", [
|
|
294
|
+
{ id: 1, name: "content", type: "string" },
|
|
295
|
+
{ id: 2, name: "output_file_path", type: "string" },
|
|
296
|
+
]);
|
|
297
|
+
addType(root, "GetMcpToolsError", [{ id: 1, name: "error", type: "string" }]);
|
|
298
|
+
addType(root, "GetMcpToolsAgentResult", [
|
|
299
|
+
{ id: 1, name: "success", type: "GetMcpToolsSuccess" },
|
|
300
|
+
{ id: 2, name: "error", type: "GetMcpToolsError" },
|
|
301
|
+
], [{ name: "result", fields: ["success", "error"] }]);
|
|
302
|
+
addType(root, "GetMcpToolsToolCall", [
|
|
303
|
+
{ id: 1, name: "args", type: "GetMcpToolsArgs" },
|
|
304
|
+
{ id: 2, name: "result", type: "GetMcpToolsAgentResult" },
|
|
305
|
+
]);
|
|
291
306
|
addType(root, "GenerateImageToolArgs", [
|
|
292
307
|
{ id: 1, name: "description", type: "string" },
|
|
293
308
|
{ id: 2, name: "file_path", type: "string" },
|
|
@@ -472,6 +487,8 @@ export function createMessageTypes() {
|
|
|
472
487
|
{ id: 1, name: "matches", type: "GrepFileMatch", repeated: true },
|
|
473
488
|
{ id: 2, name: "total_lines", type: "int32" },
|
|
474
489
|
{ id: 3, name: "total_matched_lines", type: "int32" },
|
|
490
|
+
{ id: 4, name: "client_truncated", type: "bool" },
|
|
491
|
+
{ id: 5, name: "ripgrep_truncated", type: "bool" },
|
|
475
492
|
]);
|
|
476
493
|
addType(root, "GrepUnionResult", [
|
|
477
494
|
{ id: 2, name: "files", type: "GrepFilesResult" },
|
package/dist/protocol/struct.js
CHANGED
|
@@ -249,7 +249,7 @@ export function encodeJsonAsValue(v) {
|
|
|
249
249
|
}
|
|
250
250
|
else if (typeof v === "object") {
|
|
251
251
|
const st = [];
|
|
252
|
-
for (const [key, val] of Object.entries(v).sort(([left], [right]) => left
|
|
252
|
+
for (const [key, val] of Object.entries(v).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) {
|
|
253
253
|
if (val === undefined)
|
|
254
254
|
continue;
|
|
255
255
|
const entry = [];
|
|
@@ -30,10 +30,8 @@
|
|
|
30
30
|
* - leaving plan mode is the gate the user must actually see, so it falls back
|
|
31
31
|
* to the host `question` tool and only rejects when that is absent too.
|
|
32
32
|
*
|
|
33
|
-
* Resolution is keyed solely on the advertised catalog under canonical
|
|
34
|
-
* names — never on
|
|
35
|
-
* alternate host vocabulary under canonical names before the provider sees the
|
|
36
|
-
* catalog, so the bridge itself remains OpenCode-shaped.
|
|
33
|
+
* Resolution is keyed solely on the advertised catalog under canonical
|
|
34
|
+
* OpenCode tool names — never on any external identity or model id.
|
|
37
35
|
*
|
|
38
36
|
* CLI reject reason for user declines: "Mode switch rejected by user"
|
|
39
37
|
* (chunk-7076/dist/ui.js onSwitchModeReject).
|
|
@@ -169,6 +167,7 @@ export declare function resetActiveCursorModesForTests(): void;
|
|
|
169
167
|
export declare function cursorModeSystemReminder(targetModeId: string, options?: {
|
|
170
168
|
firstTurn?: boolean;
|
|
171
169
|
planExitAdvertised?: boolean;
|
|
170
|
+
planStageAdvertised?: boolean;
|
|
172
171
|
}): string | undefined;
|
|
173
172
|
/**
|
|
174
173
|
* Consume the active-mode reminder for this OpenCode session.
|
|
@@ -30,10 +30,8 @@
|
|
|
30
30
|
* - leaving plan mode is the gate the user must actually see, so it falls back
|
|
31
31
|
* to the host `question` tool and only rejects when that is absent too.
|
|
32
32
|
*
|
|
33
|
-
* Resolution is keyed solely on the advertised catalog under canonical
|
|
34
|
-
* names — never on
|
|
35
|
-
* alternate host vocabulary under canonical names before the provider sees the
|
|
36
|
-
* catalog, so the bridge itself remains OpenCode-shaped.
|
|
33
|
+
* Resolution is keyed solely on the advertised catalog under canonical
|
|
34
|
+
* OpenCode tool names — never on any external identity or model id.
|
|
37
35
|
*
|
|
38
36
|
* CLI reject reason for user declines: "Mode switch rejected by user"
|
|
39
37
|
* (chunk-7076/dist/ui.js onSwitchModeReject).
|
|
@@ -295,12 +293,15 @@ export function cursorModeSystemReminder(targetModeId, options = {}) {
|
|
|
295
293
|
if (!id)
|
|
296
294
|
return undefined;
|
|
297
295
|
const first = options.firstTurn !== false;
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
296
|
+
// A host plan-stage tool writes the artifact and its result names the
|
|
297
|
+
// follow-up that requests approval. plan_exit only leaves plan mode.
|
|
298
|
+
// Without either tool, recording the plan is the gate and the provider asks
|
|
299
|
+
// whether to start implementing. Naming an unavailable tool would strand the model.
|
|
300
|
+
const leavePlan = options.planStageAdvertised
|
|
301
|
+
? "record the finished plan with Cursor CreatePlan. The host stage tool waits for the host plan review and does not return until the user accepts or declines. Do not call `plan_exit` to submit or skip that review, and do not implement until the tool returns success"
|
|
302
|
+
: options.planExitAdvertised === false
|
|
303
|
+
? "record the finished plan (Cursor CreatePlan). Writing it needs no approval, and the user is then asked whether to start implementing; if they decline, refine the plan and record it again"
|
|
304
|
+
: "record the finished plan, then call OpenCode `plan_exit` so the user can approve leaving plan mode";
|
|
304
305
|
if (id === "plan" || id === "spec") {
|
|
305
306
|
return wrapReminder(first
|
|
306
307
|
? `Plan mode is active. The user does not want execution yet -- you MUST NOT make edits, run non-readonly tools (including changing configs or making commits), or otherwise modify system state. This supersedes any conflicting instruction.
|
|
@@ -389,9 +390,12 @@ Your mandate is to convert user intent into a correct, high-quality implementati
|
|
|
389
390
|
return wrapReminder(first
|
|
390
391
|
? `Agent mode is active (OpenCode build / agents). You have left plan mode and may implement: edit files, run tools, and execute the agreed approach.
|
|
391
392
|
|
|
393
|
+
The plan-mode restriction is over. An earlier reminder that forbade edits no longer applies.
|
|
394
|
+
|
|
395
|
+
- Change file contents with the advertised \`write\` and \`edit\` tools.
|
|
392
396
|
- Prefer making progress with the advertised host tools.
|
|
393
397
|
- If the task is large or ambiguous again, switch back to plan mode (OpenCode \`plan_enter\` / SwitchMode target plan) before a large rewrite.`
|
|
394
|
-
: `Agent mode is still active. Continue implementing with the advertised host tools
|
|
398
|
+
: `Agent mode is still active. Continue implementing with the advertised host tools, and change file contents with \`write\` and \`edit\`. Switch back to plan mode only when a new large/ambiguous design decision appears.`);
|
|
395
399
|
}
|
|
396
400
|
/**
|
|
397
401
|
* Consume the active-mode reminder for this OpenCode session.
|
|
@@ -417,7 +421,12 @@ export function takeActiveCursorModeReminder(sessionKey, options = {}) {
|
|
|
417
421
|
}
|
|
418
422
|
const reminder = cursorModeSystemReminder(state.modeId, {
|
|
419
423
|
firstTurn: state.firstTurn,
|
|
420
|
-
...(advertised
|
|
424
|
+
...(advertised
|
|
425
|
+
? {
|
|
426
|
+
planExitAdvertised: advertised.has("plan_exit"),
|
|
427
|
+
planStageAdvertised: advertised.has("cursor_plan_stage"),
|
|
428
|
+
}
|
|
429
|
+
: {}),
|
|
421
430
|
});
|
|
422
431
|
if (state.firstTurn)
|
|
423
432
|
state.firstTurn = false;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type HostToolDialect } from "./tools.js";
|
|
1
2
|
/**
|
|
2
3
|
* Cursor's interaction_update.tool_call_* carries a typed ToolCall oneof
|
|
3
4
|
* (shell_tool_call, update_todos_tool_call, mcp_tool_call, …). Some of those
|
|
@@ -19,6 +20,8 @@ export type BridgedOpenCodeToolCall = {
|
|
|
19
20
|
callId: string;
|
|
20
21
|
variant: string;
|
|
21
22
|
};
|
|
23
|
+
/** Display variants Cursor executes itself (mcp_state + optional write spill). */
|
|
24
|
+
export declare function isNativeDisplayToolCall(variant: string): boolean;
|
|
22
25
|
/**
|
|
23
26
|
* Normalize a full todo snapshot for `mirroredTodos` storage. Returns
|
|
24
27
|
* undefined when the input is not a todo array (nothing to learn). Drops the
|
|
@@ -55,7 +58,7 @@ export declare function parseDisplayToolCall(callId: string, toolCall: Record<st
|
|
|
55
58
|
* Pick an advertised OpenCode tool for a display call, remapping args as needed.
|
|
56
59
|
* Returns undefined when nothing compatible is advertised this turn.
|
|
57
60
|
*/
|
|
58
|
-
export declare function resolveBridgedOpenCodeToolCall(display: DisplayToolCall, advertised: Iterable<string
|
|
61
|
+
export declare function resolveBridgedOpenCodeToolCall(display: DisplayToolCall, advertised: Iterable<string>, dialect?: HostToolDialect): BridgedOpenCodeToolCall | undefined;
|
|
59
62
|
/** Advertised OpenCode tool ids from Cursor McpToolDefinition descriptors. */
|
|
60
63
|
export declare function advertisedToolNamesFromDescriptors(descriptors: Array<Record<string, unknown>>): string[];
|
|
61
64
|
/** Walk a protobuf message and return top-level field numbers present. */
|