cursor-opencode-provider 0.7.6 → 0.8.0

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 (60) hide show
  1. package/README.md +9 -7
  2. package/dist/agent-url.js +2 -2
  3. package/dist/auth-renewal.d.ts +85 -0
  4. package/dist/auth-renewal.js +284 -0
  5. package/dist/auth.d.ts +38 -22
  6. package/dist/auth.js +79 -96
  7. package/dist/classic-tools.d.ts +15 -0
  8. package/dist/classic-tools.js +45 -0
  9. package/dist/context/build.d.ts +42 -5
  10. package/dist/context/build.js +96 -92
  11. package/dist/context/dynamic-catalog.d.ts +2 -58
  12. package/dist/context/dynamic-catalog.js +10 -160
  13. package/dist/context/epoch.d.ts +6 -4
  14. package/dist/context/epoch.js +22 -10
  15. package/dist/context/frozen.js +37 -14
  16. package/dist/context/index.d.ts +1 -1
  17. package/dist/context/index.js +1 -1
  18. package/dist/context/overlay.d.ts +2 -23
  19. package/dist/context/overlay.js +6 -30
  20. package/dist/context/paths.d.ts +7 -4
  21. package/dist/context/paths.js +13 -9
  22. package/dist/context/rules.d.ts +3 -15
  23. package/dist/context/rules.js +10 -186
  24. package/dist/debug.d.ts +2 -0
  25. package/dist/debug.js +4 -0
  26. package/dist/errors.d.ts +13 -0
  27. package/dist/errors.js +27 -3
  28. package/dist/host-agent-mode.js +2 -2
  29. package/dist/image-save.js +3 -3
  30. package/dist/index.d.ts +10 -0
  31. package/dist/language-model.d.ts +41 -8
  32. package/dist/language-model.js +391 -98
  33. package/dist/opencode2/integration.d.ts +30 -7
  34. package/dist/opencode2/integration.js +75 -46
  35. package/dist/plan-execution-kickoff.js +2 -2
  36. package/dist/plugin-opencode2.js +10 -16
  37. package/dist/plugin.d.ts +0 -10
  38. package/dist/plugin.js +138 -109
  39. package/dist/protocol/blob-store.js +3 -2
  40. package/dist/protocol/conversation-bind.js +1 -1
  41. package/dist/protocol/conversation-persistence.d.ts +2 -0
  42. package/dist/protocol/conversation-persistence.js +11 -1
  43. package/dist/protocol/conversation-state.d.ts +2 -0
  44. package/dist/protocol/conversation-state.js +19 -0
  45. package/dist/protocol/create-plan.js +2 -1
  46. package/dist/protocol/messages.js +22 -4
  47. package/dist/protocol/request.d.ts +11 -9
  48. package/dist/protocol/request.js +15 -25
  49. package/dist/protocol/tools.d.ts +17 -10
  50. package/dist/protocol/tools.js +53 -93
  51. package/dist/protocol/turn-provenance.d.ts +52 -0
  52. package/dist/protocol/turn-provenance.js +174 -0
  53. package/dist/session.d.ts +9 -1
  54. package/dist/shared.d.ts +7 -0
  55. package/dist/shared.js +7 -0
  56. package/package.json +1 -1
  57. package/dist/context/agents.d.ts +0 -12
  58. package/dist/context/agents.js +0 -136
  59. package/dist/context/skills.d.ts +0 -11
  60. package/dist/context/skills.js +0 -110
