cursor-opencode-provider 0.8.0 → 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.
Files changed (132) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/README.md +230 -44
  3. package/SECURITY.md +60 -12
  4. package/dist/activity.d.ts +73 -1
  5. package/dist/activity.js +231 -5
  6. package/dist/agent-url.js +37 -9
  7. package/dist/auth-renewal.d.ts +1 -1
  8. package/dist/auth-renewal.js +58 -26
  9. package/dist/billing.d.ts +105 -0
  10. package/dist/billing.js +212 -0
  11. package/dist/classic-tool-results.d.ts +1 -0
  12. package/dist/classic-tool-results.js +42 -0
  13. package/dist/context/build.d.ts +23 -4
  14. package/dist/context/build.js +65 -10
  15. package/dist/context/dynamic-catalog.js +6 -1
  16. package/dist/context/env.js +27 -3
  17. package/dist/context/epoch.js +9 -5
  18. package/dist/context/frozen.js +6 -2
  19. package/dist/context/git.d.ts +5 -2
  20. package/dist/context/git.js +17 -7
  21. package/dist/context/index.d.ts +2 -0
  22. package/dist/context/index.js +2 -0
  23. package/dist/context/paths.d.ts +45 -18
  24. package/dist/context/paths.js +72 -22
  25. package/dist/context/skills-bridge.d.ts +22 -0
  26. package/dist/context/skills-bridge.js +20 -0
  27. package/dist/context/skills.d.ts +34 -0
  28. package/dist/context/skills.js +211 -0
  29. package/dist/debug.js +13 -2
  30. package/dist/early-tool-rows.d.ts +60 -0
  31. package/dist/early-tool-rows.js +96 -0
  32. package/dist/errors.d.ts +49 -7
  33. package/dist/errors.js +163 -8
  34. package/dist/host-agent-mode.d.ts +53 -4
  35. package/dist/host-agent-mode.js +117 -13
  36. package/dist/host-interrupt.d.ts +12 -0
  37. package/dist/host-interrupt.js +18 -0
  38. package/dist/host-plan-file.d.ts +28 -0
  39. package/dist/host-plan-file.js +76 -0
  40. package/dist/host-steer.d.ts +71 -0
  41. package/dist/host-steer.js +232 -0
  42. package/dist/image-input.d.ts +18 -0
  43. package/dist/image-input.js +122 -30
  44. package/dist/image-save.d.ts +10 -2
  45. package/dist/image-save.js +33 -21
  46. package/dist/index.d.ts +2 -2
  47. package/dist/language-model.d.ts +149 -30
  48. package/dist/language-model.js +3127 -673
  49. package/dist/lost-request.d.ts +39 -0
  50. package/dist/lost-request.js +111 -0
  51. package/dist/model-config.d.ts +12 -1
  52. package/dist/model-config.js +78 -30
  53. package/dist/models.d.ts +26 -3
  54. package/dist/models.js +101 -45
  55. package/dist/opencode2/catalog.d.ts +1 -0
  56. package/dist/opencode2/catalog.js +2 -0
  57. package/dist/opencode2/image-save-tool.d.ts +8 -0
  58. package/dist/opencode2/image-save-tool.js +84 -0
  59. package/dist/opencode2/tool-results.d.ts +18 -0
  60. package/dist/opencode2/tool-results.js +66 -0
  61. package/dist/opencode2/types.d.ts +15 -0
  62. package/dist/parallel-step.d.ts +65 -0
  63. package/dist/parallel-step.js +102 -0
  64. package/dist/plugin-opencode2.js +233 -65
  65. package/dist/plugin.js +97 -23
  66. package/dist/pricing-data.d.ts +40 -3
  67. package/dist/pricing-data.js +40 -3
  68. package/dist/pricing.d.ts +1 -1
  69. package/dist/pricing.js +7 -4
  70. package/dist/process-shared.d.ts +14 -0
  71. package/dist/process-shared.js +18 -0
  72. package/dist/protocol/apply-patch.d.ts +25 -13
  73. package/dist/protocol/apply-patch.js +79 -30
  74. package/dist/protocol/ask-question.d.ts +12 -1
  75. package/dist/protocol/ask-question.js +77 -3
  76. package/dist/protocol/blob-store.d.ts +5 -1
  77. package/dist/protocol/blob-store.js +10 -6
  78. package/dist/protocol/client-version.js +6 -4
  79. package/dist/protocol/conversation-cache-header.d.ts +23 -0
  80. package/dist/protocol/conversation-cache-header.js +149 -0
  81. package/dist/protocol/conversation-persistence.d.ts +27 -1
  82. package/dist/protocol/conversation-persistence.js +628 -126
  83. package/dist/protocol/conversation-state.d.ts +8 -1
  84. package/dist/protocol/conversation-state.js +28 -7
  85. package/dist/protocol/create-plan.d.ts +47 -25
  86. package/dist/protocol/create-plan.js +33 -23
  87. package/dist/protocol/host-notes.d.ts +31 -0
  88. package/dist/protocol/host-notes.js +85 -0
  89. package/dist/protocol/interactions.d.ts +26 -17
  90. package/dist/protocol/interactions.js +80 -31
  91. package/dist/protocol/messages.js +164 -21
  92. package/dist/protocol/request.d.ts +40 -19
  93. package/dist/protocol/request.js +55 -28
  94. package/dist/protocol/sent-history-images.d.ts +49 -0
  95. package/dist/protocol/sent-history-images.js +102 -0
  96. package/dist/protocol/switch-mode.d.ts +66 -27
  97. package/dist/protocol/switch-mode.js +147 -73
  98. package/dist/protocol/token-details.d.ts +2 -0
  99. package/dist/protocol/token-details.js +11 -1
  100. package/dist/protocol/tool-call-bridge.d.ts +6 -0
  101. package/dist/protocol/tool-call-bridge.js +24 -0
  102. package/dist/protocol/tools.d.ts +90 -9
  103. package/dist/protocol/tools.js +543 -151
  104. package/dist/protocol/turn-provenance.d.ts +19 -4
  105. package/dist/protocol/turn-provenance.js +77 -20
  106. package/dist/replay-safety.d.ts +22 -4
  107. package/dist/replay-safety.js +86 -27
  108. package/dist/retry-budget.d.ts +21 -0
  109. package/dist/retry-budget.js +94 -0
  110. package/dist/runtime-files.d.ts +23 -0
  111. package/dist/runtime-files.js +581 -0
  112. package/dist/session.d.ts +129 -10
  113. package/dist/session.js +198 -35
  114. package/dist/shared.d.ts +1 -1
  115. package/dist/shared.js +1 -1
  116. package/dist/shell-timeout.d.ts +54 -10
  117. package/dist/shell-timeout.js +362 -92
  118. package/dist/stream-write.d.ts +20 -0
  119. package/dist/stream-write.js +87 -0
  120. package/dist/terminal-runner.d.ts +15 -0
  121. package/dist/terminal-runner.js +528 -0
  122. package/dist/todo-store.d.ts +2 -0
  123. package/dist/todo-store.js +19 -0
  124. package/dist/transport/connect.d.ts +10 -1
  125. package/dist/transport/connect.js +124 -27
  126. package/dist/usage.d.ts +37 -17
  127. package/dist/usage.js +85 -34
  128. package/package.json +5 -3
  129. package/dist/plan-execution-kickoff.d.ts +0 -57
  130. package/dist/plan-execution-kickoff.js +0 -186
  131. package/dist/protocol/thinking.d.ts +0 -15
  132. package/dist/protocol/thinking.js +0 -17
