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.
Files changed (96) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/README.md +204 -37
  3. package/SECURITY.md +20 -8
  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 +1 -0
  14. package/dist/context/build.js +32 -5
  15. package/dist/context/env.js +27 -3
  16. package/dist/context/epoch.js +9 -5
  17. package/dist/context/git.d.ts +5 -2
  18. package/dist/context/git.js +17 -7
  19. package/dist/context/paths.d.ts +1 -1
  20. package/dist/context/paths.js +7 -4
  21. package/dist/debug.js +13 -2
  22. package/dist/early-tool-rows.d.ts +60 -0
  23. package/dist/early-tool-rows.js +96 -0
  24. package/dist/errors.d.ts +49 -7
  25. package/dist/errors.js +163 -8
  26. package/dist/host-interrupt.d.ts +12 -0
  27. package/dist/host-interrupt.js +18 -0
  28. package/dist/host-steer.d.ts +71 -0
  29. package/dist/host-steer.js +232 -0
  30. package/dist/image-input.js +4 -3
  31. package/dist/index.d.ts +2 -2
  32. package/dist/language-model.d.ts +114 -23
  33. package/dist/language-model.js +2290 -469
  34. package/dist/lost-request.d.ts +39 -0
  35. package/dist/lost-request.js +111 -0
  36. package/dist/model-config.d.ts +3 -1
  37. package/dist/model-config.js +50 -29
  38. package/dist/models.d.ts +26 -3
  39. package/dist/models.js +101 -45
  40. package/dist/opencode2/tool-results.d.ts +18 -0
  41. package/dist/opencode2/tool-results.js +66 -0
  42. package/dist/plugin-opencode2.js +185 -25
  43. package/dist/plugin.js +28 -5
  44. package/dist/pricing-data.d.ts +26 -2
  45. package/dist/pricing-data.js +26 -2
  46. package/dist/pricing.d.ts +1 -1
  47. package/dist/pricing.js +7 -4
  48. package/dist/process-shared.d.ts +14 -0
  49. package/dist/process-shared.js +18 -0
  50. package/dist/protocol/apply-patch.d.ts +25 -13
  51. package/dist/protocol/apply-patch.js +79 -30
  52. package/dist/protocol/blob-store.d.ts +5 -1
  53. package/dist/protocol/blob-store.js +10 -6
  54. package/dist/protocol/client-version.js +6 -4
  55. package/dist/protocol/conversation-cache-header.d.ts +23 -0
  56. package/dist/protocol/conversation-cache-header.js +149 -0
  57. package/dist/protocol/conversation-persistence.d.ts +27 -1
  58. package/dist/protocol/conversation-persistence.js +628 -126
  59. package/dist/protocol/conversation-state.d.ts +8 -1
  60. package/dist/protocol/conversation-state.js +28 -7
  61. package/dist/protocol/host-notes.d.ts +31 -0
  62. package/dist/protocol/host-notes.js +85 -0
  63. package/dist/protocol/messages.js +91 -8
  64. package/dist/protocol/request.d.ts +35 -19
  65. package/dist/protocol/request.js +53 -28
  66. package/dist/protocol/sent-history-images.d.ts +49 -0
  67. package/dist/protocol/sent-history-images.js +102 -0
  68. package/dist/protocol/tool-call-bridge.d.ts +2 -0
  69. package/dist/protocol/tool-call-bridge.js +10 -0
  70. package/dist/protocol/tools.d.ts +74 -8
  71. package/dist/protocol/tools.js +431 -141
  72. package/dist/protocol/turn-provenance.d.ts +19 -4
  73. package/dist/protocol/turn-provenance.js +77 -20
  74. package/dist/replay-safety.d.ts +16 -4
  75. package/dist/replay-safety.js +34 -25
  76. package/dist/retry-budget.d.ts +21 -0
  77. package/dist/retry-budget.js +94 -0
  78. package/dist/runtime-files.d.ts +23 -0
  79. package/dist/runtime-files.js +581 -0
  80. package/dist/session.d.ts +94 -9
  81. package/dist/session.js +153 -34
  82. package/dist/shared.d.ts +1 -1
  83. package/dist/shared.js +1 -1
  84. package/dist/shell-timeout.d.ts +54 -10
  85. package/dist/shell-timeout.js +362 -92
  86. package/dist/stream-write.d.ts +20 -0
  87. package/dist/stream-write.js +87 -0
  88. package/dist/terminal-runner.d.ts +15 -0
  89. package/dist/terminal-runner.js +528 -0
  90. package/dist/transport/connect.d.ts +8 -1
  91. package/dist/transport/connect.js +103 -23
  92. package/dist/usage.d.ts +37 -17
  93. package/dist/usage.js +63 -29
  94. package/package.json +3 -2
  95. package/dist/protocol/thinking.d.ts +0 -15
  96. package/dist/protocol/thinking.js +0 -17