@@ -68,9 +68,18 @@ export function getHostCacheDirOverride() {
68
68
  export function resolveHostCacheDir(env = process.env) {
69
69
  return bridgeGlobalCacheDir() ?? openCodeGlobalCacheDir(env);
70
70
  }
71
- /** Native OpenCode global config dir. */
72
- export function opencodeGlobalConfigDir() {
73
- return path.join(resolveHome(), ".config", "opencode");
71
+ function xdgConfigHome(env = process.env) {
72
+ if (env.XDG_CONFIG_HOME && env.XDG_CONFIG_HOME.length > 0)
73
+ return env.XDG_CONFIG_HOME;
74
+ return path.join(resolveHome(env), ".config");
75
+ }
76
+ /**
77
+ * Native OpenCode global config dir: `$XDG_CONFIG_HOME/opencode`, otherwise
78
+ * `~/.config/opencode` — OpenCode's `Global.Path.config` (`xdg-basedir`
79
+ * `xdgConfig`, `packages/core/src/global.ts`), the same in 1.x and 2.0.
80
+ */
81
+ export function opencodeGlobalConfigDir(env = process.env) {
82
+ return path.join(xdgConfigHome(env), "opencode");
74
83
  }
75
84
  /**
76
85
  * Host global cache dir for Cursor project metadata + model/version caches.
@@ -116,7 +125,7 @@ export function hostPlansDir(_workspaceRoot) {
116
125
  return path.join(hostGlobalDataDir(), "plans");
117
126
  }
118
127
  /**
119
- * Cursor-compatible path slug (`/Users/a/b` → `Users-a-b`).
128
+ * Cursor-compatible path slug (`/workspace/a/b` → `workspace-a-b`).
120
129
  * Used for per-workspace metadata under the host cache.
121
130
  */
122
131
  export function slugifyWorkspacePath(workspaceRoot) {
@@ -157,8 +166,3 @@ export function ensureOpencodeProjectDir(workspaceRoot) {
157
166
  `xdg_cache_home=${process.env.XDG_CACHE_HOME ?? "(unset)"}`);
158
167
  return dir;
159
168
  }
160
- export function resolveHomeRelative(p) {
161
- if (p.startsWith("~/"))
162
- return path.join(homedir(), p.slice(2));
163
- return p;
164
- }
@@ -1,7 +1,3 @@
1
- export type CollectedRule = {
2
- fullPath: string;
3
- content: string;
4
- };
5
1
  export type OpencodeJson = {
6
2
  instructions?: string[];
7
3
  permission?: unknown;
@@ -9,18 +5,10 @@ export type OpencodeJson = {
9
5
  plugins?: string[];
10
6
  mcp?: Record<string, unknown>;
11
7
  };
12
- export declare function findGitWorktree(start: string): Promise<string>;
13
8
  /** Same truthy rule as OpenCode's Flag.OPENCODE_DISABLE_PROJECT_CONFIG. */
14
9
  export declare function isProjectConfigDisabled(): boolean;
15
- /** Fetch a remote instruction with one deadline covering headers and body. */
16
- export declare function fetchRemoteInstruction(url: string, timeoutMs?: number): Promise<string | undefined>;
17
- export declare function loadMergedConfig(workspaceRoot: string): Promise<OpencodeJson>;
18
10
  /**
19
- * Collect OpenCode instruction files.
20
- * `preloadedConfig` reuses a merged config already loaded on this Run.
11
+ * Merged `opencode.json` / `opencode.jsonc` for MCP server ids, plugin lists,
12
+ * and interaction guidance. Instruction file bodies are not collected here.
21
13
  */
22
- export declare function collectRules(workspaceRoot: string, preloadedConfig?: OpencodeJson): Promise<{
23
- rules: CollectedRule[];
24
- config: OpencodeJson;
25
- worktree: string;
26
- }>;
14
+ export declare function loadMergedConfig(workspaceRoot: string): Promise<OpencodeJson>;
@@ -1,7 +1,6 @@
1
- import { readFile, readdir, stat } from "node:fs/promises";
2
- import { homedir } from "node:os";
1
+ import { readFile, stat } from "node:fs/promises";
3
2
  import path from "node:path";
4
- import { opencodeConfigFileNames, opencodeGlobalConfigDirs, opencodeProjectConfigDirs, resolveHomeRelative } from "./paths.js";
3
+ import { opencodeConfigFileNames, opencodeGlobalConfigDirs, opencodeProjectConfigDirs } from "./paths.js";
5
4
  async function exists(file) {
6
5
  try {
7
6
  await stat(file);
@@ -18,7 +17,10 @@ async function readJsonConfig(dir) {
18
17
  continue;
19
18
  try {
20
19
  const raw = await readFile(file, "utf-8");
21
- const stripped = raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
20
+ // Match strings first so comment markers inside URLs/plugin specifiers
21
+ // survive. Remove comments before trailing commas (which may precede one).
22
+ const uncommented = raw.replace(/"(?:\\.|[^"\\])*"|\/\*[\s\S]*?\*\/|\/\/[^\r\n]*/g, token => token.startsWith('"') ? token : " ");
23
+ const stripped = uncommented.replace(/"(?:\\.|[^"\\])*"|,\s*(?=[}\]])/g, token => token.startsWith('"') ? token : "");
22
24
  return JSON.parse(stripped);
23
25
  }
24
26
  catch {
@@ -27,131 +29,11 @@ async function readJsonConfig(dir) {
27
29
  }
28
30
  return {};
29
31
  }
30
- export async function findGitWorktree(start) {
31
- let dir = path.resolve(start);
32
- for (;;) {
33
- if (await exists(path.join(dir, ".git")))
34
- return dir;
35
- const parent = path.dirname(dir);
36
- if (parent === dir)
37
- return path.resolve(start);
38
- dir = parent;
39
- }
40
- }
41
- async function findUp(name, start, stop) {
42
- let dir = path.resolve(start);
43
- const root = path.resolve(stop);
44
- for (;;) {
45
- const candidate = path.join(dir, name);
46
- if (await exists(candidate))
47
- return candidate;
48
- if (dir === root)
49
- return undefined;
50
- const parent = path.dirname(dir);
51
- if (parent === dir)
52
- return undefined;
53
- dir = parent;
54
- }
55
- }
56
- async function readRule(file) {
57
- if (!(await exists(file)))
58
- return undefined;
59
- try {
60
- const content = await readFile(file, "utf-8");
61
- if (!content.trim())
62
- return undefined;
63
- return { fullPath: path.resolve(file), content };
64
- }
65
- catch {
66
- return undefined;
67
- }
68
- }
69
- function globToRegExp(glob) {
70
- const norm = glob.replace(/\\/g, "/");
71
- let re = "^";
72
- for (let i = 0; i < norm.length; i++) {
73
- const c = norm[i];
74
- if (c === "*") {
75
- if (norm[i + 1] === "*") {
76
- re += ".*";
77
- i++;
78
- if (norm[i + 1] === "/")
79
- i++;
80
- }
81
- else {
82
- re += "[^/]*";
83
- }
84
- }
85
- else if (".$^+?()[]{}|".includes(c) || c === "\\") {
86
- re += "\\" + c;
87
- }
88
- else {
89
- re += c;
90
- }
91
- }
92
- return new RegExp(re + "$");
93
- }
94
- async function expandGlob(pattern, workspaceRoot) {
95
- const abs = path.isAbsolute(pattern) ? pattern : path.join(workspaceRoot, pattern);
96
- if (!abs.includes("*"))
97
- return (await exists(abs)) ? [abs] : [];
98
- const out = [];
99
- const base = abs.split("*")[0] || workspaceRoot;
100
- const startDir = path.dirname(base.endsWith("/") ? base : base);
101
- const regex = globToRegExp(abs);
102
- async function walk(dir, depth) {
103
- if (depth > 8)
104
- return;
105
- let entries;
106
- try {
107
- entries = await readdir(dir);
108
- }
109
- catch {
110
- return;
111
- }
112
- entries.sort();
113
- for (const name of entries) {
114
- if (name === "node_modules" || name === ".git")
115
- continue;
116
- const full = path.join(dir, name);
117
- let st;
118
- try {
119
- st = await stat(full);
120
- }
121
- catch {
122
- continue;
123
- }
124
- if (st.isDirectory())
125
- await walk(full, depth + 1);
126
- else if (regex.test(full.replace(/\\/g, "/")))
127
- out.push(full);
128
- }
129
- }
130
- await walk(startDir, 0);
131
- return out;
132
- }
133
32
  /** Same truthy rule as OpenCode's Flag.OPENCODE_DISABLE_PROJECT_CONFIG. */
134
33
  export function isProjectConfigDisabled() {
135
34
  const value = process.env.OPENCODE_DISABLE_PROJECT_CONFIG?.toLowerCase();
136
35
  return value === "true" || value === "1";
137
36
  }
138
- /** Fetch a remote instruction with one deadline covering headers and body. */
139
- export async function fetchRemoteInstruction(url, timeoutMs = 5000) {
140
- const ctrl = new AbortController();
141
- const timer = setTimeout(() => ctrl.abort(), Math.max(1, timeoutMs));
142
- try {
143
- const res = await fetch(url, { signal: ctrl.signal });
144
- if (!res.ok)
145
- return undefined;
146
- return await res.text();
147
- }
148
- catch {
149
- return undefined;
150
- }
151
- finally {
152
- clearTimeout(timer);
153
- }
154
- }
155
37
  function mergeConfig(base, overlay) {
156
38
  return {
157
39
  ...base,
@@ -163,6 +45,10 @@ function mergeConfig(base, overlay) {
163
45
  permission: overlay.permission ?? base.permission,
164
46
  };
165
47
  }
48
+ /**
49
+ * Merged `opencode.json` / `opencode.jsonc` for MCP server ids, plugin lists,
50
+ * and interaction guidance. Instruction file bodies are not collected here.
51
+ */
166
52
  export async function loadMergedConfig(workspaceRoot) {
167
53
  const globalConfig = await readJsonConfig(opencodeGlobalConfigDirs()[0] ?? "");
168
54
  if (isProjectConfigDisabled())
@@ -176,65 +62,3 @@ export async function loadMergedConfig(workspaceRoot) {
176
62
  }
177
63
  return mergeConfig(globalConfig, projectConfig);
178
64
  }
179
- /**
180
- * Collect OpenCode instruction files.
181
- * `preloadedConfig` reuses a merged config already loaded on this Run.
182
- */
183
- export async function collectRules(workspaceRoot, preloadedConfig) {
184
- const worktree = await findGitWorktree(workspaceRoot);
185
- const rules = [];
186
- const seen = new Set();
187
- const config = preloadedConfig ?? await loadMergedConfig(workspaceRoot);
188
- const add = async (file) => {
189
- if (!file)
190
- return;
191
- const resolved = path.resolve(file);
192
- if (seen.has(resolved))
193
- return;
194
- const rule = await readRule(resolved);
195
- if (!rule)
196
- return;
197
- seen.add(resolved);
198
- rules.push(rule);
199
- };
200
- // Match OpenCode: OPENCODE_DISABLE_PROJECT_CONFIG skips project AGENTS/CLAUDE/CONTEXT
201
- // discovery and project opencode.json (see loadMergedConfig).
202
- if (!isProjectConfigDisabled()) {
203
- for (const name of ["AGENTS.md", "CLAUDE.md", "CONTEXT.md"]) {
204
- const hit = await findUp(name, workspaceRoot, worktree);
205
- if (hit) {
206
- await add(hit);
207
- break;
208
- }
209
- }
210
- }
211
- for (const globalDir of opencodeGlobalConfigDirs()) {
212
- await add(path.join(globalDir, "AGENTS.md"));
213
- }
214
- await add(path.join(homedir(), ".claude", "CLAUDE.md"));
215
- for (const raw of config.instructions ?? []) {
216
- if (raw.startsWith("http://") || raw.startsWith("https://")) {
217
- // HTTPS-only. Redirects (incl. to a local proxy) are intentional — do not
218
- // disable follow-redirects or reject localhost/private/metadata hosts.
219
- let remoteUrl;
220
- try {
221
- remoteUrl = new URL(raw);
222
- }
223
- catch {
224
- continue;
225
- }
226
- if (remoteUrl.protocol !== "https:")
227
- continue;
228
- const content = await fetchRemoteInstruction(remoteUrl.href);
229
- if (!content?.trim() || seen.has(raw))
230
- continue;
231
- seen.add(raw);
232
- rules.push({ fullPath: raw, content });
233
- continue;
234
- }
235
- const expanded = resolveHomeRelative(raw);
236
- for (const m of await expandGlob(expanded, workspaceRoot))
237
- await add(m);
238
- }
239
- return { rules, config, worktree };
240
- }
package/dist/debug.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  /** Soft cap for the debug log file; exceeded size triggers truncate + new header. */
2
2
  export declare const DEBUG_LOG_MAX_BYTES: number;
3
+ /** Message of an unknown thrown value, for log lines and error text. */
4
+ export declare function errorMessage(error: unknown): string;
3
5
  /** Whether `CURSOR_PROVIDER_DEBUG` is enabled for this process. */
4
6
  export declare function isDebugEnabled(): boolean;
5
7
  /** Resolve the debug log path (env override or per-uid tmpdir default). */
package/dist/debug.js CHANGED
@@ -18,6 +18,10 @@ let _traceInitialized = false;
18
18
  let _debugFile;
19
19
  let _debugFileUsesManagedDirectory = false;
20
20
  let _announcedLogPath = false;
21
+ /** Message of an unknown thrown value, for log lines and error text. */
22
+ export function errorMessage(error) {
23
+ return error instanceof Error ? error.message : String(error);
24
+ }
21
25
  /** Whether `CURSOR_PROVIDER_DEBUG` is enabled for this process. */
22
26
  export function isDebugEnabled() {
23
27
  return DEBUG_ENABLED;
package/dist/errors.d.ts CHANGED
@@ -17,6 +17,12 @@ export declare class CursorProviderError extends Error {
17
17
  readonly origin: CursorErrorOrigin;
18
18
  readonly transient: boolean;
19
19
  replaySafe: boolean;
20
+ /**
21
+ * The Run failed because Cursor could not restore the resumed checkpoint
22
+ * (it requested blobs this client does not hold) before producing anything.
23
+ * Recovery must reseed a new conversation instead of resuming that checkpoint.
24
+ */
25
+ checkpointUnusable?: boolean;
20
26
  readonly statusCode?: number;
21
27
  readonly grpcStatus?: number | string;
22
28
  readonly rstCode?: number;
@@ -41,8 +47,15 @@ export declare class CursorProtocolError extends CursorProviderError {
41
47
  export declare class CursorAuthError extends CursorProviderError {
42
48
  constructor(message?: string, options?: Partial<CursorErrorDiagnostics> & {
43
49
  cause?: unknown;
50
+ replaySafe?: boolean;
44
51
  });
45
52
  }
53
+ /**
54
+ * Cursor refused the request's credential (HTTP 401 / gRPC unauthenticated),
55
+ * as opposed to refusing the account an action (403 / permission_denied).
56
+ * Only this case is worth one renewed-token retry.
57
+ */
58
+ export declare function isRejectedCredentialError(error: unknown): error is CursorAuthError;
46
59
  /**
47
60
  * OpenCode's SessionRetry treats several substrings in provider error messages
48
61
  * as always-retryable — notably bare "unavailable" and "exhausted", plus a
package/dist/errors.js CHANGED
@@ -3,6 +3,12 @@ export class CursorProviderError extends Error {
3
3
  origin;
4
4
  transient;
5
5
  replaySafe;
6
+ /**
7
+ * The Run failed because Cursor could not restore the resumed checkpoint
8
+ * (it requested blobs this client does not hold) before producing anything.
9
+ * Recovery must reseed a new conversation instead of resuming that checkpoint.
10
+ */
11
+ checkpointUnusable;
6
12
  statusCode;
7
13
  grpcStatus;
8
14
  rstCode;
@@ -64,11 +70,27 @@ export class CursorAuthError extends CursorProviderError {
64
70
  ...options,
65
71
  origin: "auth",
66
72
  transient: false,
67
- replaySafe: false,
73
+ replaySafe: options.replaySafe ?? false,
68
74
  });
69
75
  this.name = "CursorAuthError";
70
76
  }
71
77
  }
78
+ function isUnauthenticatedStatus(status) {
79
+ if (status === undefined)
80
+ return false;
81
+ const normalized = String(status).toLowerCase().replaceAll("-", "_");
82
+ return normalized === "16" || normalized === "unauthenticated";
83
+ }
84
+ /**
85
+ * Cursor refused the request's credential (HTTP 401 / gRPC unauthenticated),
86
+ * as opposed to refusing the account an action (403 / permission_denied).
87
+ * Only this case is worth one renewed-token retry.
88
+ */
89
+ export function isRejectedCredentialError(error) {
90
+ return error instanceof CursorAuthError && (error.statusCode === 401 ||
91
+ isUnauthenticatedStatus(error.grpcStatus) ||
92
+ isUnauthenticatedStatus(error.code));
93
+ }
72
94
  /**
73
95
  * OpenCode's SessionRetry treats several substrings in provider error messages
74
96
  * as always-retryable — notably bare "unavailable" and "exhausted", plus a
@@ -141,7 +163,9 @@ export function isAuthGrpcStatus(status) {
141
163
  }
142
164
  export function cursorHttpError(operation, statusCode, diagnostics = {}) {
143
165
  if (statusCode === 401 || statusCode === 403) {
144
- return new CursorAuthError(`Cursor authentication failed (HTTP ${statusCode}); reauthenticate with Cursor`, { ...diagnostics, statusCode });
166
+ // A rejected credential means the server did no work: replaying with a
167
+ // renewed token is safe unless the attempt already crossed a barrier.
168
+ return new CursorAuthError(`Cursor authentication failed (HTTP ${statusCode}); reauthenticate with Cursor`, { ...diagnostics, statusCode, replaySafe: statusCode === 401 });
145
169
  }
146
170
  return new CursorServerError(`${operation} HTTP ${statusCode}`, {
147
171
  ...diagnostics,
@@ -152,7 +176,7 @@ export function cursorHttpError(operation, statusCode, diagnostics = {}) {
152
176
  }
153
177
  export function cursorGrpcError(operation, grpcStatus, diagnostics = {}) {
154
178
  if (isAuthGrpcStatus(grpcStatus)) {
155
- return new CursorAuthError(`Cursor authentication failed (gRPC ${grpcStatus}); reauthenticate with Cursor`, { ...diagnostics, grpcStatus });
179
+ return new CursorAuthError(`Cursor authentication failed (gRPC ${grpcStatus}); reauthenticate with Cursor`, { ...diagnostics, grpcStatus, replaySafe: isUnauthenticatedStatus(grpcStatus) });
156
180
  }
157
181
  return new CursorServerError(`${operation} gRPC status ${grpcStatus}`, {
158
182
  ...diagnostics,
@@ -7,7 +7,7 @@
7
7
  * OpenCode 2.0 uses it to select its vendor-maintained `plan` / `build` agents;
8
8
  * OpenCode 1.x continues to use advertised `plan_enter` / `plan_exit` tools.
9
9
  */
10
- import { trace } from "./debug.js";
10
+ import { errorMessage, trace } from "./debug.js";
11
11
  let switchHostAgent;
12
12
  const pending = new Map();
13
13
  const MAX_PENDING_HOST_AGENT_SWITCHES = 256;
@@ -82,7 +82,7 @@ export async function flushHostAgentModeSwitch(sessionID, options = {}) {
82
82
  // behavioral fallback until the native host switch succeeds.
83
83
  delete state.cursorSessionID;
84
84
  trace(`host-agent-mode: FAILED sessionID=${key} target=${state.targetModeID} ` +
85
- `attempts=${state.attempts} err=${error instanceof Error ? error.message : String(error)}`);
85
+ `attempts=${state.attempts} err=${errorMessage(error)}`);
86
86
  return false;
87
87
  }
88
88
  }
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import fs from "node:fs";
10
10
  import path from "node:path";
11
- import { trace } from "./debug.js";
11
+ import { errorMessage, trace } from "./debug.js";
12
12
  import { takePendingCursorImage } from "./image-staging.js";
13
13
  /**
14
14
  * Marks a refusal that must reach Cursor as `WriteResult.permission_denied`
@@ -150,7 +150,7 @@ export async function executeCursorImageSave(args, ctx) {
150
150
  });
151
151
  }
152
152
  catch (error) {
153
- const reason = error instanceof Error ? error.message : String(error);
153
+ const reason = errorMessage(error);
154
154
  trace(`image save: permission refused path=${JSON.stringify(contained.path)} reason=${reason}`);
155
155
  throw new Error(`${IMAGE_PERMISSION_DENIED_PREFIX} ${reason}`);
156
156
  }
@@ -165,7 +165,7 @@ export async function executeCursorImageSave(args, ctx) {
165
165
  catch (error) {
166
166
  // Most often something already occupies a path component (EEXIST/ENOTDIR).
167
167
  // Say which path is blocked; the raw errno alone tells the model nothing.
168
- const reason = error instanceof Error ? error.message : String(error);
168
+ const reason = errorMessage(error);
169
169
  trace(`image save: write failed path=${JSON.stringify(contained.path)} reason=${reason}`);
170
170
  throw new Error(`Could not save the generated image to ${contained.path}: ${reason}. `
171
171
  + `Check that ${directory} is a writable directory.`);
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { CursorPlugin } from "./plugin.js";
2
2
  import type { CursorContinuationOptions } from "./session.js";
3
3
  import type { HostToolDialect } from "./protocol/tools.js";
4
+ import type { AccessTokenProvider } from "./auth-renewal.js";
4
5
  export type CursorRetryOptions = {
5
6
  /** Total attempts including the initial request. Default: 3. */
6
7
  maxAttempts?: number;
@@ -11,7 +12,16 @@ export type CursorRetryOptions = {
11
12
  };
12
13
  export type CreateCursorOptions = {
13
14
  name: string;
15
+ /**
16
+ * Current Cursor access token, asked for every time a Run opens. Takes
17
+ * precedence over `accessToken` and `apiKey`; hosts use it to renew the
18
+ * login without exposing a secret in serializable options. `forceRefresh`
19
+ * is set once after Cursor rejected the previous token.
20
+ */
21
+ getAccessToken?: AccessTokenProvider;
22
+ /** Access token sent as-is (no renewal). Used when `getAccessToken` is absent. */
14
23
  accessToken?: string;
24
+ /** Raw `crsr_` API key, exchanged and renewed. Used only when neither of the above is given. */
15
25
  apiKey?: string;
16
26
  /** API base for auth, model discovery, and GetServerConfig. */
17
27
  apiBaseURL?: string;
@@ -5,6 +5,7 @@ import { cursorContextUsageMetadata, type CursorContextUsageSource, type CursorC
5
5
  import { type ConversationBlobGraphStats } from "./protocol/blob-store.js";
6
6
  import { type CursorSession } from "./session.js";
7
7
  import { CursorProviderError } from "./errors.js";
8
+ import { type ModelInfo } from "./models.js";
8
9
  import type { SeedHistoryMessage } from "./protocol/request.js";
9
10
  type PromptIdentity = {
10
11
  hostAgent?: string;
@@ -21,6 +22,13 @@ export declare const MAX_CHECKPOINT_BLOB_GRAPH_BYTES: number;
21
22
  export declare function checkpointBlobGraphRequiresRebase(_stats: ConversationBlobGraphStats, _maxBytes?: number): boolean;
22
23
  /** Warn-only concern for incomplete / oversized checkpoint graphs (no remint). */
23
24
  export declare function checkpointBlobGraphConcern(stats: ConversationBlobGraphStats, maxBytes?: number): "incomplete-checkpoint-graph" | "oversized-checkpoint-graph" | undefined;
25
+ /**
26
+ * True when the raw frame holds exactly one control message: a KV request, a
27
+ * checkpoint update, or an interaction update that is only a heartbeat. Extra
28
+ * fields inside a KV request are tolerated (Cursor has sent them live); extra
29
+ * top-level fields are not.
30
+ */
31
+ export declare function isSoleControlFrame(payload: Uint8Array): boolean;
24
32
  export type CursorRetryPolicy = {
25
33
  maxAttempts: number;
26
34
  baseDelayMs: number;
@@ -50,9 +58,17 @@ export declare function pumpWithRecovery(input: {
50
58
  recover: (recovery: CursorRunRecovery) => Promise<CursorSession>;
51
59
  onSession?: (session: CursorSession) => void;
52
60
  maxRecoveries?: number;
61
+ /**
62
+ * Set when the credential source can renew: called once per turn after
63
+ * Cursor rejects the token (401 / unauthenticated) before the attempt did
64
+ * anything, so the reopened Run uses a force-renewed token. This retry does
65
+ * not count against the transient-failure budget.
66
+ */
67
+ renewRejectedCredential?: () => void;
53
68
  }): Promise<CursorSession>;
54
69
  export type CursorRunRecovery = {
55
70
  kind: "rebase";
71
+ reason?: "checkpoint-unusable";
56
72
  } | {
57
73
  kind: "resume";
58
74
  conversationId: string;
@@ -145,18 +161,12 @@ type ExtractedToolResult = {
145
161
  output: string;
146
162
  error?: string;
147
163
  };
148
- /**
149
- * Tool results that form a live continuation: only the trailing run of `tool`
150
- * messages after the last non-tool message. Mid-prompt historical tool results
151
- * are ignored — they are conversation history, not replies for a held-open Run.
152
- */
153
164
  export declare function extractTrailingToolResults(prompt: LanguageModelV3CallOptions["prompt"]): ExtractedToolResult[];
154
165
  /** Detect a host-owned canonical plan review, excluding Cursor exec replies. */
155
166
  export declare function hasApprovedUncorrelatedPlanStageResult(prompt: LanguageModelV3CallOptions["prompt"]): boolean;
156
167
  /**
157
- * Checkpointed Runs do not resend the system prompt. Keep the workspace root
158
- * on the live user message, and require absolute `path` arguments when that is
159
- * the host's file-tool dialect.
168
+ * Keep the workspace root on a checkpointed turn's live user message too, and
169
+ * require absolute `path` arguments when that is the host's file-tool dialect.
160
170
  */
161
171
  export declare function groundCheckpointTurnText(userText: string, checkpoint: boolean, workspaceRoot: string, tools: readonly {
162
172
  name?: string;
@@ -196,6 +206,21 @@ export declare function extractPromptHistory(prompt: LanguageModelV3CallOptions[
196
206
  preserveTrailingUser?: boolean;
197
207
  toolResults?: "omit" | "all" | "trailing";
198
208
  }): SeedHistoryMessage[];
209
+ /** Share of the target context a foreign-history rebase may fill before compaction. */
210
+ export declare const FOREIGN_HISTORY_REBASE_CONTEXT_SHARE = 0.8;
211
+ /**
212
+ * A foreign-history rebase replays the full host history. When that cannot fit,
213
+ * fail before opening a Run with an error hosts classify as context overflow
214
+ * (HTTP 413 + "prompt is too long"), so the host compacts and retries.
215
+ */
216
+ export declare function assertForeignHistoryRebaseFits(input: {
217
+ modelInfo: ModelInfo | undefined;
218
+ cursorModelId: string;
219
+ maxMode: boolean;
220
+ history: SeedHistoryMessage[];
221
+ systemPrompt: string | undefined;
222
+ userText: string;
223
+ }): void;
199
224
  /** OpenCode session id header, if present. */
200
225
  export declare function opencodeSessionKey(callOptions: LanguageModelV3CallOptions): string | undefined;
201
226
  /**
@@ -205,6 +230,14 @@ export declare function opencodeSessionKey(callOptions: LanguageModelV3CallOptio
205
230
  */
206
231
  export declare function resolveConversationId(callOptions: LanguageModelV3CallOptions): string;
207
232
  export { sessionIdToUuid } from "./protocol/conversation-bind.js";
233
+ /**
234
+ * Grow the held Run's advertised + permitted catalog from this `doStream`
235
+ * call. Continuation skips `startSession`, so without this, exec #36 and
236
+ * permission keep the freeze from Run open. MCP server ids stay those of the
237
+ * Run's one merged-config load (`startSession`); a tool that connects later
238
+ * still gets its identity from that set.
239
+ */
240
+ export declare function refreshHeldSessionToolCatalog(session: CursorSession, callOptions: LanguageModelV3CallOptions): Promise<void>;
208
241
  /** Exported for tests — AI SDK V3 span ends that must precede finish / tool-call. */
209
242
  export declare function spanEndParts(opts: {
210
243
  textStarted: boolean;