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
@@ -126,31 +126,42 @@ export async function executeCursorImageSave(args, ctx) {
126
126
  // case `assertExternalDirectory` exists for. Asking only for `edit` would
127
127
  // quietly skip a boundary the host enforces for every one of its own writes.
128
128
  const editPattern = path.relative(realRoot(workspace), contained.path);
129
- try {
130
- if (!isInsideProject(contained.path, workspace, ctx.directory)) {
131
- const parentDir = path.dirname(contained.path);
132
- const glob = path.join(parentDir, "*").replaceAll("\\", "/");
129
+ if (typeof ctx.ask === "function") {
130
+ try {
131
+ if (!isInsideProject(contained.path, workspace, ctx.directory)) {
132
+ const parentDir = path.dirname(contained.path);
133
+ const glob = path.join(parentDir, "*").replaceAll("\\", "/");
134
+ await ctx.ask({
135
+ permission: "external_directory",
136
+ patterns: [glob],
137
+ always: [glob],
138
+ metadata: { filepath: contained.path, parentDir },
139
+ });
140
+ }
133
141
  await ctx.ask({
134
- permission: "external_directory",
135
- patterns: [glob],
136
- always: [glob],
137
- metadata: { filepath: contained.path, parentDir },
142
+ permission: "edit",
143
+ patterns: [editPattern],
144
+ always: ["*"],
145
+ metadata: {
146
+ filepath: contained.path,
147
+ mime: image.mime,
148
+ bytes: image.data.length,
149
+ source: "cursor-generate-image",
150
+ },
138
151
  });
139
152
  }
140
- await ctx.ask({
141
- permission: "edit",
142
- patterns: [editPattern],
143
- always: ["*"],
144
- metadata: {
145
- filepath: contained.path,
146
- mime: image.mime,
147
- bytes: image.data.length,
148
- source: "cursor-generate-image",
149
- },
150
- });
153
+ catch (error) {
154
+ const reason = errorMessage(error);
155
+ trace(`image save: permission refused path=${JSON.stringify(contained.path)} reason=${reason}`);
156
+ throw new Error(`${IMAGE_PERMISSION_DENIED_PREFIX} ${reason}`);
157
+ }
151
158
  }
152
- catch (error) {
153
- const reason = errorMessage(error);
159
+ else if (ctx.ask === null) {
160
+ trace(`image save: host cannot prompt; writing after containment ` +
161
+ `path=${JSON.stringify(contained.path)}`);
162
+ }
163
+ else {
164
+ const reason = "this host did not provide a permission prompt for the write";
154
165
  trace(`image save: permission refused path=${JSON.stringify(contained.path)} reason=${reason}`);
155
166
  throw new Error(`${IMAGE_PERMISSION_DENIED_PREFIX} ${reason}`);
156
167
  }
@@ -185,5 +196,6 @@ export async function executeCursorImageSave(args, ctx) {
185
196
  return {
186
197
  title: editPattern,
187
198
  output: `Saved the generated image to ${contained.path} (${image.data.length} bytes).`,
199
+ bytes: image.data.length,
188
200
  };
189
201
  }
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { CursorContinuationOptions } from "./session.js";
3
3
  import type { HostToolDialect } from "./protocol/tools.js";
4
4
  import type { AccessTokenProvider } from "./auth-renewal.js";
5
5
  export type CursorRetryOptions = {
6
- /** Total attempts including the initial request. Default: 3. */
6
+ /** Optional total attempt ceiling (including the initial request), across host retries. */
7
7
  maxAttempts?: number;
8
8
  /** Initial full-jitter backoff ceiling. Default: 500ms. */
9
9
  baseDelayMs?: number;
@@ -42,7 +42,7 @@ export type CreateCursorOptions = {
42
42
  cacheDir?: string;
43
43
  /** Held-stream policy. Defaults: heartbeat 5s, semantic idle 120s, tool inactivity 10m. */
44
44
  continuation?: CursorContinuationOptions;
45
- /** Fresh-turn retry policy. Defaults: 3 attempts, 500ms base, 8000ms cap. */
45
+ /** Run retry policy. Defaults: 10 transport / 3 server retries, 500ms base, 8000ms delay cap. */
46
46
  retry?: CursorRetryOptions;
47
47
  /** Fallback host dialect when schemas are omitted or ambiguous. Default: OpenCode 1.x. */
48
48
  defaultDialect?: HostToolDialect;
@@ -1,12 +1,16 @@
1
+ import { type RetryBudget } from "./retry-budget.js";
1
2
  import type { LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3StreamPart } from "@ai-sdk/provider";
2
3
  import type { CreateCursorOptions, CursorRetryOptions } from "./index.js";
4
+ import { type ToolRefusal } from "./opencode2/tool-results.js";
3
5
  import { type OpencodeToolDef } from "./protocol/tools.js";
4
6
  import { cursorContextUsageMetadata, type CursorContextUsageSource, type CursorConversationTokenDetails } from "./protocol/token-details.js";
5
7
  import { type ConversationBlobGraphStats } from "./protocol/blob-store.js";
6
8
  import { type CursorSession } from "./session.js";
7
9
  import { CursorProviderError } from "./errors.js";
8
- import { type ModelInfo } from "./models.js";
10
+ import { type ModelInfo, type ModelParameterValue } from "./models.js";
9
11
  import type { SeedHistoryMessage } from "./protocol/request.js";
12
+ import { type CursorImageInput } from "./image-input.js";
13
+ import { type HostNoteBatch } from "./protocol/host-notes.js";
10
14
  type PromptIdentity = {
11
15
  hostAgent?: string;
12
16
  systemPromptHash?: string;
@@ -30,13 +34,13 @@ export declare function checkpointBlobGraphConcern(stats: ConversationBlobGraphS
30
34
  */
31
35
  export declare function isSoleControlFrame(payload: Uint8Array): boolean;
32
36
  export type CursorRetryPolicy = {
33
- maxAttempts: number;
37
+ maxAttempts?: number;
34
38
  baseDelayMs: number;
35
39
  maxDelayMs: number;
36
40
  };
37
41
  export declare function resolveRetryPolicy(options: CursorRetryOptions | undefined): CursorRetryPolicy;
38
42
  export declare function connectFrameError(payload: string): CursorProviderError;
39
- /** Restore the last real catalog solely for lifecycle turns such as compaction. */
43
+ /** Restore the epoch catalog for the next nonempty host turn. */
40
44
  export declare function restoreTurnToolCatalog(sessionKey: string, tools: OpencodeToolDef[]): void;
41
45
  /** Record the authoritative host todo list for an OpenCode session. */
42
46
  export declare function rememberMirroredTodos(sessionKey: string | undefined, todos: ReadonlyArray<Record<string, unknown>>): void;
@@ -48,6 +52,11 @@ export declare function snapshotMirroredTodosBySession(sessionKey: string | unde
48
52
  * churn have a single no-op definition.
49
53
  */
50
54
  export declare function promptIdentityWouldRemint(_previous: PromptIdentity, _current: PromptIdentity): boolean;
55
+ /** Test helper: the release `startSession` performs, then the host-note stores. */
56
+ export declare function releaseHostNoteInjectionsForTests(sessionKey: string): {
57
+ inFlight: string[][];
58
+ undelivered: string[];
59
+ };
51
60
  type V3Part = LanguageModelV3StreamPart;
52
61
  export declare function createCursorLanguageModel(modelId: string, providerId: string, options: CreateCursorOptions): LanguageModelV3;
53
62
  export declare function pumpWithRecovery(input: {
@@ -55,6 +64,9 @@ export declare function pumpWithRecovery(input: {
55
64
  controller: ReadableStreamDefaultController<V3Part>;
56
65
  abortSignal?: AbortSignal;
57
66
  retryPolicy?: CursorRetryPolicy;
67
+ retryBudget?: RetryBudget;
68
+ /** Proven completed host results can seed history without replaying their calls. */
69
+ canRebaseFromHistory?: (session: CursorSession) => boolean;
58
70
  recover: (recovery: CursorRunRecovery) => Promise<CursorSession>;
59
71
  onSession?: (session: CursorSession) => void;
60
72
  maxRecoveries?: number;
@@ -68,7 +80,7 @@ export declare function pumpWithRecovery(input: {
68
80
  }): Promise<CursorSession>;
69
81
  export type CursorRunRecovery = {
70
82
  kind: "rebase";
71
- reason?: "checkpoint-unusable";
83
+ reason?: "checkpoint-unusable" | "unfinished-prior-run" | "completed-tool-results";
72
84
  } | {
73
85
  kind: "resume";
74
86
  conversationId: string;
@@ -76,10 +88,6 @@ export type CursorRunRecovery = {
76
88
  };
77
89
  /** Start (or replace) the per-session heartbeat. In-flight writes from a prior attach are ignored. */
78
90
  export declare function attachSessionHeartbeat(session: CursorSession): void;
79
- /**
80
- * OpenCode re-sends the full tool-result history on every continuation. Prefer
81
- * the newest result that still has a live pending exec on its tagged session.
82
- */
83
91
  export declare function findContinuationSession(toolResults: Array<{
84
92
  sessionId: string;
85
93
  execId: number;
@@ -96,12 +104,14 @@ export declare function isProperCatalogSubset(incoming: ReadonlyArray<{
96
104
  }>): boolean;
97
105
  /**
98
106
  * An open parent Run for this OpenCode session whose catalog strictly contains
99
- * the incoming tools is treated as an in-session helper. Those calls must not
100
- * cancel/supersede the parent (that remints on the trailing tool result).
107
+ * the incoming tools is treated as an in-session helper unless the request
108
+ * carries a result for that parent's pending call. A result can precede a new
109
+ * user message or agent-change reminder and still needs to reach the parent.
110
+ * Helper calls must not cancel/supersede the parent.
101
111
  */
102
112
  export declare function shouldIsolateInSessionHelper(openCodeSessionId: string | undefined, incomingTools: ReadonlyArray<{
103
113
  name?: string;
104
- }>): boolean;
114
+ }>, historicalResults?: ExtractedToolResult[]): boolean;
105
115
  /** How long a fresh-turn drain may wait for Cursor `turn_ended` after bridged settle. */
106
116
  export declare const FRESH_TURN_DRAIN_TIMEOUT_MS = 8000;
107
117
  /**
@@ -112,13 +122,16 @@ export declare const FRESH_TURN_DRAIN_TIMEOUT_MS = 8000;
112
122
  */
113
123
  export declare const FRESH_TURN_PENDING_CANCEL_REASON = "Host started a new user turn before this tool result was delivered";
114
124
  /**
115
- * Before opening a new user-turn Run, finish the prior held-open Run cleanly:
116
- * settle display-only bridged pendings, cancel any real execs still waiting on
117
- * the host, then drain for `turn_ended`. That preserves the same
118
- * conversation_id / checkpoint prefix instead of aborting mid-tool.
125
+ * Before opening a new user-turn Run, settle the held Run's pending results
126
+ * and drain its terminal checkpoint. The unseen follow-up to results settled
127
+ * here can finish through Cursor's cancellation contract; an unacknowledged
128
+ * turn must rebase.
119
129
  */
120
130
  export declare function preparePriorSessionForFreshTurn(openCodeSessionId: string | undefined, opts?: {
121
131
  timeoutMs?: number;
132
+ /** Tool results already in this prompt; deliver any that match held pendings before cancel. */
133
+ toolResults?: ExtractedToolResult[];
134
+ hostAgent?: string;
122
135
  }): Promise<"drained" | "settled-only" | "busy" | "none">;
123
136
  /**
124
137
  * Write error/reject results for every still-open non-bridged pending on the
@@ -126,42 +139,132 @@ export declare function preparePriorSessionForFreshTurn(openCodeSessionId: strin
126
139
  *
127
140
  * @returns number of pendings successfully cancelled
128
141
  */
129
- export declare function cancelPendingExecsForFreshTurn(session: CursorSession): number;
142
+ export declare function cancelPendingExecsForFreshTurn(session: CursorSession): Promise<number>;
143
+ /**
144
+ * While the host runs a step's tools nothing pumps the Run, but Cursor keeps
145
+ * writing: right after a step it sends `tool_requests_listed` or a checkpoint,
146
+ * then KV blob writes, and it may abort an exec. Cursor CLI's single reader
147
+ * handles KV and exec control on their own channels at once; an unanswered KV
148
+ * write left Cursor waiting until the tool finished (seen live). Answer KV,
149
+ * apply an abort for an exec still waiting on the host, and skip heartbeats;
150
+ * queue every other frame in `heldFrames`, in arrival order, for the next pump.
151
+ * An abort whose exec request is still queued stays queued behind it.
152
+ *
153
+ * The reader peeks the shared `pendingFrameRead`: a pump that starts meanwhile
154
+ * awaits the same read, and the reader leaves that frame to it.
155
+ */
156
+ export declare function readHeldRun(session: CursorSession): void;
157
+ /** How long a Run must wait on host tools before its state is saved for a restart. */
158
+ export declare const HELD_RUN_SAVE_DELAY_MS = 1000;
159
+ /** How long a cancelled Run may take to end by itself before its stream is closed. */
160
+ export declare const HOST_INTERRUPT_GRACE_MS = 3000;
161
+ /**
162
+ * The host stopped the turn. OpenCode 2 never aborts `doStream` for that, so
163
+ * the Run would keep generating, and a held one would wait on results that
164
+ * never come. Cancel it as Cursor CLI's Stop does: forget what the host owed,
165
+ * send `cancel_action`, give Cursor a moment to end the Run, then close it.
166
+ * A Run that ends without `turn_ended` is saved with the last checkpoint
167
+ * Cursor sent, so the next turn resumes it and recognizes the stopped step.
168
+ */
169
+ export declare function cancelRunForHostInterrupt(session: CursorSession, reason: string, opts?: {
170
+ graceMs?: number;
171
+ }): Promise<void>;
172
+ /**
173
+ * Test helper: wait for host-interrupt cancels and in-progress Run saves, which
174
+ * write after the call that started them returned. Close the Runs first, so a
175
+ * held Run's save timer finds them closed instead of starting another save.
176
+ */
177
+ export declare function settleRunSavesForTests(): Promise<void>;
178
+ export declare function cancelIfHostStoppedSince(session: CursorSession): void;
130
179
  /**
131
180
  * Read remaining frames on an idle held-open Run until `turn_ended`, a
132
181
  * response-requiring request we cannot answer without the host, or timeout.
133
- * Used only on the fresh-turn settle path — not a general pump substitute.
182
+ * Used only to settle a Run the host left (a fresh turn, a host interrupt) —
183
+ * not a general pump substitute. `cancelReason` sends `cancel_action` first;
184
+ * `cancelled`: the caller already sent it. Either way nothing the Run still
185
+ * sends (display updates, an exec already in flight) needs the host, and
186
+ * Cursor's closing frames (the final checkpoint) are worth reading.
134
187
  */
135
188
  export declare function drainSessionUntilTurnEnded(session: CursorSession, opts?: {
136
189
  timeoutMs?: number;
137
- }): Promise<"turn-ended" | "busy" | "timeout" | "interrupted" | "skipped">;
190
+ cancelReason?: string;
191
+ cancelled?: boolean;
192
+ }): Promise<"turn-ended" | "cancelled" | "busy" | "timeout" | "interrupted" | "skipped">;
193
+ export declare function deliverContinuationResults(session: CursorSession, trailingToolResults: ExtractedToolResult[], delivery?: {
194
+ hostAgent?: string;
195
+ }): Promise<CursorSession | undefined>;
196
+ /** Notes the restart snapshot kept for this session's next user turn. */
197
+ export declare function restorePersistedHostNotes(sessionKey: string, persisted: string | undefined): void;
138
198
  /**
139
- * Deliver trailing tool results onto a live continuation session.
140
- * Returns the same session when writes succeed (or only bridged results were
141
- * cleared). Returns undefined after closing the session when a write fails, so
142
- * the caller can rebase onto a fresh Run instead of pumping a dead stream.
199
+ * Host notes a new Run's user turn carries, as `<system_reminder>` blocks:
200
+ * notes no earlier Run delivered, then notes OpenCode placed before this user
201
+ * message (they exist only in host history, which a checkpointed Run does not
202
+ * resend). Call `sent()` once the Run request is written.
143
203
  */
144
- export declare function deliverContinuationResults(session: CursorSession, trailingToolResults: ExtractedToolResult[]): CursorSession | undefined;
204
+ export declare function prepareUserTurnHostNotes(input: {
205
+ sessionKey?: string;
206
+ prompt: LanguageModelV3CallOptions["prompt"];
207
+ startedWithCheckpoint: boolean;
208
+ isCompaction: boolean;
209
+ ephemeralRun: boolean;
210
+ resuming: boolean;
211
+ /** The history actually seeded by a Run without a checkpoint. */
212
+ seedHistory?: readonly SeedHistoryMessage[];
213
+ /** Messages carried as the live action, rather than replayed in seed history. */
214
+ liveTurnStart?: number;
215
+ }): {
216
+ reminders: string[];
217
+ carriedNotes: HostNoteBatch[];
218
+ sent: () => void;
219
+ };
145
220
  /**
146
221
  * Read the held-open stream, emitting stream parts, until the turn boundary:
147
- * - a tool call (exec_server_message) → emit tool-call, finish "tool-calls",
148
- * and KEEP the session open for the result on the next doStream call;
222
+ * - tool call(s) for one Cursor generation → emit each tool-call as it
223
+ * arrives; finish "tool-calls" once Cursor's listed count (field 27) is
224
+ * met (or immediately when the process has never seen field 27); KEEP the
225
+ * session open for results on the next doStream call;
149
226
  * - turn_ended → finish "stop" and close the session;
150
- * - transport EOF before turn_ended → throw for one fresh-Run recovery.
227
+ * - transport EOF before turn_ended → throw for one fresh-Run recovery
228
+ * (after host tools were already emitted, finish "tool-calls" instead and
229
+ * let the continuation rebase).
151
230
  */
152
231
  export declare function pump(session: CursorSession, controller: ReadableStreamDefaultController<V3Part>, ids: {
153
232
  textId: string;
154
233
  reasoningId: string;
155
234
  }, abortSignal?: AbortSignal): Promise<void>;
156
- type ExtractedToolResult = {
235
+ type ToolResultContent = {
157
236
  toolCallId: string;
158
- sessionId: string;
159
- execId: number;
160
237
  toolName: string;
161
238
  output: string;
162
239
  error?: string;
240
+ refusal?: ToolRefusal;
241
+ interrupted?: boolean;
242
+ /** Host media parts of this result (own content, or the trailing media message). */
243
+ media?: unknown[];
244
+ /** `media` decoded for the exec result, with content hashes in the same order. */
245
+ images?: CursorImageInput[];
246
+ imageHashes?: string[];
247
+ /** Raw host note bodies that trailed the step (attached after typed result build). */
248
+ notes?: string[];
249
+ /** Notices about this result its typed shape cannot carry (hook context on this result). */
250
+ notices?: string[];
163
251
  };
252
+ type ExtractedToolResult = ToolResultContent & {
253
+ sessionId: string;
254
+ execId: number;
255
+ };
256
+ /**
257
+ * Host notes between the previous reply and the closing user message, e.g. an
258
+ * OpenCode 2 `<system-update>` after MCP servers reconnect. The host's leading
259
+ * system prompt is never one of them. Empty unless the prompt ends with a
260
+ * user message that is not itself a note.
261
+ */
262
+ export declare function hostNotesBeforeUserTurn(prompt: LanguageModelV3CallOptions["prompt"]): string[];
164
263
  export declare function extractTrailingToolResults(prompt: LanguageModelV3CallOptions["prompt"]): ExtractedToolResult[];
264
+ /** Decode only image-bearing pending execs, under one continuation-wide budget. */
265
+ export declare function decodeTrailingToolImages(session: CursorSession, results: ExtractedToolResult[], signal?: AbortSignal, maxBytes?: number): Promise<ExtractedToolResult[]>;
266
+ /** Debug summary of the last prompt messages: roles, part kinds, short text previews. */
267
+ export declare function describePromptTail(prompt: LanguageModelV3CallOptions["prompt"], count?: number): string;
165
268
  /** Detect a host-owned canonical plan review, excluding Cursor exec replies. */
166
269
  export declare function hasApprovedUncorrelatedPlanStageResult(prompt: LanguageModelV3CallOptions["prompt"]): boolean;
167
270
  /**
@@ -179,6 +282,7 @@ export declare function groundCheckpointTurnText(userText: string, checkpoint: b
179
282
  */
180
283
  export declare function buildOpenCodeInteractionGuidance(tools: OpencodeToolDef[], isCompaction: boolean, workspaceRoot: string, options?: {
181
284
  knownMcpServers?: Iterable<string>;
285
+ allowTools?: boolean;
182
286
  }): string | undefined;
183
287
  /** Rough char→token estimate for mid-turn usage before TurnEnded arrives. */
184
288
  export declare function estimateTokens(chars: number): number;
@@ -194,6 +298,13 @@ export declare function cursorTurnEndedProviderMetadata(te: Record<string, unkno
194
298
  context?: ReturnType<typeof cursorContextUsageMetadata>;
195
299
  };
196
300
  };
301
+ /** Partial assistant output may grow on a host retry; it is not a new user turn. */
302
+ export declare function retryTurnKey(prompt: LanguageModelV3CallOptions["prompt"], withoutLastInterruptionNotice?: boolean): string;
303
+ /** All user messages after the last assistant/tool boundary form one live user turn. */
304
+ export declare function liveUserTurn(prompt: LanguageModelV3CallOptions["prompt"]): {
305
+ start: number;
306
+ text: string;
307
+ } | undefined;
197
308
  /**
198
309
  * Prior prompt turns for a seed ConversationStateStructure. Tool results must
199
310
  * never be replayed as assistant-authored prose: that teaches the model to
@@ -205,6 +316,7 @@ export declare function cursorTurnEndedProviderMetadata(te: Record<string, unkno
205
316
  export declare function extractPromptHistory(prompt: LanguageModelV3CallOptions["prompt"], options?: {
206
317
  preserveTrailingUser?: boolean;
207
318
  toolResults?: "omit" | "all" | "trailing";
319
+ liveTurnStart?: number;
208
320
  }): SeedHistoryMessage[];
209
321
  /** Share of the target context a foreign-history rebase may fill before compaction. */
210
322
  export declare const FOREIGN_HISTORY_REBASE_CONTEXT_SHARE = 0.8;
@@ -217,6 +329,8 @@ export declare function assertForeignHistoryRebaseFits(input: {
217
329
  modelInfo: ModelInfo | undefined;
218
330
  cursorModelId: string;
219
331
  maxMode: boolean;
332
+ /** Resolved variant tuple; its `context` is the Run's window when present. */
333
+ parameters?: ModelParameterValue[];
220
334
  history: SeedHistoryMessage[];
221
335
  systemPrompt: string | undefined;
222
336
  userText: string;
@@ -248,8 +362,13 @@ export declare function spanEndParts(opts: {
248
362
  type: "text-end" | "reasoning-end";
249
363
  id: string;
250
364
  }>;
251
- /** Exported for tests — false for compaction/summary (no tools) and toolChoice none. */
252
365
  export declare function computeAllowTools(toolCount: number, toolChoice: LanguageModelV3CallOptions["toolChoice"] | undefined): boolean;
366
+ /**
367
+ * OpenCode title/summary take the first non-empty line of the answer. After a
368
+ * lifecycle refuse, Cursor often narrates that tools are unavailable — drop
369
+ * that so it does not become the session title.
370
+ */
371
+ export declare function shouldDropLifecycleToollessText(text: string, refusedExec: boolean): boolean;
253
372
  export declare function resolveTurnToolState(input: {
254
373
  sessionKey?: string;
255
374
  incomingTools: OpencodeToolDef[];