@@ -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, createPlanApprovalQuestionInput, decodeCreatePlanQuery, renderPlanReviewMessage, resolveCreatePlanBridge, writeOpencodePlanFile, } from "./create-plan.js";
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-calculated plan file via hostPlansDir (project-config
80
- * `plans/` in a git worktree, else host global data/plans); empty args still
81
- * get the CLI empty-`plan_uri` success ack
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.kind === "native" ? bridge.toolName : "question",
249
+ toolName: bridge.toolName,
247
250
  },
248
- // Keep Cursor waiting until the host tool returns, matching CLI blocking on
249
- // the mode-switch approval prompt.
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-stage tool owns the write and the approval prompt together.
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. Args-only wrappers are enough to bridge into
32
- // OpenCode; result payloads are ignored on decode.
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", [{ id: 1, name: "args", type: "McpArgs" }]);
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 },
@@ -132,6 +135,7 @@ export function createMessageTypes() {
132
135
  { id: 4, name: "model", type: "string" },
133
136
  { id: 5, name: "resume", type: "string" },
134
137
  { id: 6, name: "agent_id", type: "string" },
138
+ { id: 7, name: "attachments", type: "string", repeated: true },
135
139
  ]);
136
140
  addType(root, "TaskToolCall", [{ id: 1, name: "args", type: "TaskToolArgs" }]);
