cursor-opencode-provider 0.7.6 → 0.8.1
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 +79 -0
- package/README.md +36 -15
- package/SECURITY.md +43 -7
- package/dist/agent-url.js +2 -2
- package/dist/auth-renewal.d.ts +85 -0
- package/dist/auth-renewal.js +284 -0
- package/dist/auth.d.ts +38 -22
- package/dist/auth.js +79 -96
- package/dist/classic-tools.d.ts +15 -0
- package/dist/classic-tools.js +45 -0
- package/dist/context/build.d.ts +62 -7
- package/dist/context/build.js +127 -95
- package/dist/context/dynamic-catalog.d.ts +2 -58
- package/dist/context/dynamic-catalog.js +16 -161
- package/dist/context/epoch.d.ts +6 -4
- package/dist/context/epoch.js +22 -10
- package/dist/context/frozen.js +42 -15
- package/dist/context/index.d.ts +3 -1
- package/dist/context/index.js +3 -1
- package/dist/context/overlay.d.ts +2 -23
- package/dist/context/overlay.js +6 -30
- package/dist/context/paths.d.ts +51 -21
- package/dist/context/paths.js +78 -27
- package/dist/context/rules.d.ts +3 -15
- package/dist/context/rules.js +10 -186
- package/dist/context/skills-bridge.d.ts +22 -0
- package/dist/context/skills-bridge.js +20 -0
- package/dist/context/skills.d.ts +31 -8
- package/dist/context/skills.js +187 -86
- package/dist/debug.d.ts +2 -0
- package/dist/debug.js +4 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +27 -3
- package/dist/host-agent-mode.d.ts +53 -4
- package/dist/host-agent-mode.js +119 -15
- package/dist/host-plan-file.d.ts +28 -0
- package/dist/host-plan-file.js +76 -0
- package/dist/image-input.d.ts +18 -0
- package/dist/image-input.js +119 -28
- package/dist/image-save.d.ts +10 -2
- package/dist/image-save.js +35 -23
- package/dist/index.d.ts +10 -0
- package/dist/language-model.d.ts +86 -25
- package/dist/language-model.js +1295 -369
- package/dist/model-config.d.ts +9 -0
- package/dist/model-config.js +28 -1
- package/dist/opencode2/catalog.d.ts +1 -0
- package/dist/opencode2/catalog.js +2 -0
- package/dist/opencode2/image-save-tool.d.ts +8 -0
- package/dist/opencode2/image-save-tool.js +84 -0
- package/dist/opencode2/integration.d.ts +30 -7
- package/dist/opencode2/integration.js +75 -46
- package/dist/opencode2/types.d.ts +15 -0
- package/dist/parallel-step.d.ts +65 -0
- package/dist/parallel-step.js +102 -0
- package/dist/plugin-opencode2.js +58 -56
- package/dist/plugin.d.ts +0 -10
- package/dist/plugin.js +207 -127
- package/dist/pricing-data.d.ts +14 -1
- package/dist/pricing-data.js +14 -1
- package/dist/protocol/ask-question.d.ts +12 -1
- package/dist/protocol/ask-question.js +77 -3
- package/dist/protocol/blob-store.js +3 -2
- package/dist/protocol/conversation-bind.js +1 -1
- package/dist/protocol/conversation-persistence.d.ts +2 -0
- package/dist/protocol/conversation-persistence.js +11 -1
- package/dist/protocol/conversation-state.d.ts +2 -0
- package/dist/protocol/conversation-state.js +19 -0
- package/dist/protocol/create-plan.d.ts +47 -25
- package/dist/protocol/create-plan.js +35 -24
- package/dist/protocol/interactions.d.ts +26 -17
- package/dist/protocol/interactions.js +80 -31
- package/dist/protocol/messages.js +96 -18
- package/dist/protocol/request.d.ts +16 -9
- package/dist/protocol/request.js +17 -25
- package/dist/protocol/switch-mode.d.ts +66 -27
- package/dist/protocol/switch-mode.js +147 -73
- package/dist/protocol/token-details.d.ts +2 -0
- package/dist/protocol/token-details.js +11 -1
- package/dist/protocol/tool-call-bridge.d.ts +4 -0
- package/dist/protocol/tool-call-bridge.js +14 -0
- package/dist/protocol/tools.d.ts +33 -11
- package/dist/protocol/tools.js +165 -103
- package/dist/protocol/turn-provenance.d.ts +52 -0
- package/dist/protocol/turn-provenance.js +174 -0
- package/dist/replay-safety.d.ts +6 -0
- package/dist/replay-safety.js +56 -6
- package/dist/session.d.ts +44 -2
- package/dist/session.js +45 -1
- package/dist/shared.d.ts +7 -0
- package/dist/shared.js +7 -0
- package/dist/todo-store.d.ts +2 -0
- package/dist/todo-store.js +19 -0
- package/dist/transport/connect.d.ts +2 -0
- package/dist/transport/connect.js +23 -6
- package/dist/usage.js +23 -6
- package/package.json +3 -2
- package/dist/context/agents.d.ts +0 -12
- package/dist/context/agents.js +0 -136
- package/dist/plan-execution-kickoff.d.ts +0 -57
- package/dist/plan-execution-kickoff.js +0 -186
|
@@ -14,6 +14,8 @@ export type HandledInteraction = {
|
|
|
14
14
|
variantField: number;
|
|
15
15
|
variantName: string;
|
|
16
16
|
outcome: "rejected" | "acknowledged" | "failed" | "bridged" | "approved";
|
|
17
|
+
/** Decoded model call id, including refusals and lifecycle acknowledgments. */
|
|
18
|
+
toolCallId?: string;
|
|
17
19
|
/**
|
|
18
20
|
* Immediate reply for this query. Absent for bridged *synchronous* AskQuestion
|
|
19
21
|
* or SwitchMode: Cursor blocks until the host tool returns, exactly as its own
|
|
@@ -31,24 +33,29 @@ export type HandledInteraction = {
|
|
|
31
33
|
*/
|
|
32
34
|
switchMode?: DecodedSwitchModeQuery & {
|
|
33
35
|
bridge: SwitchModeBridge;
|
|
34
|
-
toolName?: SwitchModeHostTool
|
|
36
|
+
toolName?: SwitchModeHostTool;
|
|
35
37
|
};
|
|
36
38
|
/**
|
|
37
|
-
* Present when CreatePlan needs a host tool:
|
|
38
|
-
* owns the write *and* the
|
|
39
|
-
*
|
|
40
|
-
*
|
|
39
|
+
* Present when CreatePlan needs a host tool: a host plan-stage tool that
|
|
40
|
+
* owns the write *and* the review, the host's `plan_exit` review of the
|
|
41
|
+
* plan file the provider just wrote (`planUri` / `planPath` set), or the
|
|
42
|
+
* emulated `question` prompt after that write.
|
|
41
43
|
*/
|
|
42
44
|
createPlan?: DecodedCreatePlanQuery & {
|
|
43
45
|
bridge: CreatePlanBridge;
|
|
44
|
-
toolName: typeof CURSOR_PLAN_STAGE_TOOL | "question";
|
|
46
|
+
toolName: typeof CURSOR_PLAN_STAGE_TOOL | "plan_exit" | "question";
|
|
45
47
|
planUri?: string;
|
|
46
|
-
/** Filesystem path of the plan the provider just wrote
|
|
48
|
+
/** Filesystem path of the plan the provider just wrote. */
|
|
47
49
|
planPath?: string;
|
|
48
50
|
questionInput?: ReturnType<typeof createPlanApprovalQuestionInput>;
|
|
49
|
-
/** The plan to show the user before
|
|
51
|
+
/** The plan to show the user before the host review. */
|
|
50
52
|
planReview?: string;
|
|
51
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Cursor tool call id of a CreatePlan the host plan agent will record
|
|
56
|
+
* instead. Nothing was written, so its display must not be mirrored as a plan.
|
|
57
|
+
*/
|
|
58
|
+
deferredCreatePlanToolCallId?: string;
|
|
52
59
|
/** Present when an image generation was approved: the target to expect. */
|
|
53
60
|
generateImage?: DecodedGenerateImageQuery;
|
|
54
61
|
};
|
|
@@ -81,16 +88,18 @@ export type HandleInteractionQueryOptions = {
|
|
|
81
88
|
workspaceRoot?: string;
|
|
82
89
|
/** True when the advertised host plan-stage tool is available. */
|
|
83
90
|
canBridgeCreatePlan?: boolean;
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
/** An approved switch into the host `plan` agent waits for this Run to end. */
|
|
92
|
+
hostPlanEntryPending?: boolean;
|
|
93
|
+
/** Host primary agent this Run executes under, when the host reported it. */
|
|
94
|
+
hostAgent?: string;
|
|
95
|
+
/** Provider-recorded Cursor plan/spec mode for the CreatePlan execution gate. */
|
|
89
96
|
planModeActive?: boolean;
|
|
97
|
+
/** The host's own plan file for this session, when the host defines one. */
|
|
98
|
+
hostPlanFile?: string;
|
|
90
99
|
/**
|
|
91
100
|
* Cursor unified mode currently recorded for this session. A SwitchMode to
|
|
92
101
|
* the mode already in effect needs no approval, matching Cursor's own IDE
|
|
93
|
-
* handler and keeping the plan
|
|
102
|
+
* handler and keeping the host's plan review from running twice.
|
|
94
103
|
*/
|
|
95
104
|
activeCursorModeId?: string;
|
|
96
105
|
};
|
|
@@ -112,9 +121,9 @@ export declare function inspectInteractionQueryWire(agentServerPayload: Uint8Arr
|
|
|
112
121
|
* Bridged / persisted exceptions:
|
|
113
122
|
* - AskQuestion (#3) → OpenCode `question` tool
|
|
114
123
|
* - SwitchMode (#4) → OpenCode `plan_enter` / `plan_exit` when advertised
|
|
115
|
-
* - CreatePlan (#7) → host
|
|
116
|
-
*
|
|
117
|
-
*
|
|
124
|
+
* - CreatePlan (#7) → the host's plan file (session `Session.plan` file when
|
|
125
|
+
* known, else a new file under hostPlansDir); empty args still get the CLI
|
|
126
|
+
* empty-`plan_uri` success ack
|
|
118
127
|
* - GenerateImage (#12) → approve when `cursor_image_save` is advertised
|
|
119
128
|
*/
|
|
120
129
|
export declare function handleInteractionQuery(query: Record<string, unknown>, agentServerPayload: Uint8Array, options?: HandleInteractionQueryOptions): HandledInteraction;
|
|
@@ -2,7 +2,7 @@ import { encodeMessage } from "./messages.js";
|
|
|
2
2
|
import { readAllFields } from "./struct.js";
|
|
3
3
|
import { decodeAskQuestionQuery, MISSING_ARGS_REASON, MISSING_QUERY_REASON, } from "./ask-question.js";
|
|
4
4
|
import { decodeGenerateImageQuery, } from "./generate-image.js";
|
|
5
|
-
import { CURSOR_PLAN_STAGE_TOOL,
|
|
5
|
+
import { CURSOR_PLAN_STAGE_TOOL, decodeCreatePlanQuery, createPlanApprovalQuestionInput, renderPlanReviewMessage, resolveCreatePlanBridge, writeOpencodePlanFile, } from "./create-plan.js";
|
|
6
6
|
import { decodeSwitchModeQuery, MISSING_ARGS_REASON as SWITCH_MODE_MISSING_ARGS_REASON, MISSING_QUERY_REASON as SWITCH_MODE_MISSING_QUERY_REASON, resolveSwitchModeBridge, switchModeApprovedResult, } from "./switch-mode.js";
|
|
7
7
|
const HEADLESS_REASON = "This Cursor interaction requires UI approval and is not available through the OpenCode provider.";
|
|
8
8
|
/**
|
|
@@ -76,9 +76,9 @@ export function inspectInteractionQueryWire(agentServerPayload) {
|
|
|
76
76
|
* Bridged / persisted exceptions:
|
|
77
77
|
* - AskQuestion (#3) → OpenCode `question` tool
|
|
78
78
|
* - SwitchMode (#4) → OpenCode `plan_enter` / `plan_exit` when advertised
|
|
79
|
-
* - CreatePlan (#7) → host
|
|
80
|
-
*
|
|
81
|
-
*
|
|
79
|
+
* - CreatePlan (#7) → the host's plan file (session `Session.plan` file when
|
|
80
|
+
* known, else a new file under hostPlansDir); empty args still get the CLI
|
|
81
|
+
* empty-`plan_uri` success ack
|
|
82
82
|
* - GenerateImage (#12) → approve when `cursor_image_save` is advertised
|
|
83
83
|
*/
|
|
84
84
|
export function handleInteractionQuery(query, agentServerPayload, options = {}) {
|
|
@@ -160,7 +160,7 @@ export function handleInteractionQuery(query, agentServerPayload, options = {})
|
|
|
160
160
|
* non-blocking prompt, and otherwise hold the query open until the user picks.
|
|
161
161
|
*/
|
|
162
162
|
function handleAskQuestionQuery(id, variantBytes, options) {
|
|
163
|
-
const base = { id, variantField: 3, variantName: "ask_question_interaction_query" };
|
|
163
|
+
const base = { id, variantField: 3, variantName: "ask_question_interaction_query", toolCallId: undefined };
|
|
164
164
|
const reject = (reason) => ({
|
|
165
165
|
...base,
|
|
166
166
|
outcome: "rejected",
|
|
@@ -171,6 +171,7 @@ function handleAskQuestionQuery(id, variantBytes, options) {
|
|
|
171
171
|
const decoded = decodeAskQuestionQuery(variantBytes);
|
|
172
172
|
if (!decoded)
|
|
173
173
|
return reject(MISSING_ARGS_REASON);
|
|
174
|
+
base.toolCallId = decoded.toolCallId;
|
|
174
175
|
if (!options.canBridgeAskQuestion)
|
|
175
176
|
return reject(ASK_QUESTION_UNAVAILABLE_REASON);
|
|
176
177
|
return {
|
|
@@ -200,7 +201,7 @@ export function buildAskQuestionInteractionReply(id, result) {
|
|
|
200
201
|
* until the host tool returns, then reply approved{} or rejected{reason}.
|
|
201
202
|
*/
|
|
202
203
|
function handleSwitchModeQuery(id, variantBytes, options) {
|
|
203
|
-
const base = { id, variantField: 4, variantName: "switch_mode_request_query" };
|
|
204
|
+
const base = { id, variantField: 4, variantName: "switch_mode_request_query", toolCallId: undefined };
|
|
204
205
|
const reject = (reason) => ({
|
|
205
206
|
...base,
|
|
206
207
|
outcome: "rejected",
|
|
@@ -211,10 +212,12 @@ function handleSwitchModeQuery(id, variantBytes, options) {
|
|
|
211
212
|
const decoded = decodeSwitchModeQuery(variantBytes);
|
|
212
213
|
if (!decoded)
|
|
213
214
|
return reject(SWITCH_MODE_MISSING_ARGS_REASON);
|
|
215
|
+
base.toolCallId = decoded.toolCallId;
|
|
214
216
|
const bridge = resolveSwitchModeBridge(decoded.args.targetModeId, {
|
|
215
217
|
allowTools: options.allowTools === true,
|
|
216
218
|
advertised: options.advertisedTools ?? [],
|
|
217
219
|
...(options.activeCursorModeId ? { activeModeId: options.activeCursorModeId } : {}),
|
|
220
|
+
...(options.hostAgent ? { hostAgent: options.hostAgent } : {}),
|
|
218
221
|
});
|
|
219
222
|
if (bridge.kind === "reject")
|
|
220
223
|
return reject(bridge.reason);
|
|
@@ -243,10 +246,10 @@ function handleSwitchModeQuery(id, variantBytes, options) {
|
|
|
243
246
|
switchMode: {
|
|
244
247
|
...decoded,
|
|
245
248
|
bridge,
|
|
246
|
-
toolName: bridge.
|
|
249
|
+
toolName: bridge.toolName,
|
|
247
250
|
},
|
|
248
|
-
// Keep Cursor waiting until the host tool returns
|
|
249
|
-
//
|
|
251
|
+
// Keep Cursor waiting until the host plan tool returns: the host owns the
|
|
252
|
+
// mode-switch approval.
|
|
250
253
|
reply: undefined,
|
|
251
254
|
};
|
|
252
255
|
}
|
|
@@ -298,7 +301,7 @@ export function buildAsyncAskQuestionCompletion(originalToolCallId, originalArgs
|
|
|
298
301
|
* written but not accepted, so the model keeps planning.
|
|
299
302
|
*/
|
|
300
303
|
function handleCreatePlanQuery(id, variantBytes, options) {
|
|
301
|
-
const base = { id, variantField: 7, variantName: "create_plan_request_query" };
|
|
304
|
+
const base = { id, variantField: 7, variantName: "create_plan_request_query", toolCallId: undefined };
|
|
302
305
|
const reply = (result) => ({
|
|
303
306
|
...base,
|
|
304
307
|
outcome: result.error ? "failed" : "acknowledged",
|
|
@@ -315,13 +318,46 @@ function handleCreatePlanQuery(id, variantBytes, options) {
|
|
|
315
318
|
const decoded = decodeCreatePlanQuery(variantBytes);
|
|
316
319
|
if (!decoded)
|
|
317
320
|
return reply({ success: {}, plan_uri: "" });
|
|
321
|
+
base.toolCallId = decoded.toolCallId;
|
|
318
322
|
const bridge = resolveCreatePlanBridge({
|
|
319
323
|
allowTools: options.allowTools === true,
|
|
320
324
|
canStage: options.canBridgeCreatePlan === true,
|
|
321
|
-
planModeActive: options.planModeActive === true,
|
|
322
325
|
advertised: options.advertisedTools ?? [],
|
|
326
|
+
hostPlanEntryPending: options.hostPlanEntryPending === true,
|
|
327
|
+
...(options.hostAgent ? { hostAgent: options.hostAgent } : {}),
|
|
328
|
+
planModeActive: options.planModeActive === true,
|
|
329
|
+
...(options.hostPlanFile ? { hostPlanFile: options.hostPlanFile } : {}),
|
|
323
330
|
});
|
|
324
|
-
// The host plan
|
|
331
|
+
// The host's plan agent records and reviews the plan; write nothing here.
|
|
332
|
+
if (bridge.kind === "defer") {
|
|
333
|
+
return {
|
|
334
|
+
...reply({ error: { error: bridge.reason }, plan_uri: "" }),
|
|
335
|
+
...(decoded.toolCallId ? { deferredCreatePlanToolCallId: decoded.toolCallId } : {}),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
// The host plan agent reviews its own plan file: record the plan there and
|
|
339
|
+
// hand it to the host plan_exit, which asks the user and, on approval,
|
|
340
|
+
// moves the session to its build agent.
|
|
341
|
+
if (bridge.kind === "exit") {
|
|
342
|
+
const written = writeOpencodePlanFile(decoded.args, options.workspaceRoot ?? "", Date.now(), bridge.planPath);
|
|
343
|
+
if (!written.ok) {
|
|
344
|
+
return reply({ error: { error: written.error }, plan_uri: "" });
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
...base,
|
|
348
|
+
outcome: "bridged",
|
|
349
|
+
createPlan: {
|
|
350
|
+
...decoded,
|
|
351
|
+
bridge,
|
|
352
|
+
toolName: "plan_exit",
|
|
353
|
+
planUri: written.planUri,
|
|
354
|
+
planPath: written.planPath,
|
|
355
|
+
planReview: renderPlanReviewMessage(written.markdown, written.planPath),
|
|
356
|
+
},
|
|
357
|
+
reply: undefined,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
// The host plan-stage tool owns the write and the review together.
|
|
325
361
|
if (bridge.kind === "stage") {
|
|
326
362
|
return {
|
|
327
363
|
...base,
|
|
@@ -330,25 +366,18 @@ function handleCreatePlanQuery(id, variantBytes, options) {
|
|
|
330
366
|
reply: undefined,
|
|
331
367
|
};
|
|
332
368
|
}
|
|
333
|
-
// A lifecycle turn (title generation, compaction) runs alongside the real one
|
|
334
|
-
// and would otherwise write a second, throwaway plan file for the same
|
|
335
|
-
// request. Ack it the CLI way instead of persisting anything.
|
|
336
|
-
if (options.allowTools !== true)
|
|
337
|
-
return reply({ success: {}, plan_uri: "" });
|
|
338
|
-
const workspaceRoot = options.workspaceRoot?.trim();
|
|
339
|
-
if (!workspaceRoot) {
|
|
340
|
-
return reply({
|
|
341
|
-
error: { error: "CreatePlan requires a workspace root to write the plan file" },
|
|
342
|
-
plan_uri: "",
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
const written = writeOpencodePlanFile(decoded.args, workspaceRoot);
|
|
346
|
-
if (!written.ok) {
|
|
347
|
-
return reply({ error: { error: written.error }, plan_uri: "" });
|
|
348
|
-
}
|
|
349
|
-
// The plan is on disk either way; only execution needs the user. Hold Cursor's
|
|
350
|
-
// query open while the host asks, exactly as the CLI blocks on its own prompt.
|
|
351
369
|
if (bridge.kind === "approve") {
|
|
370
|
+
const workspaceRoot = options.workspaceRoot?.trim();
|
|
371
|
+
if (!workspaceRoot) {
|
|
372
|
+
return reply({
|
|
373
|
+
error: { error: "CreatePlan requires a workspace root to write the plan file" },
|
|
374
|
+
plan_uri: "",
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
const written = writeOpencodePlanFile(decoded.args, workspaceRoot, Date.now(), options.hostPlanFile);
|
|
378
|
+
if (!written.ok) {
|
|
379
|
+
return reply({ error: { error: written.error }, plan_uri: "" });
|
|
380
|
+
}
|
|
352
381
|
return {
|
|
353
382
|
...base,
|
|
354
383
|
outcome: "bridged",
|
|
@@ -364,6 +393,25 @@ function handleCreatePlanQuery(id, variantBytes, options) {
|
|
|
364
393
|
reply: undefined,
|
|
365
394
|
};
|
|
366
395
|
}
|
|
396
|
+
// A lifecycle turn (title generation, compaction) runs alongside the real one
|
|
397
|
+
// and would otherwise write a second, throwaway plan file for the same
|
|
398
|
+
// request. Ack it the CLI way instead of persisting anything.
|
|
399
|
+
if (options.allowTools !== true)
|
|
400
|
+
return reply({ success: {}, plan_uri: "" });
|
|
401
|
+
const workspaceRoot = options.workspaceRoot?.trim();
|
|
402
|
+
if (!workspaceRoot) {
|
|
403
|
+
return reply({
|
|
404
|
+
error: { error: "CreatePlan requires a workspace root to write the plan file" },
|
|
405
|
+
plan_uri: "",
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
// The session's own plan file when known, as the host's plan agent uses it.
|
|
409
|
+
const written = writeOpencodePlanFile(decoded.args, workspaceRoot, Date.now(), options.hostPlanFile);
|
|
410
|
+
if (!written.ok) {
|
|
411
|
+
return reply({ error: { error: written.error }, plan_uri: "" });
|
|
412
|
+
}
|
|
413
|
+
// Nothing advertised can ask: the plan is recorded where the host keeps
|
|
414
|
+
// plans. Execution starts only if the user later switches agents.
|
|
367
415
|
return reply({ success: {}, plan_uri: written.planUri });
|
|
368
416
|
}
|
|
369
417
|
/**
|
|
@@ -375,7 +423,7 @@ function handleCreatePlanQuery(id, variantBytes, options) {
|
|
|
375
423
|
* nowhere to go.
|
|
376
424
|
*/
|
|
377
425
|
function handleGenerateImageQuery(id, variantBytes, options) {
|
|
378
|
-
const base = { id, variantField: 12, variantName: "generate_image_request_query" };
|
|
426
|
+
const base = { id, variantField: 12, variantName: "generate_image_request_query", toolCallId: undefined };
|
|
379
427
|
const reject = (reason) => ({
|
|
380
428
|
...base,
|
|
381
429
|
outcome: "rejected",
|
|
@@ -388,6 +436,7 @@ function handleGenerateImageQuery(id, variantBytes, options) {
|
|
|
388
436
|
const decoded = decodeGenerateImageQuery(variantBytes);
|
|
389
437
|
if (!decoded)
|
|
390
438
|
return reject("Missing generate image arguments");
|
|
439
|
+
base.toolCallId = decoded.toolCallId;
|
|
391
440
|
if (!options.canSaveGeneratedImage) {
|
|
392
441
|
return reject("This OpenCode agent cannot save a generated image, so generating one would "
|
|
393
442
|
+ "produce nothing. Describe the image in your reply instead.");
|
|
@@ -28,8 +28,8 @@ export function createMessageTypes() {
|
|
|
28
28
|
]);
|
|
29
29
|
addType(root, "Heartbeat", []);
|
|
30
30
|
// Display ToolCall (interaction_update.tool_call_*) — agent.v1 oneof, not
|
|
31
|
-
// {tool_name,args} strings.
|
|
32
|
-
//
|
|
31
|
+
// {tool_name,args} strings. Decode finalized todo state for mirroring and
|
|
32
|
+
// MCP errors for diagnostics; other completion results are not replayed.
|
|
33
33
|
root.add(new protobuf.Enum("TodoStatus", {
|
|
34
34
|
TODO_STATUS_UNSPECIFIED: 0,
|
|
35
35
|
TODO_STATUS_PENDING: 1,
|
|
@@ -79,7 +79,10 @@ export function createMessageTypes() {
|
|
|
79
79
|
]);
|
|
80
80
|
addType(root, "EditToolCall", [{ id: 1, name: "args", type: "EditToolArgs" }]);
|
|
81
81
|
addType(root, "LsToolCall", [{ id: 1, name: "args", type: "LsArgs" }]);
|
|
82
|
-
addType(root, "McpToolCall", [
|
|
82
|
+
addType(root, "McpToolCall", [
|
|
83
|
+
{ id: 1, name: "args", type: "McpArgs" },
|
|
84
|
+
{ id: 2, name: "result", type: "McpResult" },
|
|
85
|
+
]);
|
|
83
86
|
addType(root, "CreatePlanArgs", [
|
|
84
87
|
{ id: 1, name: "plan", type: "string" },
|
|
85
88
|
{ id: 2, name: "todos", type: "TodoItem", repeated: true },
|
|
@@ -389,6 +392,13 @@ export function createMessageTypes() {
|
|
|
389
392
|
{ id: 3, name: "args_text_delta", type: "string" },
|
|
390
393
|
{ id: 4, name: "model_call_id", type: "string" },
|
|
391
394
|
]);
|
|
395
|
+
// agent.v1 ToolCallDeltaUpdate: keep the nested delta opaque; the pump
|
|
396
|
+
// needs its presence as generation progress, not its display-only content.
|
|
397
|
+
addType(root, "ToolCallDeltaUpdate", [
|
|
398
|
+
{ id: 1, name: "call_id", type: "string" },
|
|
399
|
+
{ id: 2, name: "tool_call_delta", type: "bytes" },
|
|
400
|
+
{ id: 3, name: "model_call_id", type: "string" },
|
|
401
|
+
]);
|
|
392
402
|
// agent.v1 StepStartedUpdate / StepCompletedUpdate: step_id is uint64 (T:4),
|
|
393
403
|
// not string — wrong type made every step_completed frame throw
|
|
394
404
|
// "index out of range" in decodeMessage.
|
|
@@ -399,6 +409,12 @@ export function createMessageTypes() {
|
|
|
399
409
|
{ id: 1, name: "step_id", type: "uint64" },
|
|
400
410
|
{ id: 2, name: "step_duration_ms", type: "int64" },
|
|
401
411
|
]);
|
|
412
|
+
// agent.v1 ToolRequestsListedUpdate — Cursor lists how many tool calls this
|
|
413
|
+
// generation will send so the client can place its step boundary (CLI does
|
|
414
|
+
// not need this; OpenCode's AI SDK step does). Field 27 on InteractionUpdate.
|
|
415
|
+
addType(root, "ToolRequestsListedUpdate", [
|
|
416
|
+
{ id: 1, name: "call_count", type: "uint32" },
|
|
417
|
+
]);
|
|
402
418
|
// InteractionUpdate — the core streaming update message
|
|
403
419
|
addType(root, "InteractionUpdate", [
|
|
404
420
|
{ id: 1, name: "text_delta", type: "TextDeltaUpdate" },
|
|
@@ -408,9 +424,11 @@ export function createMessageTypes() {
|
|
|
408
424
|
{ id: 7, name: "partial_tool_call", type: "PartialToolCall" },
|
|
409
425
|
{ id: 13, name: "heartbeat", type: "Heartbeat" },
|
|
410
426
|
{ id: 14, name: "turn_ended", type: "TurnEnded" },
|
|
427
|
+
{ id: 15, name: "tool_call_delta", type: "ToolCallDeltaUpdate" },
|
|
411
428
|
{ id: 16, name: "step_started", type: "StepStarted" },
|
|
412
429
|
{ id: 17, name: "step_completed", type: "StepCompleted" },
|
|
413
|
-
|
|
430
|
+
{ id: 27, name: "tool_requests_listed", type: "ToolRequestsListedUpdate" },
|
|
431
|
+
], [{ name: "update", fields: ["text_delta", "tool_call_started", "tool_call_completed", "thinking_delta", "partial_tool_call", "heartbeat", "turn_ended", "tool_call_delta", "step_started", "step_completed", "tool_requests_listed"] }]);
|
|
414
432
|
// ── Exec channel ──
|
|
415
433
|
// Field numbers match agent.v1. Extra fields we don't use are still declared
|
|
416
434
|
// so protobufjs doesn't drop them on decode.
|
|
@@ -934,12 +952,25 @@ export function createMessageTypes() {
|
|
|
934
952
|
{ id: 5, name: "tool_name", type: "string" },
|
|
935
953
|
]);
|
|
936
954
|
addType(root, "McpTextContent", [{ id: 1, name: "text", type: "string" }]);
|
|
937
|
-
|
|
955
|
+
// Cursor CLI 2026.10.01 `agent/v1/mcp_exec_pb.js`: "McpImageContent|1 data 12|2 mime_type 9",
|
|
956
|
+
// "McpToolResultContentItem|1 text #0 content|2 image #1 content".
|
|
957
|
+
addType(root, "McpImageContent", [
|
|
958
|
+
{ id: 1, name: "data", type: "bytes" },
|
|
959
|
+
{ id: 2, name: "mime_type", type: "string" },
|
|
960
|
+
]);
|
|
961
|
+
addType(root, "McpToolResultContentItem", [
|
|
962
|
+
{ id: 1, name: "text", type: "McpTextContent" },
|
|
963
|
+
{ id: 2, name: "image", type: "McpImageContent" },
|
|
964
|
+
], [{ name: "content", fields: ["text", "image"] }]);
|
|
938
965
|
addType(root, "McpSuccess", [
|
|
939
966
|
{ id: 1, name: "content", type: "McpToolResultContentItem", repeated: true },
|
|
940
967
|
{ id: 2, name: "is_error", type: "bool" },
|
|
941
968
|
]);
|
|
942
|
-
addType(root, "McpError", [
|
|
969
|
+
addType(root, "McpError", [
|
|
970
|
+
{ id: 1, name: "error", type: "string" },
|
|
971
|
+
// Server-side CallDynamicTool validation uses a title (#1) and detail (#2).
|
|
972
|
+
{ id: 2, name: "detail", type: "string" },
|
|
973
|
+
]);
|
|
943
974
|
addType(root, "McpResult", [
|
|
944
975
|
{ id: 1, name: "success", type: "McpSuccess" },
|
|
945
976
|
{ id: 2, name: "error", type: "McpError" },
|
|
@@ -1062,9 +1093,27 @@ export function createMessageTypes() {
|
|
|
1062
1093
|
{ id: 20, name: "is_working_dir_home_dir", type: "bool" },
|
|
1063
1094
|
{ id: 21, name: "process_working_directory", type: "string" },
|
|
1064
1095
|
]);
|
|
1096
|
+
// Cursor applies a rule by its type: `global` is the CLI's `alwaysApply: true`
|
|
1097
|
+
// (its AGENTS.md / CLAUDE.md default). An untyped rule is only attachable on
|
|
1098
|
+
// request, so its content never reaches the model on its own.
|
|
1099
|
+
addType(root, "CursorRuleTypeGlobal", []);
|
|
1100
|
+
addType(root, "CursorRuleTypeFileGlobs", [
|
|
1101
|
+
{ id: 1, name: "globs", type: "string", repeated: true },
|
|
1102
|
+
]);
|
|
1103
|
+
addType(root, "CursorRuleTypeAgentFetched", [
|
|
1104
|
+
{ id: 1, name: "description", type: "string" },
|
|
1105
|
+
]);
|
|
1106
|
+
addType(root, "CursorRuleTypeManuallyAttached", []);
|
|
1107
|
+
addType(root, "CursorRuleType", [
|
|
1108
|
+
{ id: 1, name: "global", type: "CursorRuleTypeGlobal" },
|
|
1109
|
+
{ id: 2, name: "file_globbed", type: "CursorRuleTypeFileGlobs" },
|
|
1110
|
+
{ id: 3, name: "agent_fetched", type: "CursorRuleTypeAgentFetched" },
|
|
1111
|
+
{ id: 4, name: "manually_attached", type: "CursorRuleTypeManuallyAttached" },
|
|
1112
|
+
], [{ name: "type", fields: ["global", "file_globbed", "agent_fetched", "manually_attached"] }]);
|
|
1065
1113
|
addType(root, "CursorRule", [
|
|
1066
1114
|
{ id: 1, name: "full_path", type: "string" },
|
|
1067
1115
|
{ id: 2, name: "content", type: "string" },
|
|
1116
|
+
{ id: 3, name: "type", type: "CursorRuleType" },
|
|
1068
1117
|
]);
|
|
1069
1118
|
addType(root, "RepositoryIndexingInfo", [
|
|
1070
1119
|
{ id: 1, name: "relative_workspace_path", type: "string" },
|
|
@@ -1151,8 +1200,8 @@ export function createMessageTypes() {
|
|
|
1151
1200
|
{ id: 1, name: "success", type: "RequestContextSuccess" },
|
|
1152
1201
|
]);
|
|
1153
1202
|
// Cursor probes MCP server availability before emitting an MCP-backed tool
|
|
1154
|
-
// call. OpenCode owns those servers, so answer from the
|
|
1155
|
-
//
|
|
1203
|
+
// call. OpenCode owns those servers, so answer from the live host catalog
|
|
1204
|
+
// rather than surfacing this as a user tool.
|
|
1156
1205
|
addType(root, "McpStateExecArgs", [
|
|
1157
1206
|
{ id: 1, name: "server_identifiers", type: "string", repeated: true },
|
|
1158
1207
|
{ id: 2, name: "kick_only", type: "bool" },
|
|
@@ -1329,13 +1378,26 @@ export function createMessageTypes() {
|
|
|
1329
1378
|
addType(root, "SelectedContext", [
|
|
1330
1379
|
{ id: 1, name: "selected_images", type: "SelectedImage", repeated: true },
|
|
1331
1380
|
]);
|
|
1381
|
+
// agent.v1.AgentMode — the client's current mode, sent on every user message.
|
|
1382
|
+
root.add(new protobuf.Enum("AgentMode", {
|
|
1383
|
+
AGENT_MODE_UNSPECIFIED: 0,
|
|
1384
|
+
AGENT_MODE_AGENT: 1,
|
|
1385
|
+
AGENT_MODE_ASK: 2,
|
|
1386
|
+
AGENT_MODE_PLAN: 3,
|
|
1387
|
+
AGENT_MODE_DEBUG: 4,
|
|
1388
|
+
AGENT_MODE_TRIAGE: 5,
|
|
1389
|
+
AGENT_MODE_PROJECT: 6,
|
|
1390
|
+
AGENT_MODE_MULTITASK: 7,
|
|
1391
|
+
AGENT_MODE_CUSTOM: 8,
|
|
1392
|
+
}));
|
|
1332
1393
|
addType(root, "UserMessage", [
|
|
1333
1394
|
{ id: 1, name: "text", type: "string" },
|
|
1334
1395
|
{ id: 2, name: "message_id", type: "string" },
|
|
1335
1396
|
{ id: 3, name: "selected_context", type: "SelectedContext" },
|
|
1397
|
+
{ id: 4, name: "mode", type: "AgentMode" },
|
|
1336
1398
|
]);
|
|
1337
|
-
// RequestContext — UserMessageAction #2.
|
|
1338
|
-
//
|
|
1399
|
+
// RequestContext — UserMessageAction #2. Slim mcp_meta_tool_options names
|
|
1400
|
+
// the advertised catalog; AgentRunRequest.mcp_tools (#4) is empty on real turns.
|
|
1339
1401
|
addType(root, "RequestContext", [
|
|
1340
1402
|
{ id: 2, name: "rules", type: "CursorRule", repeated: true },
|
|
1341
1403
|
{ id: 4, name: "env", type: "RequestContextEnv" },
|
|
@@ -1372,7 +1434,8 @@ export function createMessageTypes() {
|
|
|
1372
1434
|
{ id: 2, name: "request_context", type: "RequestContext" },
|
|
1373
1435
|
]);
|
|
1374
1436
|
addType(root, "CancelAction", [
|
|
1375
|
-
|
|
1437
|
+
// Cursor CLI CancelAction field #1 is the interruption reason, not an id.
|
|
1438
|
+
{ id: 1, name: "reason", type: "string" },
|
|
1376
1439
|
]);
|
|
1377
1440
|
// Deferred answers for an AskQuestion the client already replied to with
|
|
1378
1441
|
// `async`. `original_args` is kept as raw bytes so the exact AskQuestionArgs
|
|
@@ -1383,11 +1446,23 @@ export function createMessageTypes() {
|
|
|
1383
1446
|
{ id: 2, name: "original_args", type: "bytes" },
|
|
1384
1447
|
{ id: 3, name: "result", type: "AskQuestionResult" },
|
|
1385
1448
|
]);
|
|
1449
|
+
// Cursor CLI 2026.10.01 descriptors: system context is
|
|
1450
|
+
// injected into the current Run without submitting another user turn.
|
|
1451
|
+
addType(root, "SystemContextInjection", [
|
|
1452
|
+
{ id: 1, name: "producer", type: "string" },
|
|
1453
|
+
{ id: 2, name: "content", type: "string" },
|
|
1454
|
+
]);
|
|
1455
|
+
addType(root, "InjectContextAction", [
|
|
1456
|
+
{ id: 1, name: "injection_id", type: "string" },
|
|
1457
|
+
{ id: 2, name: "expected_run_id", type: "string" },
|
|
1458
|
+
{ id: 4, name: "system_context", type: "SystemContextInjection" },
|
|
1459
|
+
]);
|
|
1386
1460
|
addType(root, "ConversationAction", [
|
|
1387
1461
|
{ id: 1, name: "user_message_action", type: "UserMessageAction" },
|
|
1388
1462
|
{ id: 2, name: "resume_action", type: "ResumeAction" },
|
|
1389
1463
|
{ id: 3, name: "cancel_action", type: "CancelAction" },
|
|
1390
1464
|
{ id: 8, name: "async_ask_question_completion_action", type: "AsyncAskQuestionCompletionAction" },
|
|
1465
|
+
{ id: 19, name: "inject_context_action", type: "InjectContextAction" },
|
|
1391
1466
|
], [{
|
|
1392
1467
|
name: "action",
|
|
1393
1468
|
fields: [
|
|
@@ -1395,6 +1470,7 @@ export function createMessageTypes() {
|
|
|
1395
1470
|
"resume_action",
|
|
1396
1471
|
"cancel_action",
|
|
1397
1472
|
"async_ask_question_completion_action",
|
|
1473
|
+
"inject_context_action",
|
|
1398
1474
|
],
|
|
1399
1475
|
}]);
|
|
1400
1476
|
// Seed ConversationStateStructure for turn 1 (system prompt as JSON strings
|
|
@@ -1683,11 +1759,11 @@ export function createMessageTypes() {
|
|
|
1683
1759
|
{ id: 2, name: "value", type: "string" },
|
|
1684
1760
|
]);
|
|
1685
1761
|
addType(root, "AvailableModelVariant", [
|
|
1686
|
-
{ id: 1, name: "
|
|
1687
|
-
{ id: 2, name: "
|
|
1688
|
-
{ id: 3, name: "
|
|
1689
|
-
{ id: 4, name: "
|
|
1690
|
-
{ id: 5, name: "
|
|
1762
|
+
{ id: 1, name: "parameter_values", type: "AvailableModelParameterValue", repeated: true },
|
|
1763
|
+
{ id: 2, name: "display_name", type: "string" },
|
|
1764
|
+
{ id: 3, name: "is_max_mode", type: "bool" },
|
|
1765
|
+
{ id: 4, name: "is_default_max_config", type: "bool" },
|
|
1766
|
+
{ id: 5, name: "is_default_non_max_config", type: "bool" },
|
|
1691
1767
|
]);
|
|
1692
1768
|
addType(root, "AvailableModelEntry", [
|
|
1693
1769
|
{ id: 1, name: "name", type: "string" },
|
|
@@ -1696,14 +1772,16 @@ export function createMessageTypes() {
|
|
|
1696
1772
|
{ id: 9, name: "supports_thinking", type: "bool" },
|
|
1697
1773
|
{ id: 10, name: "supports_images", type: "bool" },
|
|
1698
1774
|
{ id: 14, name: "supports_max_mode", type: "bool" },
|
|
1699
|
-
{ id: 15, name: "context_token_limit", type: "
|
|
1775
|
+
{ id: 15, name: "context_token_limit", type: "int32" },
|
|
1776
|
+
{ id: 16, name: "context_token_limit_for_max_mode", type: "int32" },
|
|
1700
1777
|
{ id: 17, name: "client_display_name", type: "string" },
|
|
1701
1778
|
{ id: 18, name: "server_model_name", type: "string" },
|
|
1702
1779
|
{ id: 29, name: "parameter_definitions", type: "AvailableModelParameterDefinition", repeated: true },
|
|
1703
1780
|
{ id: 30, name: "variants", type: "AvailableModelVariant", repeated: true },
|
|
1704
1781
|
]);
|
|
1705
1782
|
addType(root, "AvailableModelsResponse", [
|
|
1706
|
-
{ id: 1, name: "
|
|
1783
|
+
{ id: 1, name: "model_names", type: "string", repeated: true },
|
|
1784
|
+
{ id: 2, name: "models", type: "AvailableModelEntry", repeated: true },
|
|
1707
1785
|
]);
|
|
1708
1786
|
return root;
|
|
1709
1787
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { OpencodeToolDef } from "./tools.js";
|
|
2
2
|
import type { CursorImageInput } from "../image-input.js";
|
|
3
3
|
export type SeedHistoryMessage = {
|
|
4
4
|
role: "system" | "user" | "assistant";
|
|
@@ -11,7 +11,6 @@ export type RunRequestInput = {
|
|
|
11
11
|
conversationId: string;
|
|
12
12
|
/** Stable parent group; unlike conversationId, this survives compaction/rebase. */
|
|
13
13
|
conversationGroupId?: string;
|
|
14
|
-
systemPrompt?: string;
|
|
15
14
|
/**
|
|
16
15
|
* Prior chat turns for a seed ConversationStateStructure (no checkpoint).
|
|
17
16
|
* Tool outputs, when required for compaction/recovery, are represented as
|
|
@@ -22,7 +21,7 @@ export type RunRequestInput = {
|
|
|
22
21
|
* Opaque ConversationStateStructure bytes from the last
|
|
23
22
|
* conversation_checkpoint_update for this conversation_id. When set, echoed
|
|
24
23
|
* as AgentRunRequest.conversation_state (CLI parity). When absent, a seed
|
|
25
|
-
* state
|
|
24
|
+
* state carrying only `history` (or empty) is built for turn 1.
|
|
26
25
|
*/
|
|
27
26
|
conversationState?: Uint8Array;
|
|
28
27
|
parameterValues?: Array<{
|
|
@@ -38,23 +37,31 @@ export type RunRequestInput = {
|
|
|
38
37
|
requestContext?: Record<string, unknown>;
|
|
39
38
|
/** Resume the supplied checkpoint instead of submitting another user turn. */
|
|
40
39
|
action?: "user" | "resume";
|
|
40
|
+
/**
|
|
41
|
+
* Cursor mode of this user turn (`UserMessage.mode`, an `agent.v1.AgentMode`
|
|
42
|
+
* value). Cursor CLI sends its current mode on every user message.
|
|
43
|
+
*/
|
|
44
|
+
mode?: number;
|
|
41
45
|
};
|
|
42
46
|
/**
|
|
43
47
|
* Seed ConversationStateStructure for the first turn (no checkpoint yet).
|
|
44
48
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
49
|
+
* After the first checkpoint arrives we stop inventing state and echo the
|
|
50
|
+
* server's opaque structure instead (CLI behavior). Compaction resets and
|
|
51
|
+
* rebases also use this seed, with `history` carrying OpenCode's prompt turns
|
|
52
|
+
* so Cursor can continue without the old checkpoint.
|
|
48
53
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
54
|
+
* No `system` entry is seeded: Cursor does not follow a client-authored
|
|
55
|
+
* `system` root message, and neither Cursor client writes
|
|
56
|
+
* `root_prompt_messages_json`. Host system context travels as the
|
|
57
|
+
* system-instructions rule in RequestContext (`systemInstructionsRule`), so
|
|
58
|
+
* `system` history entries are dropped here rather than duplicated.
|
|
51
59
|
*
|
|
52
60
|
* We deliberately do NOT use `AgentRunRequest.custom_system_prompt` (#8): that
|
|
53
61
|
* field is the internal `--system-prompt` CLI override and the server rejects
|
|
54
62
|
* it for normal accounts.
|
|
55
63
|
*/
|
|
56
64
|
export declare function buildSeedConversationState(input?: {
|
|
57
|
-
systemPrompt?: string;
|
|
58
65
|
history?: SeedHistoryMessage[];
|
|
59
66
|
}): Uint8Array;
|
|
60
67
|
/**
|