@@ -6,10 +6,15 @@ export declare function hydrateConversationState(cacheDir: string, sessionKey: s
6
6
  toolCatalog: OpencodeToolDef[];
7
7
  hostAgent?: string;
8
8
  systemPromptHash?: string;
9
+ hostNote?: string;
9
10
  } | undefined>;
10
11
  /** Restore only turn provenance when its in-memory entry was evicted. */
11
12
  export declare function hydrateTurnProvenance(cacheDir: string, sessionKey: string): Promise<void>;
12
- /** Persist the complete resumable state only after Cursor confirms TurnEnded. */
13
+ /**
14
+ * Persist the resumable state at Cursor's TurnEnded, or before it
15
+ * (`runInProgress`) so a Run the host interrupts, or a restart, resumes from
16
+ * the latest checkpoint and still recognizes the step in progress.
17
+ */
13
18
  export declare function persistConversationState(cacheDir: string, input: {
14
19
  sessionKey: string;
15
20
  conversationId: string;
@@ -18,5 +23,7 @@ export declare function persistConversationState(cacheDir: string, input: {
18
23
  postCompactionRebase?: boolean;
19
24
  hostAgent?: string;
20
25
  systemPromptHash?: string;
26
+ hostNote?: string;
27
+ runInProgress?: boolean;
21
28
  }): Promise<void>;
22
29
  export declare function clearPersistedConversationState(cacheDir: string, sessionKey: string, expectedConversationId?: string): Promise<void>;
@@ -1,10 +1,13 @@
1
1
  import { trace } from "../debug.js";
2
+ import { restoreEarlySteers, serializeEarlySteers } from "../host-steer.js";
3
+ import { restoreLostRequest, serializeLostRequest } from "../lost-request.js";
2
4
  import { getFrozenRequestContext, setFrozenRequestContext, } from "../context/frozen.js";
3
5
  import { hasConversationBinding, isActiveConversationBinding, restoreConversationBinding, } from "./conversation-bind.js";
4
6
  import { compactConversationBlobs, restoreConversationBlobs, } from "./blob-store.js";
5
7
  import { getCheckpoint, setCheckpoint } from "./checkpoint.js";
6
8
  import { getTurnProvenance, parseTurnProvenance, restoreTurnProvenance, serializeTurnProvenance, } from "./turn-provenance.js";
7
- import { deletePersistedConversation, loadPersistedConversation, persistConversation, } from "./conversation-persistence.js";
9
+ import { heldHistoryImagesSnapshot, restoreHeldHistoryImages } from "./sent-history-images.js";
10
+ import { deletePersistedConversation, getPersistedTurnProvenance, loadPersistedConversation, persistConversation, } from "./conversation-persistence.js";
8
11
  /** Restore one OpenCode session before its conversation binding is resolved. */
9
12
  export async function hydrateConversationState(cacheDir, sessionKey) {
10
13
  if (hasConversationBinding(sessionKey))
@@ -17,6 +20,8 @@ export async function hydrateConversationState(cacheDir, sessionKey) {
17
20
  return undefined;
18
21
  }
19
22
  restoreConversationBinding(sessionKey, persisted.conversationId);
23
+ restoreEarlySteers(sessionKey, persisted.earlySteers);
24
+ restoreLostRequest(sessionKey, persisted.lostRequest);
20
25
  if (persisted.checkpoint)
21
26
  setCheckpoint(persisted.conversationId, persisted.checkpoint);
22
27
  restoreConversationBlobs(persisted.conversationId, persisted.blobs);
@@ -24,29 +29,38 @@ export async function hydrateConversationState(cacheDir, sessionKey) {
24
29
  const provenance = persisted.turnProvenance ? parseTurnProvenance(persisted.turnProvenance) : undefined;
25
30
  if (provenance?.conversationId === persisted.conversationId)
26
31
  restoreTurnProvenance(sessionKey, provenance);
32
+ restoreHeldHistoryImages(sessionKey, persisted.conversationId, {
33
+ checkpoint: persisted.sentImagesCheckpoint,
34
+ hashes: persisted.sentImageHashes,
35
+ });
27
36
  trace(`conversation persistence: restored sessionKey=${sessionKey} ` +
28
37
  `conversationId=${persisted.conversationId} checkpoint=${persisted.checkpoint?.length ?? 0}B ` +
29
- `blobs=${persisted.blobs.length}`);
38
+ `blobs=${persisted.blobs.length} heldImages=${persisted.sentImageHashes?.length ?? 0}`);
30
39
  return {
31
40
  conversationId: persisted.conversationId,
32
41
  postCompactionRebase: persisted.postCompactionRebase,
33
42
  toolCatalog: structuredClone(persisted.toolCatalog),
34
43
  ...(persisted.hostAgent ? { hostAgent: persisted.hostAgent } : {}),
35
44
  ...(persisted.systemPromptHash ? { systemPromptHash: persisted.systemPromptHash } : {}),
45
+ ...(persisted.hostNote ? { hostNote: persisted.hostNote } : {}),
36
46
  };
37
47
  }
38
48
  /** Restore only turn provenance when its in-memory entry was evicted. */
39
49
  export async function hydrateTurnProvenance(cacheDir, sessionKey) {
40
50
  if (getTurnProvenance(sessionKey))
41
51
  return;
42
- const persisted = (await loadPersistedConversation(cacheDir, sessionKey)).value;
43
- if (!persisted?.turnProvenance)
52
+ const persisted = await getPersistedTurnProvenance(cacheDir, sessionKey);
53
+ if (!persisted)
44
54
  return;
45
55
  const provenance = parseTurnProvenance(persisted.turnProvenance);
46
56
  if (provenance?.conversationId === persisted.conversationId)
47
57
  restoreTurnProvenance(sessionKey, provenance);
48
58
  }
49
- /** Persist the complete resumable state only after Cursor confirms TurnEnded. */
59
+ /**
60
+ * Persist the resumable state at Cursor's TurnEnded, or before it
61
+ * (`runInProgress`) so a Run the host interrupts, or a restart, resumes from
62
+ * the latest checkpoint and still recognizes the step in progress.
63
+ */
50
64
  export async function persistConversationState(cacheDir, input) {
51
65
  // A newer Run may have reset/superseded this conversation while its final
52
66
  // frame was still in flight. Never let that late TurnEnded resurrect it.
@@ -56,10 +70,13 @@ export async function persistConversationState(cacheDir, input) {
56
70
  return;
57
71
  }
58
72
  const checkpoint = getCheckpoint(input.conversationId);
59
- const blobCompaction = compactConversationBlobs(input.conversationId, checkpoint);
73
+ const blobCompaction = compactConversationBlobs(input.conversationId, checkpoint, {
74
+ keepInMemory: input.runInProgress,
75
+ });
60
76
  const blobs = blobCompaction.blobs;
61
77
  const requestContext = getFrozenRequestContext(input.conversationId) ?? input.requestContext;
62
78
  const provenance = getTurnProvenance(input.sessionKey);
79
+ const heldImages = heldHistoryImagesSnapshot(input.sessionKey, input.conversationId, checkpoint);
63
80
  await persistConversation(cacheDir, {
64
81
  sessionKey: input.sessionKey,
65
82
  conversationId: input.conversationId,
@@ -70,11 +87,15 @@ export async function persistConversationState(cacheDir, input) {
70
87
  postCompactionRebase: input.postCompactionRebase,
71
88
  hostAgent: input.hostAgent,
72
89
  systemPromptHash: input.systemPromptHash,
90
+ hostNote: input.hostNote,
91
+ earlySteers: serializeEarlySteers(input.sessionKey),
92
+ lostRequest: serializeLostRequest(input.sessionKey),
93
+ ...(heldImages ? { sentImageHashes: heldImages.hashes, sentImagesCheckpoint: heldImages.checkpoint } : {}),
73
94
  ...(provenance?.conversationId === input.conversationId
74
95
  ? { turnProvenance: serializeTurnProvenance(provenance) }
75
96
  : {}),
76
97
  });
77
- trace(`conversation persistence: saved sessionKey=${input.sessionKey} ` +
98
+ trace(`conversation persistence: saved${input.runInProgress ? " in-progress" : ""} sessionKey=${input.sessionKey} ` +
78
99
  `conversationId=${input.conversationId} checkpoint=${checkpoint?.length ?? 0}B ` +
79
100
  `blobs=${blobCompaction.beforeCount}->${blobCompaction.afterCount} ` +
80
101
  `blobBytes=${blobCompaction.beforeBytes}->${blobCompaction.afterBytes}` +
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Host notes (OpenCode `<system-update>` and read-tool instructions) in the
3
+ * shapes Cursor's own clients give model-visible context:
4
+ * - mid-turn, plain text in a `user_context` injection, as Cursor SDK
5
+ * `Run.steer` sends it (no wrapper: wrapped text was refused live);
6
+ * - on a later user turn, a `<system_reminder>` block in the message text;
7
+ * - provider notices about one exec result, as postToolUse hook context.
8
+ */
9
+ /** Cursor CLI rejects a hook additional_context longer than this. */
10
+ export declare const HOOK_ADDITIONAL_CONTEXT_MAX_CHARS = 10000;
11
+ /** Stable delivery order survives asynchronous acknowledgements and Run recovery. */
12
+ export type HostNoteBatch = {
13
+ notes: string[];
14
+ order: number;
15
+ };
16
+ export type HookAdditionalContext = {
17
+ hook_event_name: string;
18
+ content: string;
19
+ };
20
+ /** Plain note text for one mid-turn injection, in host order. */
21
+ export declare function hostNoteText(notes: readonly string[]): string;
22
+ /**
23
+ * One postToolUse hook context per note, trimmed like Cursor CLI's carrier.
24
+ * A note over the CLI limit is split rather than dropped, so no text is lost.
25
+ */
26
+ export declare function hostNoteHookContexts(notes: readonly string[]): HookAdditionalContext[];
27
+ /** One `<system_reminder>` block per note, for a user turn's text. */
28
+ export declare function wrapHostNoteForCursor(text: string): string;
29
+ export declare function wrapHostNotesForCursor(notes: readonly string[]): string[];
30
+ export declare function encodePersistedHostNotes(notes: readonly string[]): string | undefined;
31
+ export declare function decodePersistedHostNotes(blob: string | undefined): string[];
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Host notes (OpenCode `<system-update>` and read-tool instructions) in the
3
+ * shapes Cursor's own clients give model-visible context:
4
+ * - mid-turn, plain text in a `user_context` injection, as Cursor SDK
5
+ * `Run.steer` sends it (no wrapper: wrapped text was refused live);
6
+ * - on a later user turn, a `<system_reminder>` block in the message text;
7
+ * - provider notices about one exec result, as postToolUse hook context.
8
+ */
9
+ const SYSTEM_UPDATE_BLOCK = /<system-update>([\s\S]*?)<\/system-update>/g;
10
+ const SYSTEM_REMINDER_OPEN = "<system_reminder>";
11
+ const SYSTEM_REMINDER_CLOSE = "</system_reminder>";
12
+ /** Cursor CLI's hook event name for context attached to a tool result. */
13
+ const POST_TOOL_USE_HOOK_EVENT = "postToolUse";
14
+ /** Cursor CLI rejects a hook additional_context longer than this. */
15
+ export const HOOK_ADDITIONAL_CONTEXT_MAX_CHARS = 10_000;
16
+ /** Delimiter for ordered note bodies in the restart-snapshot `hostNote` string. */
17
+ const HOST_NOTE_PERSIST_SEP = "\n\u001e\n";
18
+ /** Plain note text for one mid-turn injection, in host order. */
19
+ export function hostNoteText(notes) {
20
+ return notes.map(hostNoteBody).filter(Boolean).join("\n\n");
21
+ }
22
+ /**
23
+ * One postToolUse hook context per note, trimmed like Cursor CLI's carrier.
24
+ * A note over the CLI limit is split rather than dropped, so no text is lost.
25
+ */
26
+ export function hostNoteHookContexts(notes) {
27
+ return notes.flatMap((note) => {
28
+ const body = hostNoteBody(note);
29
+ const chunks = [];
30
+ for (let start = 0; start < body.length;) {
31
+ let end = Math.min(body.length, start + HOOK_ADDITIONAL_CONTEXT_MAX_CHARS);
32
+ // Never split a surrogate pair.
33
+ if (end < body.length && /[\uD800-\uDBFF]/.test(body[end - 1]))
34
+ end--;
35
+ chunks.push(body.slice(start, end));
36
+ start = end;
37
+ }
38
+ return chunks.map((content) => ({ hook_event_name: POST_TOOL_USE_HOOK_EVENT, content }));
39
+ });
40
+ }
41
+ /** One `<system_reminder>` block per note, for a user turn's text. */
42
+ export function wrapHostNoteForCursor(text) {
43
+ return `${SYSTEM_REMINDER_OPEN}\n${escapeReminderTags(hostNoteBody(text))}\n${SYSTEM_REMINDER_CLOSE}`;
44
+ }
45
+ export function wrapHostNotesForCursor(notes) {
46
+ return notes.filter((note) => hostNoteBody(note).length > 0).map(wrapHostNoteForCursor);
47
+ }
48
+ export function encodePersistedHostNotes(notes) {
49
+ const present = notes.filter((note) => note.length > 0);
50
+ if (present.length === 0)
51
+ return undefined;
52
+ return present.join(HOST_NOTE_PERSIST_SEP);
53
+ }
54
+ export function decodePersistedHostNotes(blob) {
55
+ if (!blob)
56
+ return [];
57
+ return blob.split(HOST_NOTE_PERSIST_SEP).filter((note) => note.length > 0);
58
+ }
59
+ /**
60
+ * Unwrap OpenCode `<system-update>` blocks. One host message may hold several
61
+ * (joined by `hostTailNote`); text that is not only such blocks stays as is.
62
+ */
63
+ function hostNoteBody(text) {
64
+ const trimmed = text.trim();
65
+ const bodies = [];
66
+ let rest = trimmed;
67
+ for (const match of trimmed.matchAll(SYSTEM_UPDATE_BLOCK)) {
68
+ bodies.push(unescapeSystemUpdateText(match[1].trim()));
69
+ rest = rest.replace(match[0], "");
70
+ }
71
+ return bodies.length > 0 && rest.trim() === ""
72
+ ? bodies.filter(Boolean).join("\n\n")
73
+ : trimmed;
74
+ }
75
+ /**
76
+ * Inverse of OpenCode's `escapeSystemUpdateText` (`packages/llm/src/protocols/
77
+ * shared.ts`): it escapes `&`, `<`, `>` inside the wrapper. `&amp;` goes last.
78
+ */
79
+ function unescapeSystemUpdateText(text) {
80
+ return text.replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
81
+ }
82
+ /** Same neutralization Cursor CLI applies to text placed inside its reminders. */
83
+ function escapeReminderTags(text) {
84
+ return text.replace(/system_reminder/gi, "system_reminder_");
85
+ }
@@ -135,6 +135,7 @@ export function createMessageTypes() {
135
135
  { id: 4, name: "model", type: "string" },
136
136
  { id: 5, name: "resume", type: "string" },
137
137
  { id: 6, name: "agent_id", type: "string" },
138
+ { id: 7, name: "attachments", type: "string", repeated: true },
138
139
  ]);
139
140
  addType(root, "TaskToolCall", [{ id: 1, name: "args", type: "TaskToolArgs" }]);
140
141
  // Native subagent execution is separate from the display-only TaskToolCall.
@@ -427,8 +428,33 @@ export function createMessageTypes() {
427
428
  { id: 15, name: "tool_call_delta", type: "ToolCallDeltaUpdate" },
428
429
  { id: 16, name: "step_started", type: "StepStarted" },
429
430
  { id: 17, name: "step_completed", type: "StepCompleted" },
431
+ { id: 23, name: "context_injection_state", type: "ContextInjectionStateUpdate" },
430
432
  { 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"] }]);
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
+ ]);
432
458
  // ── Exec channel ──
433
459
  // Field numbers match agent.v1. Extra fields we don't use are still declared
434
460
  // so protobufjs doesn't drop them on decode.
@@ -465,16 +491,21 @@ export function createMessageTypes() {
465
491
  { id: 1, name: "path", type: "string" },
466
492
  { id: 2, name: "reason", type: "string" },
467
493
  ]);
494
+ addType(root, "ReadRejected", [
495
+ { id: 1, name: "path", type: "string" },
496
+ { id: 2, name: "reason", type: "string" },
497
+ ]);
468
498
  addType(root, "ReadResult", [
469
499
  { id: 1, name: "success", type: "ReadSuccess" },
470
500
  { id: 2, name: "error", type: "ReadError" },
501
+ { id: 3, name: "rejected", type: "ReadRejected" },
471
502
  { id: 4, name: "file_not_found", type: "ReadFileNotFound" },
472
503
  { id: 5, name: "permission_denied", type: "ReadPermissionDenied" },
473
504
  { id: 6, name: "invalid_file", type: "ReadInvalidFile" },
474
505
  ], [
475
506
  {
476
507
  name: "result",
477
- fields: ["success", "error", "file_not_found", "permission_denied", "invalid_file"],
508
+ fields: ["success", "error", "rejected", "file_not_found", "permission_denied", "invalid_file"],
478
509
  },
479
510
  ]);
480
511
  addType(root, "GrepArgs", [
@@ -554,11 +585,16 @@ export function createMessageTypes() {
554
585
  { id: 4, name: "error", type: "string" },
555
586
  { id: 5, name: "is_readonly", type: "bool" },
556
587
  ]);
588
+ addType(root, "WriteRejected", [
589
+ { id: 1, name: "path", type: "string" },
590
+ { id: 2, name: "reason", type: "string" },
591
+ ]);
557
592
  addType(root, "WriteResult", [
558
593
  { id: 1, name: "success", type: "WriteSuccess" },
559
594
  { id: 3, name: "permission_denied", type: "WritePermissionDenied" },
560
595
  { id: 5, name: "error", type: "WriteError" },
561
- ], [{ name: "result", fields: ["success", "permission_denied", "error"] }]);
596
+ { id: 6, name: "rejected", type: "WriteRejected" },
597
+ ], [{ name: "result", fields: ["success", "permission_denied", "error", "rejected"] }]);
562
598
  // agent.v1 Pi exec results. Cursor places Pi requests at ExecServerMessage
563
599
  // fields #45..#51 and their corresponding ExecClientMessage results at
564
600
  // #46..#52 (the result fields are deliberately offset by one).
@@ -634,10 +670,21 @@ export function createMessageTypes() {
634
670
  { id: 1, name: "path", type: "string" },
635
671
  { id: 2, name: "error", type: "string" },
636
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
+ ]);
637
682
  addType(root, "DeleteResult", [
638
683
  { id: 1, name: "success", type: "DeleteSuccess" },
684
+ { id: 4, name: "permission_denied", type: "DeletePermissionDenied" },
685
+ { id: 6, name: "rejected", type: "DeleteRejected" },
639
686
  { id: 7, name: "error", type: "DeleteError" },
640
- ], [{ name: "result", fields: ["success", "error"] }]);
687
+ ], [{ name: "result", fields: ["success", "permission_denied", "rejected", "error"] }]);
641
688
  addType(root, "LsArgs", [
642
689
  { id: 1, name: "path", type: "string" },
643
690
  { id: 2, name: "ignore", type: "string", repeated: true },
@@ -657,10 +704,15 @@ export function createMessageTypes() {
657
704
  { id: 1, name: "path", type: "string" },
658
705
  { id: 2, name: "error", type: "string" },
659
706
  ]);
707
+ addType(root, "LsRejected", [
708
+ { id: 1, name: "path", type: "string" },
709
+ { id: 2, name: "reason", type: "string" },
710
+ ]);
660
711
  addType(root, "LsResult", [
661
712
  { id: 1, name: "success", type: "LsSuccess" },
662
713
  { id: 2, name: "error", type: "LsError" },
663
- ], [{ name: "result", fields: ["success", "error"] }]);
714
+ { id: 3, name: "rejected", type: "LsRejected" },
715
+ ], [{ name: "result", fields: ["success", "error", "rejected"] }]);
664
716
  addType(root, "ShellArgs", [
665
717
  { id: 1, name: "command", type: "string" },
666
718
  { id: 2, name: "working_directory", type: "string" },
@@ -670,6 +722,7 @@ export function createMessageTypes() {
670
722
  // foreground cancellation deadline from a soft background handoff.
671
723
  { id: 13, name: "timeout_behavior", type: "uint32" },
672
724
  { id: 14, name: "hard_timeout", type: "uint32" },
725
+ { id: 15, name: "description", type: "string" },
673
726
  ]);
674
727
  addType(root, "ShellStreamStart", []); // optional SandboxPolicy only; empty is valid
675
728
  addType(root, "ShellStreamStdout", [{ id: 1, name: "data", type: "string" }]);
@@ -740,9 +793,10 @@ export function createMessageTypes() {
740
793
  { id: 2, name: "failure", type: "ShellFailure" },
741
794
  { id: 3, name: "timeout", type: "ShellTimeout" },
742
795
  { id: 4, name: "rejected", type: "ShellRejected" },
796
+ { id: 7, name: "permission_denied", type: "ShellPermissionDenied" },
743
797
  { id: 102, name: "is_background", type: "bool" },
744
798
  { id: 104, name: "pid", type: "uint32" },
745
- ], [{ name: "result", fields: ["success", "failure", "timeout", "rejected"] }]);
799
+ ], [{ name: "result", fields: ["success", "failure", "timeout", "rejected", "permission_denied"] }]);
746
800
  addType(root, "DiagnosticsError", [
747
801
  { id: 1, name: "path", type: "string" },
748
802
  { id: 2, name: "error", type: "string" },
@@ -924,6 +978,15 @@ export function createMessageTypes() {
924
978
  { id: 3, name: "rejected", type: "ShellRejected" },
925
979
  { id: 4, name: "permission_denied", type: "ShellPermissionDenied" },
926
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
+ ]);
927
990
  addType(root, "ShellStream", [
928
991
  { id: 1, name: "stdout", type: "ShellStreamStdout" },
929
992
  { id: 2, name: "stderr", type: "ShellStreamStderr" },
@@ -932,7 +995,8 @@ export function createMessageTypes() {
932
995
  { id: 5, name: "rejected", type: "ShellRejected" },
933
996
  { id: 6, name: "permission_denied", type: "ShellPermissionDenied" },
934
997
  { id: 7, name: "backgrounded", type: "ShellStreamBackgrounded" },
935
- ], [{ 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"] }]);
936
1000
  addType(root, "GlobArgs", [
937
1001
  { id: 1, name: "target_directory", type: "string" },
938
1002
  { id: 2, name: "glob_pattern", type: "string" },
@@ -962,6 +1026,14 @@ export function createMessageTypes() {
962
1026
  { id: 1, name: "text", type: "McpTextContent" },
963
1027
  { id: 2, name: "image", type: "McpImageContent" },
964
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
+ ]);
965
1037
  addType(root, "McpSuccess", [
966
1038
  { id: 1, name: "content", type: "McpToolResultContentItem", repeated: true },
967
1039
  { id: 2, name: "is_error", type: "bool" },
@@ -974,7 +1046,9 @@ export function createMessageTypes() {
974
1046
  addType(root, "McpResult", [
975
1047
  { id: 1, name: "success", type: "McpSuccess" },
976
1048
  { id: 2, name: "error", type: "McpError" },
977
- ], [{ 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"] }]);
978
1052
  // McpToolDefinition — agent.v1 shape for RequestContext.tools (#7) and
979
1053
  // AgentRunRequest.mcp_tools (#4). Defined early so RequestContext* can
980
1054
  // reference it.
@@ -1259,6 +1333,7 @@ export function createMessageTypes() {
1259
1333
  { id: 49, name: "pi_grep_args", type: "PiGrepToolArgs" },
1260
1334
  { id: 50, name: "pi_find_args", type: "PiFindToolArgs" },
1261
1335
  { id: 51, name: "pi_ls_args", type: "PiLsToolArgs" },
1336
+ { id: 55, name: "accept_hook_additional_contexts", type: "bool" },
1262
1337
  ], [{ name: "args", fields: [
1263
1338
  "shell_args", "write_args", "delete_args", "grep_args", "read_args", "ls_args",
1264
1339
  "request_context_args", "mcp_args", "shell_stream_args", "background_shell_spawn_args",
@@ -1272,6 +1347,7 @@ export function createMessageTypes() {
1272
1347
  { id: 1, name: "id", type: "uint32" },
1273
1348
  { id: 15, name: "exec_id", type: "string" },
1274
1349
  { id: 39, name: "local_execution_time_ms", type: "uint64" },
1350
+ { id: 45, name: "hook_additional_contexts", type: "HookAdditionalContext", repeated: true },
1275
1351
  { id: 2, name: "shell_result", type: "ShellResult" },
1276
1352
  { id: 3, name: "write_result", type: "WriteResult" },
1277
1353
  { id: 4, name: "delete_result", type: "DeleteResult" },
@@ -1452,9 +1528,16 @@ export function createMessageTypes() {
1452
1528
  { id: 1, name: "producer", type: "string" },
1453
1529
  { id: 2, name: "content", type: "string" },
1454
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
+ ]);
1455
1537
  addType(root, "InjectContextAction", [
1456
1538
  { id: 1, name: "injection_id", type: "string" },
1457
1539
  { id: 2, name: "expected_run_id", type: "string" },
1540
+ { id: 3, name: "user_context", type: "UserContextInjection" },
1458
1541
  { id: 4, name: "system_context", type: "SystemContextInjection" },
1459
1542
  ]);
1460
1543
  addType(root, "ConversationAction", [
@@ -12,16 +12,17 @@ export type RunRequestInput = {
12
12
  /** Stable parent group; unlike conversationId, this survives compaction/rebase. */
13
13
  conversationGroupId?: string;
14
14
  /**
15
- * Prior chat turns for a seed ConversationStateStructure (no checkpoint).
16
- * Tool outputs, when required for compaction/recovery, are represented as
17
- * user-role OpenCode host observations rather than assistant-authored prose.
15
+ * Prior chat turns for a Run without a checkpoint, carried in the live user
16
+ * text (see {@link seedHistoryUserText}). Tool outputs, when required for
17
+ * compaction/recovery, are represented as user-role OpenCode host
18
+ * observations rather than assistant-authored prose.
18
19
  */
19
20
  history?: SeedHistoryMessage[];
20
21
  /**
21
22
  * Opaque ConversationStateStructure bytes from the last
22
23
  * conversation_checkpoint_update for this conversation_id. When set, echoed
23
- * as AgentRunRequest.conversation_state (CLI parity). When absent, a seed
24
- * state carrying only `history` (or empty) is built for turn 1.
24
+ * as AgentRunRequest.conversation_state (CLI parity). When absent, an empty
25
+ * seed state is sent and `history` travels in the user text.
25
26
  */
26
27
  conversationState?: Uint8Array;
27
28
  parameterValues?: Array<{
@@ -44,26 +45,32 @@ export type RunRequestInput = {
44
45
  mode?: number;
45
46
  };
46
47
  /**
47
- * Seed ConversationStateStructure for the first turn (no checkpoint yet).
48
+ * Empty ConversationStateStructure for a Run without a checkpoint (turn 1,
49
+ * compaction, rebase, reseed). After the first checkpoint arrives we stop
50
+ * inventing state and echo the server's opaque structure instead (CLI behavior).
48
51
  *
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.
53
- *
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.
52
+ * Nothing goes in `root_prompt_messages_json` (#1). Neither Cursor client
53
+ * writes it: Cursor checkpoints fill it with references to the conversation's
54
+ * own rendered root prompt. A client-written entry stands in for that prompt,
55
+ * so Cursor stops rendering RequestContext rules, skills and subagents — the
56
+ * host system-instructions rule included. Verified live 2026-10-09
57
+ * (claude-sonnet-5-5): two seeded history messages there gave checkpoint
58
+ * categories `rules:0, subagents:0` and a reply that ignored the rule; the same
59
+ * history in the user text kept both. History therefore travels in the user
60
+ * text ({@link seedHistoryUserText}).
59
61
  *
60
62
  * We deliberately do NOT use `AgentRunRequest.custom_system_prompt` (#8): that
61
63
  * field is the internal `--system-prompt` CLI override and the server rejects
62
64
  * it for normal accounts.
63
65
  */
64
- export declare function buildSeedConversationState(input?: {
65
- history?: SeedHistoryMessage[];
66
- }): Uint8Array;
66
+ export declare function buildSeedConversationState(): Uint8Array;
67
+ /**
68
+ * Prior turns of a Run without a checkpoint, as a transcript ahead of the live
69
+ * user text. `system` entries are dropped: host system context travels as the
70
+ * system-instructions rule in RequestContext. Closing tags inside a message are
71
+ * escaped so a message cannot end its own block early.
72
+ */
73
+ export declare function seedHistoryUserText(text: string, history: readonly SeedHistoryMessage[] | undefined): string;
67
74
  /**
68
75
  * Build an AgentClientMessage{run_request} for a conversation turn.
69
76
  *
@@ -75,3 +82,12 @@ export declare function buildRunRequest(input: RunRequestInput): Uint8Array;
75
82
  * Build a heartbeat message.
76
83
  */
77
84
  export declare function buildHeartbeat(): Uint8Array;
85
+ /**
86
+ * ACM #5 `exec_client_control_message.heartbeat{id}`: Cursor CLI sends it every
87
+ * 3 s for each exec it is still running, keyed by the exec message id. The CLI
88
+ * leaves a zero id out, as proto3 does for a default scalar; protobufjs would
89
+ * write it explicitly, so it is omitted here to send the CLI's exact bytes.
90
+ */
91
+ export declare function buildExecHeartbeat(execId: number): Uint8Array;
92
+ /** Stops the live Run, as Cursor CLI does on a user Stop before it closes the stream. */
93
+ export declare function buildCancelAction(reason: string): Uint8Array;