137
141
  // Native subagent execution is separate from the display-only TaskToolCall.
@@ -389,6 +393,13 @@ export function createMessageTypes() {
389
393
  { id: 3, name: "args_text_delta", type: "string" },
390
394
  { id: 4, name: "model_call_id", type: "string" },
391
395
  ]);
396
+ // agent.v1 ToolCallDeltaUpdate: keep the nested delta opaque; the pump
397
+ // needs its presence as generation progress, not its display-only content.
398
+ addType(root, "ToolCallDeltaUpdate", [
399
+ { id: 1, name: "call_id", type: "string" },
400
+ { id: 2, name: "tool_call_delta", type: "bytes" },
401
+ { id: 3, name: "model_call_id", type: "string" },
402
+ ]);
392
403
  // agent.v1 StepStartedUpdate / StepCompletedUpdate: step_id is uint64 (T:4),
393
404
  // not string — wrong type made every step_completed frame throw
394
405
  // "index out of range" in decodeMessage.
@@ -399,6 +410,12 @@ export function createMessageTypes() {
399
410
  { id: 1, name: "step_id", type: "uint64" },
400
411
  { id: 2, name: "step_duration_ms", type: "int64" },
401
412
  ]);
413
+ // agent.v1 ToolRequestsListedUpdate — Cursor lists how many tool calls this
414
+ // generation will send so the client can place its step boundary (CLI does
415
+ // not need this; OpenCode's AI SDK step does). Field 27 on InteractionUpdate.
416
+ addType(root, "ToolRequestsListedUpdate", [
417
+ { id: 1, name: "call_count", type: "uint32" },
418
+ ]);
402
419
  // InteractionUpdate — the core streaming update message
