cursor-opencode-provider 0.8.1 → 0.8.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/CHANGELOG.md +95 -0
- package/README.md +204 -37
- package/SECURITY.md +20 -8
- package/dist/activity.d.ts +73 -1
- package/dist/activity.js +231 -5
- package/dist/agent-url.js +37 -9
- package/dist/auth-renewal.d.ts +1 -1
- package/dist/auth-renewal.js +58 -26
- package/dist/billing.d.ts +105 -0
- package/dist/billing.js +212 -0
- package/dist/classic-tool-results.d.ts +1 -0
- package/dist/classic-tool-results.js +42 -0
- package/dist/context/build.d.ts +1 -0
- package/dist/context/build.js +32 -5
- package/dist/context/env.js +27 -3
- package/dist/context/epoch.js +9 -5
- package/dist/context/git.d.ts +5 -2
- package/dist/context/git.js +17 -7
- package/dist/context/paths.d.ts +1 -1
- package/dist/context/paths.js +7 -4
- package/dist/debug.js +13 -2
- package/dist/early-tool-rows.d.ts +60 -0
- package/dist/early-tool-rows.js +96 -0
- package/dist/errors.d.ts +49 -7
- package/dist/errors.js +163 -8
- package/dist/host-interrupt.d.ts +12 -0
- package/dist/host-interrupt.js +18 -0
- package/dist/host-steer.d.ts +71 -0
- package/dist/host-steer.js +232 -0
- package/dist/image-input.js +4 -3
- package/dist/index.d.ts +2 -2
- package/dist/language-model.d.ts +114 -23
- package/dist/language-model.js +2290 -469
- package/dist/lost-request.d.ts +39 -0
- package/dist/lost-request.js +111 -0
- package/dist/model-config.d.ts +3 -1
- package/dist/model-config.js +50 -29
- package/dist/models.d.ts +26 -3
- package/dist/models.js +101 -45
- package/dist/opencode2/tool-results.d.ts +18 -0
- package/dist/opencode2/tool-results.js +66 -0
- package/dist/plugin-opencode2.js +185 -25
- package/dist/plugin.js +28 -5
- package/dist/pricing-data.d.ts +26 -2
- package/dist/pricing-data.js +26 -2
- package/dist/pricing.d.ts +1 -1
- package/dist/pricing.js +7 -4
- package/dist/process-shared.d.ts +14 -0
- package/dist/process-shared.js +18 -0
- package/dist/protocol/apply-patch.d.ts +25 -13
- package/dist/protocol/apply-patch.js +79 -30
- package/dist/protocol/blob-store.d.ts +5 -1
- package/dist/protocol/blob-store.js +10 -6
- package/dist/protocol/client-version.js +6 -4
- package/dist/protocol/conversation-cache-header.d.ts +23 -0
- package/dist/protocol/conversation-cache-header.js +149 -0
- package/dist/protocol/conversation-persistence.d.ts +27 -1
- package/dist/protocol/conversation-persistence.js +628 -126
- package/dist/protocol/conversation-state.d.ts +8 -1
- package/dist/protocol/conversation-state.js +28 -7
- package/dist/protocol/host-notes.d.ts +31 -0
- package/dist/protocol/host-notes.js +85 -0
- package/dist/protocol/messages.js +91 -8
- package/dist/protocol/request.d.ts +35 -19
- package/dist/protocol/request.js +53 -28
- package/dist/protocol/sent-history-images.d.ts +49 -0
- package/dist/protocol/sent-history-images.js +102 -0
- package/dist/protocol/tool-call-bridge.d.ts +2 -0
- package/dist/protocol/tool-call-bridge.js +10 -0
- package/dist/protocol/tools.d.ts +74 -8
- package/dist/protocol/tools.js +431 -141
- package/dist/protocol/turn-provenance.d.ts +19 -4
- package/dist/protocol/turn-provenance.js +77 -20
- package/dist/replay-safety.d.ts +16 -4
- package/dist/replay-safety.js +34 -25
- package/dist/retry-budget.d.ts +21 -0
- package/dist/retry-budget.js +94 -0
- package/dist/runtime-files.d.ts +23 -0
- package/dist/runtime-files.js +581 -0
- package/dist/session.d.ts +94 -9
- package/dist/session.js +153 -34
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/shell-timeout.d.ts +54 -10
- package/dist/shell-timeout.js +362 -92
- package/dist/stream-write.d.ts +20 -0
- package/dist/stream-write.js +87 -0
- package/dist/terminal-runner.d.ts +15 -0
- package/dist/terminal-runner.js +528 -0
- package/dist/transport/connect.d.ts +8 -1
- package/dist/transport/connect.js +103 -23
- package/dist/usage.d.ts +37 -17
- package/dist/usage.js +63 -29
- package/package.json +3 -2
- package/dist/protocol/thinking.d.ts +0 -15
- package/dist/protocol/thinking.js +0 -17
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export const TOOL_RESULT_STATUS_KEY = "opencodeToolResult";
|
|
2
|
+
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value)
|
|
3
|
+
? value : undefined;
|
|
4
|
+
export function nativeToolFailure(value, refusal) {
|
|
5
|
+
const envelope = record(value);
|
|
6
|
+
const error = record(envelope?.error);
|
|
7
|
+
const type = typeof error?.type === "string" && error.type ? error.type : "tool.execution";
|
|
8
|
+
const message = typeof error?.message === "string" && error.message
|
|
9
|
+
? error.message : typeof value === "string" && value ? value : `OpenCode tool failed (${type})`;
|
|
10
|
+
const content = Array.isArray(envelope?.content) ? envelope.content.flatMap(item => {
|
|
11
|
+
const part = record(item);
|
|
12
|
+
return part?.type === "text" && typeof part.text === "string" && part.text ? [part.text] : [];
|
|
13
|
+
}).join("\n") : "";
|
|
14
|
+
return { type, text: content ? `${message}\n\n${content}` : message,
|
|
15
|
+
...(refusal ? { refusal } : type === "permission.rejected" ? { refusal: "rejected" } : {}) };
|
|
16
|
+
}
|
|
17
|
+
/** The native tool hook preserves the underlying typed permission cause. */
|
|
18
|
+
export function nativeToolRefusal(error) {
|
|
19
|
+
const seen = new Set();
|
|
20
|
+
for (let depth = 0; depth < 16 && !seen.has(error); depth++) {
|
|
21
|
+
seen.add(error);
|
|
22
|
+
const cause = record(error);
|
|
23
|
+
if (!cause)
|
|
24
|
+
return undefined;
|
|
25
|
+
if (cause._tag === "Permission.BlockedError")
|
|
26
|
+
return "permission_denied";
|
|
27
|
+
if (cause._tag === "Permission.CorrectedError" || cause._tag === "Permission.DeclinedError")
|
|
28
|
+
return "rejected";
|
|
29
|
+
error = cause.error;
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
/** Metadata travels with each part, including history and independently loaded SDK copies. */
|
|
34
|
+
export function preserveNativeToolResults(messages, refusals = new Map()) {
|
|
35
|
+
if (!Array.isArray(messages))
|
|
36
|
+
return [];
|
|
37
|
+
return messages.map(message => {
|
|
38
|
+
const msg = record(message);
|
|
39
|
+
if (msg?.role !== "tool" || !Array.isArray(msg.content))
|
|
40
|
+
return message;
|
|
41
|
+
return { ...msg, content: msg.content.map(item => {
|
|
42
|
+
const part = record(item);
|
|
43
|
+
const result = record(part?.result);
|
|
44
|
+
if (part?.type !== "tool-result" || typeof part.id !== "string" || !result
|
|
45
|
+
|| typeof result.type !== "string" || !["error", "text", "json", "content"].includes(result.type))
|
|
46
|
+
return item;
|
|
47
|
+
const metadata = record(part.providerMetadata);
|
|
48
|
+
const cursor = record(metadata?.cursor);
|
|
49
|
+
const status = result.type === "error"
|
|
50
|
+
? { callID: part.id, status: "error", failure: nativeToolFailure(result.value, refusals.get(part.id)) }
|
|
51
|
+
: { callID: part.id, status: "completed" };
|
|
52
|
+
return { ...part, providerMetadata: { ...metadata, cursor: { ...cursor, [TOOL_RESULT_STATUS_KEY]: status } } };
|
|
53
|
+
}) };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export function toolResultFailure(options, callID) {
|
|
57
|
+
const status = record(record(record(options)?.cursor)?.[TOOL_RESULT_STATUS_KEY]);
|
|
58
|
+
if (status?.callID !== callID || status.status !== "error")
|
|
59
|
+
return undefined;
|
|
60
|
+
const failure = record(status.failure);
|
|
61
|
+
if (!failure || typeof failure.type !== "string" || typeof failure.text !== "string")
|
|
62
|
+
return undefined;
|
|
63
|
+
const refusal = failure.refusal === "permission_denied" || failure.refusal === "rejected" ? failure.refusal : undefined;
|
|
64
|
+
return { type: failure.type, text: failure.text || `OpenCode tool failed (${failure.type || "tool.execution"})`,
|
|
65
|
+
...(refusal ? { refusal } : {}) };
|
|
66
|
+
}
|
package/dist/plugin-opencode2.js
CHANGED
|
@@ -3,9 +3,11 @@ import { createSdk, cursorApiBaseURL, cursorGetServerConfigTelemetryEnabled, isC
|
|
|
3
3
|
import { opencode2PlanDir, opencodeGlobalCacheDir, setNativePlansDir } from "./context/paths.js";
|
|
4
4
|
import { discoverModels, isCacheFresh, readCache } from "./models.js";
|
|
5
5
|
import { resolveAgentUrl } from "./agent-url.js";
|
|
6
|
-
import { sessionActivity } from "./activity.js";
|
|
6
|
+
import { applyHostPromptEvent, applyHostToolEvent, sessionActivity } from "./activity.js";
|
|
7
|
+
import { notifyHostInterrupt } from "./host-interrupt.js";
|
|
8
|
+
import { announceHostSteer, forgetEarlySteerOfInbox, forgetEarlySteers, rememberQueuedSteer, takeQueuedSteer, } from "./host-steer.js";
|
|
7
9
|
import { fetchOpenCodeWebSearchText, parseExaWebSearchResults, } from "./web-tools.js";
|
|
8
|
-
import { captureCursorShellResult,
|
|
10
|
+
import { armCursorShellCreate, captureCursorShellResult, captureCursorShellFailure, prepareCursorShellCreate, prepareCursorShellArgs, releaseCursorShellEnv, sanitizeRegisteredCursorShellOutput, } from "./shell-timeout.js";
|
|
9
11
|
import { applyCursorProviderInventory, CURSOR_INTEGRATION_ID } from "./opencode2/catalog.js";
|
|
10
12
|
import { applyCursorIntegration, requireCursorAccessToken, resolveCursorAccessToken, } from "./opencode2/integration.js";
|
|
11
13
|
import { exposeDirectMcpTools, rememberDirectMcpNamespaces } from "./opencode2/mcp-direct.js";
|
|
@@ -20,6 +22,7 @@ import { trace } from "./debug.js";
|
|
|
20
22
|
import { cancelHostAgentModeSwitch, hostAgentSwitchPromptText, setHostAgentModeSwitch, } from "./host-agent-mode.js";
|
|
21
23
|
import { clearActiveCursorMode, getActiveCursorMode, normalizeSwitchModeId, setActiveCursorMode, } from "./protocol/switch-mode.js";
|
|
22
24
|
import { CursorPlugin } from "./plugin.js";
|
|
25
|
+
import { nativeToolRefusal, preserveNativeToolResults } from "./opencode2/tool-results.js";
|
|
23
26
|
/**
|
|
24
27
|
* OpenCode 2.0 plugin.
|
|
25
28
|
*
|
|
@@ -101,6 +104,60 @@ const plugin = {
|
|
|
101
104
|
let disposeHostAgentModeSwitch;
|
|
102
105
|
trace(`opencode2 plugin: setup directory=${workspaceRoot}`);
|
|
103
106
|
const hasShellEnvHook = typeof ctx.shell?.hook === "function";
|
|
107
|
+
const refusals = new Map();
|
|
108
|
+
const permissionCalls = new Map();
|
|
109
|
+
const rememberRefusal = (id, refusal) => {
|
|
110
|
+
refusals.delete(id);
|
|
111
|
+
refusals.set(id, refusal);
|
|
112
|
+
while (refusals.size > 512)
|
|
113
|
+
refusals.delete(refusals.keys().next().value);
|
|
114
|
+
};
|
|
115
|
+
const releasePermissionCalls = () => {
|
|
116
|
+
for (const call of permissionCalls.values())
|
|
117
|
+
releaseCursorShellEnv(call.callID);
|
|
118
|
+
permissionCalls.clear();
|
|
119
|
+
};
|
|
120
|
+
const toolLifecycle = (event) => {
|
|
121
|
+
const payload = eventPayload(event);
|
|
122
|
+
if (event?.type === "permission.asked" && payload?.source?.type === "tool"
|
|
123
|
+
&& typeof payload.source.id === "string" && payload.source.id.startsWith("cursor_")
|
|
124
|
+
&& typeof payload.id === "string" && typeof payload.sessionID === "string") {
|
|
125
|
+
permissionCalls.set(payload.id, { sessionID: payload.sessionID, callID: payload.source.id });
|
|
126
|
+
}
|
|
127
|
+
else if (event?.type === "permission.replied") {
|
|
128
|
+
const call = permissionCalls.get(payload?.requestID);
|
|
129
|
+
if (!call || call.sessionID !== payload?.sessionID)
|
|
130
|
+
return;
|
|
131
|
+
permissionCalls.delete(payload.requestID);
|
|
132
|
+
if (payload.reply === "reject") {
|
|
133
|
+
rememberRefusal(call.callID, "rejected");
|
|
134
|
+
releaseCursorShellEnv(call.callID);
|
|
135
|
+
// Native rejection also rejects the session's other pending requests.
|
|
136
|
+
for (const [id, pending] of permissionCalls)
|
|
137
|
+
if (pending.sessionID === call.sessionID) {
|
|
138
|
+
rememberRefusal(pending.callID, "rejected");
|
|
139
|
+
releaseCursorShellEnv(pending.callID);
|
|
140
|
+
permissionCalls.delete(id);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else if ((event?.type === "session.tool.failed" || event?.type === "session.tool.success")
|
|
145
|
+
&& typeof payload?.id === "string") {
|
|
146
|
+
if (event.type === "session.tool.failed")
|
|
147
|
+
releaseCursorShellEnv(payload.id);
|
|
148
|
+
for (const [id, call] of permissionCalls)
|
|
149
|
+
if (call.callID === payload.id && call.sessionID === payload.sessionID) {
|
|
150
|
+
permissionCalls.delete(id);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else if (event?.type === "session.deleted") {
|
|
154
|
+
for (const [id, call] of permissionCalls)
|
|
155
|
+
if (call.sessionID === payload?.sessionID) {
|
|
156
|
+
releaseCursorShellEnv(call.callID);
|
|
157
|
+
permissionCalls.delete(id);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
104
161
|
if (typeof ctx.session.switchAgent === "function") {
|
|
105
162
|
const switchAgent = ctx.session.switchAgent;
|
|
106
163
|
const continueTurn = typeof ctx.session.synthetic === "function"
|
|
@@ -192,15 +249,13 @@ const plugin = {
|
|
|
192
249
|
};
|
|
193
250
|
// ── AI SDK wiring ────────────────────────────────────────
|
|
194
251
|
await track(ctx.aisdk.hook("sdk", async (event) => {
|
|
195
|
-
if (event.sdk)
|
|
196
|
-
return;
|
|
197
252
|
if (!isCursorPackage(event.package, event.model.providerID))
|
|
198
253
|
return;
|
|
254
|
+
// The host's generic loader runs before plugin hooks and may already
|
|
255
|
+
// have created a Cursor SDK with a captured token. Own our package's
|
|
256
|
+
// SDK so current integration credentials are resolved on every Run.
|
|
199
257
|
event.sdk = createSdk({
|
|
200
258
|
name: event.model.providerID || CURSOR_PROVIDER_ID,
|
|
201
|
-
// Resolved per Run open: this SDK lives as long as the daemon, so
|
|
202
|
-
// a token captured here would outlive its expiry.
|
|
203
|
-
getAccessToken: (request) => requireCursorAccessToken(ctx.integration, request),
|
|
204
259
|
// Static fallback only. This hook fires once per model/package, not
|
|
205
260
|
// per session, and 2.0 runs one daemon across many projects — the
|
|
206
261
|
// real per-request directory comes from `x-opencode-directory`
|
|
@@ -208,6 +263,9 @@ const plugin = {
|
|
|
208
263
|
workspaceRoot,
|
|
209
264
|
cacheDir,
|
|
210
265
|
...event.options,
|
|
266
|
+
// Keep after host options: a captured resolver/token must not bypass
|
|
267
|
+
// the integration's current credential and renewal path.
|
|
268
|
+
getAccessToken: (request) => requireCursorAccessToken(ctx.integration, request),
|
|
211
269
|
// Keep after `event.options` so the OC2 plugin always selects the
|
|
212
270
|
// `path`/`shell` dialect when advertised schemas are opaque.
|
|
213
271
|
defaultDialect: OPENCODE_2_TOOL_DIALECT,
|
|
@@ -262,22 +320,35 @@ const plugin = {
|
|
|
262
320
|
exposeDirectMcpTools(draft, directMcpNamespaces);
|
|
263
321
|
}));
|
|
264
322
|
// ── Shell timeout wrapper ────────────────────────────────
|
|
265
|
-
await track(ctx.tool.hook("execute.before", (event) => {
|
|
266
|
-
if (!isShellTool(event.tool))
|
|
323
|
+
await track(ctx.tool.hook("execute.before", async (event) => {
|
|
324
|
+
if (!isShellTool(event.tool) || typeof event.input !== "object" || event.input === null)
|
|
267
325
|
return;
|
|
268
326
|
const executionID = toolExecutionID(event);
|
|
269
|
-
|
|
270
|
-
|
|
327
|
+
const input = event.input;
|
|
328
|
+
prepareCursorShellArgs(executionID, input, {
|
|
329
|
+
deferToShellHook: hasShellEnvHook,
|
|
271
330
|
preferWrapperCommand: !hasShellEnvHook,
|
|
272
331
|
});
|
|
332
|
+
if (hasShellEnvHook)
|
|
333
|
+
await armCursorShellCreate(executionID, event);
|
|
273
334
|
}));
|
|
274
335
|
await track(ctx.tool.hook("execute.after", (event) => {
|
|
336
|
+
const executionID = toolExecutionID(event);
|
|
337
|
+
if (event.status === "error") {
|
|
338
|
+
const refusal = nativeToolRefusal(event.error);
|
|
339
|
+
if (refusal)
|
|
340
|
+
rememberRefusal(executionID, refusal);
|
|
341
|
+
}
|
|
342
|
+
else
|
|
343
|
+
refusals.delete(executionID);
|
|
275
344
|
if (!isShellTool(event.tool))
|
|
276
345
|
return;
|
|
277
|
-
const executionID = toolExecutionID(event);
|
|
278
346
|
try {
|
|
279
|
-
if (event.status !== "completed")
|
|
347
|
+
if (event.status !== "completed") {
|
|
348
|
+
if (event.status === "error")
|
|
349
|
+
captureCursorShellFailure(executionID, event.error);
|
|
280
350
|
return;
|
|
351
|
+
}
|
|
281
352
|
const result = event.result;
|
|
282
353
|
// V1: `output` is the model-facing string. V2: `output` is structured
|
|
283
354
|
// per-tool output (shell: `{ output: string, ... }`) and the
|
|
@@ -324,10 +395,7 @@ const plugin = {
|
|
|
324
395
|
}));
|
|
325
396
|
if (ctx.shell) {
|
|
326
397
|
await track(ctx.shell.hook("create.before", (event) => {
|
|
327
|
-
|
|
328
|
-
if (!env)
|
|
329
|
-
return;
|
|
330
|
-
event.env = { ...event.env, ...env };
|
|
398
|
+
prepareCursorShellCreate(event);
|
|
331
399
|
}));
|
|
332
400
|
}
|
|
333
401
|
const rememberSessionDirectory = async (sessionID) => {
|
|
@@ -341,6 +409,7 @@ const plugin = {
|
|
|
341
409
|
}
|
|
342
410
|
};
|
|
343
411
|
await track(ctx.session.hook("context", async (event) => {
|
|
412
|
+
event.messages = preserveNativeToolResults(event.messages, refusals);
|
|
344
413
|
markCompactionAndOptions(event, event.agent === "compaction");
|
|
345
414
|
event.options ??= {};
|
|
346
415
|
event.options[CURSOR_HOST_AGENT_OPTION] = event.agent;
|
|
@@ -401,14 +470,17 @@ const plugin = {
|
|
|
401
470
|
};
|
|
402
471
|
}, { providerID: CURSOR_PROVIDER_ID }));
|
|
403
472
|
await track(ctx.session.hook("compaction", async (event) => {
|
|
473
|
+
event.messages = preserveNativeToolResults(event.messages, refusals);
|
|
404
474
|
markCompactionAndOptions(event, true);
|
|
405
475
|
await rememberSessionDirectory(event.sessionID);
|
|
406
476
|
}));
|
|
407
477
|
await track(ctx.session.hook("generate", async (event) => {
|
|
478
|
+
event.messages = preserveNativeToolResults(event.messages, refusals);
|
|
408
479
|
markCompactionAndOptions(event, false);
|
|
409
480
|
await rememberSessionDirectory(event.sessionID);
|
|
410
481
|
}));
|
|
411
482
|
await track(ctx.session.hook("title", async (event) => {
|
|
483
|
+
event.messages = preserveNativeToolResults(event.messages, refusals);
|
|
412
484
|
markCompactionAndOptions(event, false);
|
|
413
485
|
await rememberSessionDirectory(event.sessionID);
|
|
414
486
|
}));
|
|
@@ -473,10 +545,13 @@ const plugin = {
|
|
|
473
545
|
modelsLoaded = false;
|
|
474
546
|
credentialGeneration++;
|
|
475
547
|
};
|
|
476
|
-
const unsubscribe = subscribeSessionActivity(ctx, ensureModels, onCredentialSwitch);
|
|
548
|
+
const unsubscribe = subscribeSessionActivity(ctx, ensureModels, onCredentialSwitch, toolLifecycle, releasePermissionCalls);
|
|
477
549
|
return async () => {
|
|
478
550
|
clearInterval(retry);
|
|
479
551
|
unsubscribe?.();
|
|
552
|
+
releasePermissionCalls();
|
|
553
|
+
refusals.clear();
|
|
554
|
+
await saveOpenTurnsForShutdown();
|
|
480
555
|
disposeHostAgentModeSwitch?.();
|
|
481
556
|
disposeNativePlansDir();
|
|
482
557
|
trace(`opencode2 plugin: cleanup directory=${workspaceRoot}`);
|
|
@@ -486,22 +561,45 @@ const plugin = {
|
|
|
486
561
|
};
|
|
487
562
|
},
|
|
488
563
|
};
|
|
489
|
-
|
|
564
|
+
const SHUTDOWN_SAVE_TIMEOUT_MS = 2_000;
|
|
565
|
+
/** Save every open turn for the next server start, without holding shutdown up for long. */
|
|
566
|
+
async function saveOpenTurnsForShutdown() {
|
|
567
|
+
let timer;
|
|
568
|
+
const timedOut = new Promise((resolve) => {
|
|
569
|
+
timer = setTimeout(resolve, SHUTDOWN_SAVE_TIMEOUT_MS);
|
|
570
|
+
timer.unref?.();
|
|
571
|
+
});
|
|
572
|
+
try {
|
|
573
|
+
await Promise.race([notifyHostInterrupt(undefined, "shutdown"), timedOut]);
|
|
574
|
+
}
|
|
575
|
+
finally {
|
|
576
|
+
clearTimeout(timer);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function subscribeSessionActivity(ctx, onEvent, onCredentialSwitch, onToolEvent, onStreamClosed) {
|
|
490
580
|
try {
|
|
491
|
-
const
|
|
581
|
+
const controller = new AbortController();
|
|
582
|
+
const stream = ctx.event.subscribe({ signal: controller.signal });
|
|
492
583
|
if (!stream || typeof stream[Symbol.asyncIterator] !== "function")
|
|
493
584
|
return undefined;
|
|
494
585
|
let stopped = false;
|
|
495
586
|
void (async () => {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
587
|
+
try {
|
|
588
|
+
for await (const event of stream) {
|
|
589
|
+
if (stopped)
|
|
590
|
+
break;
|
|
591
|
+
applySessionActivity(event, onCredentialSwitch);
|
|
592
|
+
onToolEvent?.(event);
|
|
593
|
+
onEvent?.();
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
finally {
|
|
597
|
+
onStreamClosed?.();
|
|
501
598
|
}
|
|
502
599
|
})().catch(() => { });
|
|
503
600
|
return () => {
|
|
504
601
|
stopped = true;
|
|
602
|
+
controller.abort();
|
|
505
603
|
};
|
|
506
604
|
}
|
|
507
605
|
catch {
|
|
@@ -511,6 +609,8 @@ function subscribeSessionActivity(ctx, onEvent, onCredentialSwitch) {
|
|
|
511
609
|
function applySessionActivity(event, onCredentialSwitch) {
|
|
512
610
|
const payload = eventPayload(event);
|
|
513
611
|
const info = payload?.info;
|
|
612
|
+
applyHostPromptEvent(sessionActivity, event?.type, payload);
|
|
613
|
+
applyHostToolEvent(sessionActivity, event?.type, payload);
|
|
514
614
|
switch (event?.type) {
|
|
515
615
|
case "credential.switched":
|
|
516
616
|
case "credential.updated": {
|
|
@@ -537,6 +637,7 @@ function applySessionActivity(event, onCredentialSwitch) {
|
|
|
537
637
|
if (id) {
|
|
538
638
|
sessionActivity.removeSession(id);
|
|
539
639
|
clearSessionTodos(id);
|
|
640
|
+
forgetEarlySteers(id);
|
|
540
641
|
clearActiveCursorMode(id);
|
|
541
642
|
cancelHostAgentModeSwitch(id);
|
|
542
643
|
}
|
|
@@ -561,6 +662,42 @@ function applySessionActivity(event, onCredentialSwitch) {
|
|
|
561
662
|
sessionActivity.recordActivity(id);
|
|
562
663
|
break;
|
|
563
664
|
}
|
|
665
|
+
case "session.inbox.enqueued": {
|
|
666
|
+
const id = payload?.sessionID;
|
|
667
|
+
if (id)
|
|
668
|
+
sessionActivity.recordActivity(id);
|
|
669
|
+
const steer = inboxSteer(id, payload?.inboxID, payload?.item);
|
|
670
|
+
if (!steer)
|
|
671
|
+
break;
|
|
672
|
+
if (payload.item.delivery === "steer")
|
|
673
|
+
announceInboxSteer(steer);
|
|
674
|
+
else
|
|
675
|
+
rememberQueuedSteer(steer);
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
case "session.inbox.delivery.changed": {
|
|
679
|
+
const id = payload?.sessionID;
|
|
680
|
+
if (id)
|
|
681
|
+
sessionActivity.recordActivity(id);
|
|
682
|
+
if (payload?.delivery !== "steer" || typeof payload?.inboxID !== "string")
|
|
683
|
+
break;
|
|
684
|
+
const steer = takeQueuedSteer(payload.inboxID);
|
|
685
|
+
if (steer)
|
|
686
|
+
announceInboxSteer(steer);
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
case "session.inbox.delivered":
|
|
690
|
+
case "session.inbox.cancelled": {
|
|
691
|
+
const id = payload?.sessionID;
|
|
692
|
+
if (id)
|
|
693
|
+
sessionActivity.recordActivity(id);
|
|
694
|
+
if (typeof payload?.inboxID !== "string")
|
|
695
|
+
break;
|
|
696
|
+
takeQueuedSteer(payload.inboxID);
|
|
697
|
+
if (event.type === "session.inbox.cancelled" && typeof id === "string")
|
|
698
|
+
forgetEarlySteerOfInbox(id, payload.inboxID);
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
564
701
|
default: {
|
|
565
702
|
// OpenCode 2.0 emits granular `session.*` progress events instead of
|
|
566
703
|
// the V1 `message.updated` family (`session.tool.called/success/failed`,
|
|
@@ -571,9 +708,32 @@ function applySessionActivity(event, onCredentialSwitch) {
|
|
|
571
708
|
const id = payload?.sessionID ?? info?.sessionID ?? info?.id;
|
|
572
709
|
if (id)
|
|
573
710
|
sessionActivity.recordActivity(id);
|
|
711
|
+
if (id && type === "session.execution.interrupted") {
|
|
712
|
+
void notifyHostInterrupt(id, typeof payload?.reason === "string" ? payload.reason : "user", typeof event.created === "number" ? event.created : Date.now());
|
|
713
|
+
}
|
|
574
714
|
}
|
|
575
715
|
break;
|
|
576
716
|
}
|
|
577
717
|
}
|
|
578
718
|
}
|
|
719
|
+
/** A plain-text user message; one with files or skills reaches the prompt in another shape. */
|
|
720
|
+
function inboxSteer(sessionID, inboxID, item) {
|
|
721
|
+
if (typeof sessionID !== "string" || !sessionID || typeof inboxID !== "string" || !inboxID)
|
|
722
|
+
return undefined;
|
|
723
|
+
if (item?.type !== "user" || (item.delivery !== "steer" && item.delivery !== "queue"))
|
|
724
|
+
return undefined;
|
|
725
|
+
const prompt = item.payload;
|
|
726
|
+
if (typeof prompt?.text !== "string" || !prompt.text.trim())
|
|
727
|
+
return undefined;
|
|
728
|
+
if (prompt.files?.length || prompt.skills?.length)
|
|
729
|
+
return undefined;
|
|
730
|
+
return { sessionID, inboxID, text: prompt.text };
|
|
731
|
+
}
|
|
732
|
+
function announceInboxSteer(steer) {
|
|
733
|
+
const taken = announceHostSteer(steer);
|
|
734
|
+
if (taken === undefined)
|
|
735
|
+
return;
|
|
736
|
+
trace(`steer: OpenCode enqueued a mid-step message sessionID=${steer.sessionID} inboxID=${steer.inboxID} ` +
|
|
737
|
+
`chars=${steer.text.length} ${taken ? "injected into the pumping Run" : "left for the next step"}`);
|
|
738
|
+
}
|
|
579
739
|
export default plugin;
|
package/dist/plugin.js
CHANGED
|
@@ -4,14 +4,15 @@ import { pollForTokens, exchangeApiKey, isExpiringSoon, generatePkceParams, gene
|
|
|
4
4
|
import { renewSessionIfDue, resolveApiKeyToken, } from "./auth-renewal.js";
|
|
5
5
|
import { CursorAuthError } from "./errors.js";
|
|
6
6
|
import { errorMessage, trace } from "./debug.js";
|
|
7
|
+
import { normalizeClassicInterruptedResults } from "./classic-tool-results.js";
|
|
7
8
|
import { readCache, discoverModels, isCacheFresh } from "./models.js";
|
|
8
9
|
import { modelsToConfig } from "./model-config.js";
|
|
9
10
|
import { loadClassicTools } from "./classic-tools.js";
|
|
10
11
|
import { opencodeGlobalCacheDir } from "./context/paths.js";
|
|
11
12
|
import { readStoredAuth } from "./context/auth-store.js";
|
|
12
13
|
import { resolveAgentUrl } from "./agent-url.js";
|
|
13
|
-
import { captureCursorShellResult, cursorShellEnvForCall, cursorShellOriginalCommand, prepareCursorShellArgs, releaseCursorShellEnv, sanitizeRegisteredCursorShellOutput, setCursorShellPath, } from "./shell-timeout.js";
|
|
14
|
-
import { sessionActivity } from "./activity.js";
|
|
14
|
+
import { captureCursorShellResult, cursorShellEnvForCall, cursorShellOriginalCommand, cursorShellTimeoutMs, prepareCursorShellArgs, releaseCursorShellEnv, sanitizeRegisteredCursorShellOutput, setCursorShellPath, resolveCursorShellPath, } from "./shell-timeout.js";
|
|
15
|
+
import { applyHostPromptEvent, applyHostToolEvent, sessionActivity } from "./activity.js";
|
|
15
16
|
import { dispatchHostEventBridge } from "./host-event-bridge.js";
|
|
16
17
|
import { createPromptHostAgentModeSwitch, setHostAgentModeSwitch } from "./host-agent-mode.js";
|
|
17
18
|
import { isHostPlanFileResolved, resolveHostPlanFile } from "./host-plan-file.js";
|
|
@@ -250,6 +251,9 @@ export async function CursorPlugin(input) {
|
|
|
250
251
|
cursor_image_save: classicTools.imageSave,
|
|
251
252
|
},
|
|
252
253
|
async event({ event }) {
|
|
254
|
+
// The SDK's event union predates these prompt events; read them untyped.
|
|
255
|
+
applyHostPromptEvent(sessionActivity, event.type, event.properties);
|
|
256
|
+
applyHostToolEvent(sessionActivity, event.type, event.properties);
|
|
253
257
|
switch (event.type) {
|
|
254
258
|
case "session.created":
|
|
255
259
|
sessionActivity.linkSession(event.properties.info.id, event.properties.info.parentID);
|
|
@@ -266,6 +270,11 @@ export async function CursorPlugin(input) {
|
|
|
266
270
|
break;
|
|
267
271
|
case "message.part.updated":
|
|
268
272
|
sessionActivity.recordActivity(event.properties.part.sessionID);
|
|
273
|
+
// Rejection/abort can stop the classic host before a continuation or
|
|
274
|
+
// execute.after. Its persisted tool state is still authoritative.
|
|
275
|
+
if (event.properties.part.type === "tool" && event.properties.part.state.status === "error") {
|
|
276
|
+
releaseCursorShellEnv(event.properties.part.callID);
|
|
277
|
+
}
|
|
269
278
|
break;
|
|
270
279
|
}
|
|
271
280
|
await dispatchHostEventBridge({
|
|
@@ -275,13 +284,22 @@ export async function CursorPlugin(input) {
|
|
|
275
284
|
serverUrl: input.serverUrl,
|
|
276
285
|
});
|
|
277
286
|
},
|
|
287
|
+
async "experimental.chat.messages.transform"(_hookInput, output) {
|
|
288
|
+
// The classic host retains the original array and discards hook output.
|
|
289
|
+
// Replace entries with request-only clones, preserving array identity.
|
|
290
|
+
const normalized = normalizeClassicInterruptedResults(output.messages);
|
|
291
|
+
for (let index = 0; index < output.messages.length; index++)
|
|
292
|
+
output.messages[index] = normalized[index];
|
|
293
|
+
},
|
|
278
294
|
async "tool.execute.before"(hookInput, output) {
|
|
279
295
|
if (hookInput.tool !== "bash")
|
|
280
296
|
return;
|
|
281
297
|
// bash/zsh retain the original display/permission command and wrap via
|
|
282
|
-
// shell.env. sh/dash
|
|
298
|
+
// shell.env. sh/dash disclose the managed launcher inline because their
|
|
283
299
|
// non-interactive `-c` path ignores BASH_ENV / ZDOTDIR.
|
|
284
|
-
prepareCursorShellArgs(hookInput.callID, output.args
|
|
300
|
+
prepareCursorShellArgs(hookInput.callID, output.args, {
|
|
301
|
+
shellPath: resolveCursorShellPath(undefined, input.directory),
|
|
302
|
+
});
|
|
285
303
|
},
|
|
286
304
|
async "shell.env"(hookInput, output) {
|
|
287
305
|
const env = cursorShellEnvForCall(hookInput.callID);
|
|
@@ -302,6 +320,11 @@ export async function CursorPlugin(input) {
|
|
|
302
320
|
if (typeof metadata.output === "string") {
|
|
303
321
|
metadata.output = sanitizeRegisteredCursorShellOutput(hookInput.callID, metadata.output);
|
|
304
322
|
}
|
|
323
|
+
// The stored output keeps only a display notice, and the saved
|
|
324
|
+
// outcome does not survive consumption or a restart.
|
|
325
|
+
const timeoutMs = cursorShellTimeoutMs(hookInput.callID);
|
|
326
|
+
if (timeoutMs !== undefined)
|
|
327
|
+
metadata.cursorShellTimeoutMs = timeoutMs;
|
|
305
328
|
}
|
|
306
329
|
}
|
|
307
330
|
finally {
|
|
@@ -339,7 +362,7 @@ export async function CursorPlugin(input) {
|
|
|
339
362
|
}
|
|
340
363
|
},
|
|
341
364
|
async config(cfg) {
|
|
342
|
-
setCursorShellPath(cfg.shell);
|
|
365
|
+
setCursorShellPath(cfg.shell, input.directory);
|
|
343
366
|
cfg.provider ??= {};
|
|
344
367
|
const models = await loadModels();
|
|
345
368
|
const existing = cfg.provider[CURSOR_PROVIDER_ID];
|
package/dist/pricing-data.d.ts
CHANGED
|
@@ -140,14 +140,19 @@ export declare const CURSOR_MODEL_COSTS: {
|
|
|
140
140
|
};
|
|
141
141
|
readonly "gemini-3.7-flash": {
|
|
142
142
|
readonly input: 0.75;
|
|
143
|
-
readonly output: 3.
|
|
143
|
+
readonly output: 3.75;
|
|
144
144
|
readonly cache_read: 0.075;
|
|
145
145
|
};
|
|
146
146
|
readonly "gemini-3.8-flash": {
|
|
147
147
|
readonly input: 0.75;
|
|
148
|
-
readonly output: 3.
|
|
148
|
+
readonly output: 3.75;
|
|
149
149
|
readonly cache_read: 0.075;
|
|
150
150
|
};
|
|
151
|
+
readonly "gemini-4-argon": {
|
|
152
|
+
readonly input: 2;
|
|
153
|
+
readonly output: 10;
|
|
154
|
+
readonly cache_read: 0.1;
|
|
155
|
+
};
|
|
151
156
|
readonly "glm-5.2": {
|
|
152
157
|
readonly input: 1.4;
|
|
153
158
|
readonly output: 4.4;
|
|
@@ -304,6 +309,11 @@ export declare const CURSOR_MODEL_COSTS: {
|
|
|
304
309
|
readonly output: 4.25;
|
|
305
310
|
readonly cache_read: 0.15;
|
|
306
311
|
};
|
|
312
|
+
readonly "muse-wonder-1.0": {
|
|
313
|
+
readonly input: 4;
|
|
314
|
+
readonly output: 20;
|
|
315
|
+
readonly cache_read: 0.2;
|
|
316
|
+
};
|
|
307
317
|
};
|
|
308
318
|
export declare const CURSOR_MODEL_CONTEXTS: {
|
|
309
319
|
readonly "claude-fable-5": {
|
|
@@ -396,6 +406,10 @@ export declare const CURSOR_MODEL_CONTEXTS: {
|
|
|
396
406
|
readonly maxContext: 200000;
|
|
397
407
|
readonly maxContextForMaxMode: 1000000;
|
|
398
408
|
};
|
|
409
|
+
readonly "gemini-4-argon": {
|
|
410
|
+
readonly maxContext: 200000;
|
|
411
|
+
readonly maxContextForMaxMode: 1000000;
|
|
412
|
+
};
|
|
399
413
|
readonly "glm-5.2": {
|
|
400
414
|
readonly maxContext: 200000;
|
|
401
415
|
};
|
|
@@ -464,6 +478,10 @@ export declare const CURSOR_MODEL_CONTEXTS: {
|
|
|
464
478
|
readonly maxContext: 300000;
|
|
465
479
|
readonly maxContextForMaxMode: 1000000;
|
|
466
480
|
};
|
|
481
|
+
readonly "muse-wonder-1.0": {
|
|
482
|
+
readonly maxContext: 300000;
|
|
483
|
+
readonly maxContextForMaxMode: 1000000;
|
|
484
|
+
};
|
|
467
485
|
};
|
|
468
486
|
export declare const CURSOR_MODEL_CAPABILITIES: {
|
|
469
487
|
readonly "claude-fable-5": {
|
|
@@ -535,6 +553,9 @@ export declare const CURSOR_MODEL_CAPABILITIES: {
|
|
|
535
553
|
readonly "gemini-3.8-flash": {
|
|
536
554
|
readonly supportsImages: true;
|
|
537
555
|
};
|
|
556
|
+
readonly "gemini-4-argon": {
|
|
557
|
+
readonly supportsImages: true;
|
|
558
|
+
};
|
|
538
559
|
readonly "glm-5.2": {
|
|
539
560
|
readonly supportsImages: false;
|
|
540
561
|
};
|
|
@@ -595,4 +616,7 @@ export declare const CURSOR_MODEL_CAPABILITIES: {
|
|
|
595
616
|
readonly "muse-spark-1.3": {
|
|
596
617
|
readonly supportsImages: true;
|
|
597
618
|
};
|
|
619
|
+
readonly "muse-wonder-1.0": {
|
|
620
|
+
readonly supportsImages: true;
|
|
621
|
+
};
|
|
598
622
|
};
|
package/dist/pricing-data.js
CHANGED
|
@@ -140,14 +140,19 @@ export const CURSOR_MODEL_COSTS = {
|
|
|
140
140
|
},
|
|
141
141
|
"gemini-3.7-flash": {
|
|
142
142
|
"input": 0.75,
|
|
143
|
-
"output": 3.
|
|
143
|
+
"output": 3.75,
|
|
144
144
|
"cache_read": 0.075
|
|
145
145
|
},
|
|
146
146
|
"gemini-3.8-flash": {
|
|
147
147
|
"input": 0.75,
|
|
148
|
-
"output": 3.
|
|
148
|
+
"output": 3.75,
|
|
149
149
|
"cache_read": 0.075
|
|
150
150
|
},
|
|
151
|
+
"gemini-4-argon": {
|
|
152
|
+
"input": 2,
|
|
153
|
+
"output": 10,
|
|
154
|
+
"cache_read": 0.1
|
|
155
|
+
},
|
|
151
156
|
"glm-5.2": {
|
|
152
157
|
"input": 1.4,
|
|
153
158
|
"output": 4.4,
|
|
@@ -303,6 +308,11 @@ export const CURSOR_MODEL_COSTS = {
|
|
|
303
308
|
"input": 1.25,
|
|
304
309
|
"output": 4.25,
|
|
305
310
|
"cache_read": 0.15
|
|
311
|
+
},
|
|
312
|
+
"muse-wonder-1.0": {
|
|
313
|
+
"input": 4,
|
|
314
|
+
"output": 20,
|
|
315
|
+
"cache_read": 0.2
|
|
306
316
|
}
|
|
307
317
|
};
|
|
308
318
|
export const CURSOR_MODEL_CONTEXTS = {
|
|
@@ -396,6 +406,10 @@ export const CURSOR_MODEL_CONTEXTS = {
|
|
|
396
406
|
"maxContext": 200000,
|
|
397
407
|
"maxContextForMaxMode": 1000000
|
|
398
408
|
},
|
|
409
|
+
"gemini-4-argon": {
|
|
410
|
+
"maxContext": 200000,
|
|
411
|
+
"maxContextForMaxMode": 1000000
|
|
412
|
+
},
|
|
399
413
|
"glm-5.2": {
|
|
400
414
|
"maxContext": 200000
|
|
401
415
|
},
|
|
@@ -463,6 +477,10 @@ export const CURSOR_MODEL_CONTEXTS = {
|
|
|
463
477
|
"muse-spark-1.3": {
|
|
464
478
|
"maxContext": 300000,
|
|
465
479
|
"maxContextForMaxMode": 1000000
|
|
480
|
+
},
|
|
481
|
+
"muse-wonder-1.0": {
|
|
482
|
+
"maxContext": 300000,
|
|
483
|
+
"maxContextForMaxMode": 1000000
|
|
466
484
|
}
|
|
467
485
|
};
|
|
468
486
|
export const CURSOR_MODEL_CAPABILITIES = {
|
|
@@ -535,6 +553,9 @@ export const CURSOR_MODEL_CAPABILITIES = {
|
|
|
535
553
|
"gemini-3.8-flash": {
|
|
536
554
|
"supportsImages": true
|
|
537
555
|
},
|
|
556
|
+
"gemini-4-argon": {
|
|
557
|
+
"supportsImages": true
|
|
558
|
+
},
|
|
538
559
|
"glm-5.2": {
|
|
539
560
|
"supportsImages": false
|
|
540
561
|
},
|
|
@@ -594,5 +615,8 @@ export const CURSOR_MODEL_CAPABILITIES = {
|
|
|
594
615
|
},
|
|
595
616
|
"muse-spark-1.3": {
|
|
596
617
|
"supportsImages": true
|
|
618
|
+
},
|
|
619
|
+
"muse-wonder-1.0": {
|
|
620
|
+
"supportsImages": true
|
|
597
621
|
}
|
|
598
622
|
};
|
package/dist/pricing.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export declare function validateOpenCodeModelCost(value: unknown, path?: string)
|
|
|
40
40
|
errors: string[];
|
|
41
41
|
};
|
|
42
42
|
export declare function isOpenCodeModelCost(value: unknown): value is OpenCodeModelCost;
|
|
43
|
-
/** Strip synthetic
|
|
43
|
+
/** Strip synthetic context suffixes (`…-1m`, `…-500k`, `…-500k-2-fast`, …). */
|
|
44
44
|
export declare function wireModelIdForPricing(modelId: string): {
|
|
45
45
|
baseId: string;
|
|
46
46
|
longContextEntry: boolean;
|