403
420
  addType(root, "InteractionUpdate", [
404
421
  { id: 1, name: "text_delta", type: "TextDeltaUpdate" },
@@ -408,9 +425,36 @@ export function createMessageTypes() {
408
425
  { id: 7, name: "partial_tool_call", type: "PartialToolCall" },
409
426
  { id: 13, name: "heartbeat", type: "Heartbeat" },
410
427
  { id: 14, name: "turn_ended", type: "TurnEnded" },
428
+ { id: 15, name: "tool_call_delta", type: "ToolCallDeltaUpdate" },
411
429
  { id: 16, name: "step_started", type: "StepStarted" },
412
430
  { id: 17, name: "step_completed", type: "StepCompleted" },
413
- ], [{ name: "update", fields: ["text_delta", "tool_call_started", "tool_call_completed", "thinking_delta", "partial_tool_call", "heartbeat", "turn_ended", "step_started", "step_completed"] }]);
431
+ { id: 23, name: "context_injection_state", type: "ContextInjectionStateUpdate" },
432
+ { id: 27, name: "tool_requests_listed", type: "ToolRequestsListedUpdate" },
433
+ ], [{ 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", "context_injection_state", "tool_requests_listed"] }]);
434
+ // Server status for an `inject_context_action` (InteractionUpdate #23).
435
+ // Cursor CLI only tracks it to show queued steer delivery; it carries no output.
436
+ addType(root, "ContextInjectionQueued", []);
437
+ addType(root, "ContextInjectionQueuedForNextTurn", []);
438
+ addType(root, "ContextInjectionCancelled", []);
439
+ addType(root, "ContextInjectionDelivered", [
440
+ { id: 1, name: "step", type: "int32" },
441
+ { id: 2, name: "delivery_batch_id", type: "string" },
442
+ { id: 3, name: "delivered_at_ms", type: "int64" },
443
+ ]);
444
+ addType(root, "ContextInjectionRejected", [
445
+ { id: 1, name: "reason", type: "string" },
446
+ ]);
447
+ addType(root, "ContextInjectionState", [
448
+ { id: 1, name: "queued", type: "ContextInjectionQueued" },
449
+ { id: 2, name: "delivered", type: "ContextInjectionDelivered" },
450
+ { id: 3, name: "queued_for_next_turn", type: "ContextInjectionQueuedForNextTurn" },
451
+ { id: 4, name: "cancelled", type: "ContextInjectionCancelled" },
452
+ { id: 5, name: "rejected", type: "ContextInjectionRejected" },
453
+ ], [{ name: "state", fields: ["queued", "delivered", "queued_for_next_turn", "cancelled", "rejected"] }]);
454
+ addType(root, "ContextInjectionStateUpdate", [
455
+ { id: 1, name: "injection_id", type: "string" },
456
+ { id: 2, name: "state", type: "ContextInjectionState" },
457
+ ]);
414
458
  // ── Exec channel ──
415
459
  // Field numbers match agent.v1. Extra fields we don't use are still declared
416
460
  // so protobufjs doesn't drop them on decode.
@@ -447,16 +491,21 @@ export function createMessageTypes() {
447
491
  { id: 1, name: "path", type: "string" },
448
492
  { id: 2, name: "reason", type: "string" },
449
493
  ]);
494
+ addType(root, "ReadRejected", [
495
+ { id: 1, name: "path", type: "string" },
496
+ { id: 2, name: "reason", type: "string" },
497
+ ]);
450
498
  addType(root, "ReadResult", [
451
499
  { id: 1, name: "success", type: "ReadSuccess" },
452
500
  { id: 2, name: "error", type: "ReadError" },
501
+ { id: 3, name: "rejected", type: "ReadRejected" },
453
502
  { id: 4, name: "file_not_found", type: "ReadFileNotFound" },
454
503
  { id: 5, name: "permission_denied", type: "ReadPermissionDenied" },
455
504
  { id: 6, name: "invalid_file", type: "ReadInvalidFile" },
456
505
  ], [
457
506
  {
458
507
  name: "result",
459
- fields: ["success", "error", "file_not_found", "permission_denied", "invalid_file"],
508
+ fields: ["success", "error", "rejected", "file_not_found", "permission_denied", "invalid_file"],
460
509
  },
461
510
  ]);
462
511
  addType(root, "GrepArgs", [
@@ -536,11 +585,16 @@ export function createMessageTypes() {
536
585
  { id: 4, name: "error", type: "string" },
537
586
  { id: 5, name: "is_readonly", type: "bool" },
538
587
  ]);
588
+ addType(root, "WriteRejected", [
589
+ { id: 1, name: "path", type: "string" },
590
+ { id: 2, name: "reason", type: "string" },
591
+ ]);
539
592
  addType(root, "WriteResult", [
540
593
  { id: 1, name: "success", type: "WriteSuccess" },
541
594
  { id: 3, name: "permission_denied", type: "WritePermissionDenied" },
542
595
  { id: 5, name: "error", type: "WriteError" },
543
- ], [{ name: "result", fields: ["success", "permission_denied", "error"] }]);
596
+ { id: 6, name: "rejected", type: "WriteRejected" },
597
+ ], [{ name: "result", fields: ["success", "permission_denied", "error", "rejected"] }]);
544
598
  // agent.v1 Pi exec results. Cursor places Pi requests at ExecServerMessage
545
599
  // fields #45..#51 and their corresponding ExecClientMessage results at
546
600
  // #46..#52 (the result fields are deliberately offset by one).
@@ -616,10 +670,21 @@ export function createMessageTypes() {
616
670
  { id: 1, name: "path", type: "string" },
617
671
  { id: 2, name: "error", type: "string" },
618
672
  ]);
673
+ addType(root, "DeletePermissionDenied", [
674
+ { id: 1, name: "path", type: "string" },
675
+ { id: 2, name: "client_visible_error", type: "string" },
676
+ { id: 3, name: "is_readonly", type: "bool" },
677
+ ]);
678
+ addType(root, "DeleteRejected", [
679
+ { id: 1, name: "path", type: "string" },
680
+ { id: 2, name: "reason", type: "string" },
681
+ ]);
619
682
  addType(root, "DeleteResult", [
620
683
  { id: 1, name: "success", type: "DeleteSuccess" },
684
+ { id: 4, name: "permission_denied", type: "DeletePermissionDenied" },
685
+ { id: 6, name: "rejected", type: "DeleteRejected" },
621
686
  { id: 7, name: "error", type: "DeleteError" },
622
- ], [{ name: "result", fields: ["success", "error"] }]);
687
+ ], [{ name: "result", fields: ["success", "permission_denied", "rejected", "error"] }]);
623
688
  addType(root, "LsArgs", [
624
689
  { id: 1, name: "path", type: "string" },
625
690
  { id: 2, name: "ignore", type: "string", repeated: true },
@@ -639,10 +704,15 @@ export function createMessageTypes() {
639
704
  { id: 1, name: "path", type: "string" },
640
705
  { id: 2, name: "error", type: "string" },
641
706
  ]);
707
+ addType(root, "LsRejected", [
708
+ { id: 1, name: "path", type: "string" },
709
+ { id: 2, name: "reason", type: "string" },
710
+ ]);
642
711
  addType(root, "LsResult", [
643
712
  { id: 1, name: "success", type: "LsSuccess" },
644
713
  { id: 2, name: "error", type: "LsError" },
645
- ], [{ name: "result", fields: ["success", "error"] }]);
714
+ { id: 3, name: "rejected", type: "LsRejected" },
715
+ ], [{ name: "result", fields: ["success", "error", "rejected"] }]);
646
716
  addType(root, "ShellArgs", [
647
717
  { id: 1, name: "command", type: "string" },
648
718
  { id: 2, name: "working_directory", type: "string" },
@@ -652,6 +722,7 @@ export function createMessageTypes() {
652
722
  // foreground cancellation deadline from a soft background handoff.
653
723
  { id: 13, name: "timeout_behavior", type: "uint32" },
654
724
  { id: 14, name: "hard_timeout", type: "uint32" },
725
+ { id: 15, name: "description", type: "string" },
655
726
  ]);
656
727
  addType(root, "ShellStreamStart", []); // optional SandboxPolicy only; empty is valid
657
728
  addType(root, "ShellStreamStdout", [{ id: 1, name: "data", type: "string" }]);
@@ -722,9 +793,10 @@ export function createMessageTypes() {
722
793
  { id: 2, name: "failure", type: "ShellFailure" },
723
794
  { id: 3, name: "timeout", type: "ShellTimeout" },
724
795
  { id: 4, name: "rejected", type: "ShellRejected" },
796
+ { id: 7, name: "permission_denied", type: "ShellPermissionDenied" },
725
797
  { id: 102, name: "is_background", type: "bool" },
726
798
  { id: 104, name: "pid", type: "uint32" },
727
- ], [{ name: "result", fields: ["success", "failure", "timeout", "rejected"] }]);
799
+ ], [{ name: "result", fields: ["success", "failure", "timeout", "rejected", "permission_denied"] }]);
728
800
  addType(root, "DiagnosticsError", [
729
801
  { id: 1, name: "path", type: "string" },
730
802
  { id: 2, name: "error", type: "string" },
@@ -906,6 +978,15 @@ export function createMessageTypes() {
906
978
  { id: 3, name: "rejected", type: "ShellRejected" },
907
979
  { id: 4, name: "permission_denied", type: "ShellPermissionDenied" },
908
980
  ], [{ name: "result", fields: ["success", "error", "rejected", "permission_denied"] }]);
981
+ // Cursor CLI postToolUse hook context: ExecClientMessage #45 on a unary
982
+ // result, ShellStream #8 on a shell stream (agent.v1 hook_additional_context).
983
+ addType(root, "HookAdditionalContext", [
984
+ { id: 1, name: "hook_event_name", type: "string" },
985
+ { id: 2, name: "content", type: "string" },
986
+ ]);
987
+ addType(root, "ShellStreamHookContext", [
988
+ { id: 1, name: "hook_additional_contexts", type: "HookAdditionalContext", repeated: true },
989
+ ]);
909
990
  addType(root, "ShellStream", [
910
991
  { id: 1, name: "stdout", type: "ShellStreamStdout" },
911
992
  { id: 2, name: "stderr", type: "ShellStreamStderr" },
@@ -914,7 +995,8 @@ export function createMessageTypes() {
914
995
  { id: 5, name: "rejected", type: "ShellRejected" },
915
996
  { id: 6, name: "permission_denied", type: "ShellPermissionDenied" },
916
997
  { id: 7, name: "backgrounded", type: "ShellStreamBackgrounded" },
917
- ], [{ name: "event", fields: ["stdout", "stderr", "exit", "start", "rejected", "permission_denied", "backgrounded"] }]);
998
+ { id: 8, name: "hook_context", type: "ShellStreamHookContext" },
999
+ ], [{ name: "event", fields: ["stdout", "stderr", "exit", "start", "rejected", "permission_denied", "backgrounded", "hook_context"] }]);
918
1000
  addType(root, "GlobArgs", [
919
1001
  { id: 1, name: "target_directory", type: "string" },
920
1002
  { id: 2, name: "glob_pattern", type: "string" },
@@ -934,16 +1016,39 @@ export function createMessageTypes() {
934
1016
  { id: 5, name: "tool_name", type: "string" },
935
1017
  ]);
936
1018
  addType(root, "McpTextContent", [{ id: 1, name: "text", type: "string" }]);
937
- addType(root, "McpToolResultContentItem", [{ id: 1, name: "text", type: "McpTextContent" }], [{ name: "content", fields: ["text"] }]);
1019
+ // Cursor CLI 2026.10.01 `agent/v1/mcp_exec_pb.js`: "McpImageContent|1 data 12|2 mime_type 9",
1020
+ // "McpToolResultContentItem|1 text #0 content|2 image #1 content".
1021
+ addType(root, "McpImageContent", [
1022
+ { id: 1, name: "data", type: "bytes" },
1023
+ { id: 2, name: "mime_type", type: "string" },
1024
+ ]);
1025
+ addType(root, "McpToolResultContentItem", [
1026
+ { id: 1, name: "text", type: "McpTextContent" },
1027
+ { id: 2, name: "image", type: "McpImageContent" },
1028
+ ], [{ name: "content", fields: ["text", "image"] }]);
1029
+ addType(root, "McpRejected", [
1030
+ { id: 1, name: "reason", type: "string" },
1031
+ { id: 2, name: "is_readonly", type: "bool" },
1032
+ ]);
1033
+ addType(root, "McpPermissionDenied", [
1034
+ { id: 1, name: "error", type: "string" },
1035
+ { id: 2, name: "is_readonly", type: "bool" },
1036
+ ]);
938
1037
  addType(root, "McpSuccess", [
939
1038
  { id: 1, name: "content", type: "McpToolResultContentItem", repeated: true },
940
1039
  { id: 2, name: "is_error", type: "bool" },
941
1040
  ]);
942
- addType(root, "McpError", [{ id: 1, name: "error", type: "string" }]);
1041
+ addType(root, "McpError", [
1042
+ { id: 1, name: "error", type: "string" },
1043
+ // Server-side CallDynamicTool validation uses a title (#1) and detail (#2).
1044
+ { id: 2, name: "detail", type: "string" },
1045
+ ]);
943
1046
  addType(root, "McpResult", [
944
1047
  { id: 1, name: "success", type: "McpSuccess" },
945
1048
  { id: 2, name: "error", type: "McpError" },
946
- ], [{ name: "result", fields: ["success", "error"] }]);
1049
+ { id: 3, name: "rejected", type: "McpRejected" },
1050
+ { id: 4, name: "permission_denied", type: "McpPermissionDenied" },
1051
+ ], [{ name: "result", fields: ["success", "error", "rejected", "permission_denied"] }]);
947
1052
  // McpToolDefinition — agent.v1 shape for RequestContext.tools (#7) and
948
1053
  // AgentRunRequest.mcp_tools (#4). Defined early so RequestContext* can
949
1054
  // reference it.
@@ -1228,6 +1333,7 @@ export function createMessageTypes() {
1228
1333
  { id: 49, name: "pi_grep_args", type: "PiGrepToolArgs" },
1229
1334
  { id: 50, name: "pi_find_args", type: "PiFindToolArgs" },
1230
1335
  { id: 51, name: "pi_ls_args", type: "PiLsToolArgs" },
1336
+ { id: 55, name: "accept_hook_additional_contexts", type: "bool" },
1231
1337
  ], [{ name: "args", fields: [
1232
1338
  "shell_args", "write_args", "delete_args", "grep_args", "read_args", "ls_args",
1233
1339
  "request_context_args", "mcp_args", "shell_stream_args", "background_shell_spawn_args",
@@ -1241,6 +1347,7 @@ export function createMessageTypes() {
1241
1347
  { id: 1, name: "id", type: "uint32" },
1242
1348
  { id: 15, name: "exec_id", type: "string" },
1243
1349
  { id: 39, name: "local_execution_time_ms", type: "uint64" },
1350
+ { id: 45, name: "hook_additional_contexts", type: "HookAdditionalContext", repeated: true },
1244
1351
  { id: 2, name: "shell_result", type: "ShellResult" },
1245
1352
  { id: 3, name: "write_result", type: "WriteResult" },
1246
1353
  { id: 4, name: "delete_result", type: "DeleteResult" },
@@ -1347,10 +1454,23 @@ export function createMessageTypes() {
1347
1454
  addType(root, "SelectedContext", [
1348
1455
  { id: 1, name: "selected_images", type: "SelectedImage", repeated: true },
1349
1456
  ]);
1457
+ // agent.v1.AgentMode — the client's current mode, sent on every user message.
1458
+ root.add(new protobuf.Enum("AgentMode", {
1459
+ AGENT_MODE_UNSPECIFIED: 0,
1460
+ AGENT_MODE_AGENT: 1,
1461
+ AGENT_MODE_ASK: 2,
1462
+ AGENT_MODE_PLAN: 3,
1463
+ AGENT_MODE_DEBUG: 4,
1464
+ AGENT_MODE_TRIAGE: 5,
1465
+ AGENT_MODE_PROJECT: 6,
1466
+ AGENT_MODE_MULTITASK: 7,
1467
+ AGENT_MODE_CUSTOM: 8,
1468
+ }));
1350
1469
  addType(root, "UserMessage", [
1351
1470
  { id: 1, name: "text", type: "string" },
1352
1471
  { id: 2, name: "message_id", type: "string" },
1353
1472
  { id: 3, name: "selected_context", type: "SelectedContext" },
1473
+ { id: 4, name: "mode", type: "AgentMode" },
1354
1474
  ]);
1355
1475
  // RequestContext — UserMessageAction #2. Slim mcp_meta_tool_options names
1356
1476
  // the advertised catalog; AgentRunRequest.mcp_tools (#4) is empty on real turns.
@@ -1390,7 +1510,8 @@ export function createMessageTypes() {
1390
1510
  { id: 2, name: "request_context", type: "RequestContext" },
1391
1511
  ]);
1392
1512
  addType(root, "CancelAction", [
1393
- { id: 1, name: "conversation_id", type: "string" },
1513
+ // Cursor CLI CancelAction field #1 is the interruption reason, not an id.
1514
+ { id: 1, name: "reason", type: "string" },
1394
1515
  ]);
1395
1516
  // Deferred answers for an AskQuestion the client already replied to with
1396
1517
  // `async`. `original_args` is kept as raw bytes so the exact AskQuestionArgs
@@ -1401,11 +1522,30 @@ export function createMessageTypes() {
1401
1522
  { id: 2, name: "original_args", type: "bytes" },
1402
1523
  { id: 3, name: "result", type: "AskQuestionResult" },
1403
1524
  ]);
1525
+ // Cursor CLI 2026.10.01 descriptors: system context is
1526
+ // injected into the current Run without submitting another user turn.
1527
+ addType(root, "SystemContextInjection", [
1528
+ { id: 1, name: "producer", type: "string" },
1529
+ { id: 2, name: "content", type: "string" },
1530
+ ]);
1531
+ // Cursor SDK `Run.steer` / CLI steering payload: a user message added to the
1532
+ // running turn. `request_context` is optional and unused by the SDK.
1533
+ addType(root, "UserContextInjection", [
1534
+ { id: 1, name: "user_message", type: "UserMessage" },
1535
+ { id: 2, name: "request_context", type: "RequestContext" },
1536
+ ]);
1537
+ addType(root, "InjectContextAction", [
1538
+ { id: 1, name: "injection_id", type: "string" },
1539
+ { id: 2, name: "expected_run_id", type: "string" },
1540
+ { id: 3, name: "user_context", type: "UserContextInjection" },
1541
+ { id: 4, name: "system_context", type: "SystemContextInjection" },
1542
+ ]);
1404
1543
  addType(root, "ConversationAction", [
1405
1544
  { id: 1, name: "user_message_action", type: "UserMessageAction" },
1406
1545
  { id: 2, name: "resume_action", type: "ResumeAction" },
1407
1546
  { id: 3, name: "cancel_action", type: "CancelAction" },
1408
1547
  { id: 8, name: "async_ask_question_completion_action", type: "AsyncAskQuestionCompletionAction" },
1548
+ { id: 19, name: "inject_context_action", type: "InjectContextAction" },
1409
1549
  ], [{
1410
1550
  name: "action",
1411
1551
  fields: [
@@ -1413,6 +1553,7 @@ export function createMessageTypes() {
1413
1553
  "resume_action",
1414
1554
  "cancel_action",
1415
1555
  "async_ask_question_completion_action",
1556
+ "inject_context_action",
1416
1557
  ],
1417
1558
  }]);
1418
1559
  // Seed ConversationStateStructure for turn 1 (system prompt as JSON strings
@@ -1701,11 +1842,11 @@ export function createMessageTypes() {
1701
1842
  { id: 2, name: "value", type: "string" },
1702
1843
  ]);
1703
1844
  addType(root, "AvailableModelVariant", [
1704
- { id: 1, name: "display_name", type: "string" },
1705
- { id: 2, name: "is_max_mode", type: "bool" },
1706
- { id: 3, name: "is_default_max_config", type: "bool" },
1707
- { id: 4, name: "is_default_non_max_config", type: "bool" },
1708
- { id: 5, name: "parameter_values", type: "AvailableModelParameterValue", repeated: true },
1845
+ { id: 1, name: "parameter_values", type: "AvailableModelParameterValue", repeated: true },
1846
+ { id: 2, name: "display_name", type: "string" },
1847
+ { id: 3, name: "is_max_mode", type: "bool" },
1848
+ { id: 4, name: "is_default_max_config", type: "bool" },
1849
+ { id: 5, name: "is_default_non_max_config", type: "bool" },
1709
1850
  ]);
1710
1851
  addType(root, "AvailableModelEntry", [
1711
1852
  { id: 1, name: "name", type: "string" },
@@ -1714,14 +1855,16 @@ export function createMessageTypes() {
1714
1855
  { id: 9, name: "supports_thinking", type: "bool" },
1715
1856
  { id: 10, name: "supports_images", type: "bool" },
1716
1857
  { id: 14, name: "supports_max_mode", type: "bool" },
1717
- { id: 15, name: "context_token_limit", type: "uint32" },
1858
+ { id: 15, name: "context_token_limit", type: "int32" },
1859
+ { id: 16, name: "context_token_limit_for_max_mode", type: "int32" },
1718
1860
  { id: 17, name: "client_display_name", type: "string" },
1719
1861
  { id: 18, name: "server_model_name", type: "string" },
1720
1862
  { id: 29, name: "parameter_definitions", type: "AvailableModelParameterDefinition", repeated: true },
1721
1863
  { id: 30, name: "variants", type: "AvailableModelVariant", repeated: true },
1722
1864
  ]);
1723
1865
  addType(root, "AvailableModelsResponse", [
1724
- { id: 1, name: "models", type: "AvailableModelEntry", repeated: true },
1866
+ { id: 1, name: "model_names", type: "string", repeated: true },
1867
+ { id: 2, name: "models", type: "AvailableModelEntry", repeated: true },
1725
1868
  ]);
1726
1869
  return root;
1727
1870
  }