cursor-opencode-provider 0.4.3 → 0.6.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.
- package/README.md +33 -23
- package/dist/context/agents.d.ts +1 -1
- package/dist/context/agents.js +1 -1
- package/dist/context/auth-store.js +2 -2
- package/dist/context/build.d.ts +11 -1
- package/dist/context/build.js +76 -25
- package/dist/context/frozen.d.ts +29 -0
- package/dist/context/frozen.js +168 -0
- package/dist/context/index.d.ts +3 -2
- package/dist/context/index.js +3 -2
- package/dist/context/paths.d.ts +34 -53
- package/dist/context/paths.js +54 -118
- package/dist/context/rules.d.ts +1 -0
- package/dist/context/rules.js +2 -2
- package/dist/image-save-tool.d.ts +19 -0
- package/dist/image-save-tool.js +25 -0
- package/dist/image-save.d.ts +58 -0
- package/dist/image-save.js +189 -0
- package/dist/image-staging.d.ts +59 -0
- package/dist/image-staging.js +91 -0
- package/dist/index.d.ts +1 -1
- package/dist/language-model.d.ts +13 -11
- package/dist/language-model.js +957 -150
- package/dist/plan-execution-kickoff.d.ts +55 -0
- package/dist/plan-execution-kickoff.js +162 -0
- package/dist/plugin-opencode2.js +52 -3
- package/dist/plugin-v2.d.ts +11 -9
- package/dist/plugin-v2.js +6 -13
- package/dist/plugin.d.ts +9 -0
- package/dist/plugin.js +61 -5
- package/dist/pricing-data.d.ts +16 -4
- package/dist/pricing-data.js +16 -4
- package/dist/protocol/ask-question.d.ts +133 -0
- package/dist/protocol/ask-question.js +320 -0
- package/dist/protocol/blob-reachability.d.ts +15 -0
- package/dist/protocol/blob-reachability.js +140 -0
- package/dist/protocol/blob-store.d.ts +37 -0
- package/dist/protocol/blob-store.js +115 -0
- package/dist/protocol/conversation-bind.d.ts +13 -0
- package/dist/protocol/conversation-bind.js +27 -1
- package/dist/protocol/conversation-persistence.d.ts +32 -0
- package/dist/protocol/conversation-persistence.js +504 -0
- package/dist/protocol/conversation-state.d.ts +16 -0
- package/dist/protocol/conversation-state.js +62 -0
- package/dist/protocol/create-plan.d.ts +141 -0
- package/dist/protocol/create-plan.js +373 -0
- package/dist/protocol/generate-image.d.ts +54 -0
- package/dist/protocol/generate-image.js +101 -0
- package/dist/protocol/interactions.d.ts +110 -8
- package/dist/protocol/interactions.js +295 -35
- package/dist/protocol/kv.d.ts +1 -0
- package/dist/protocol/kv.js +2 -0
- package/dist/protocol/messages.d.ts +2 -0
- package/dist/protocol/messages.js +148 -8
- package/dist/protocol/request.d.ts +2 -0
- package/dist/protocol/request.js +2 -0
- package/dist/protocol/switch-mode.d.ts +178 -0
- package/dist/protocol/switch-mode.js +417 -0
- package/dist/protocol/token-details.d.ts +33 -0
- package/dist/protocol/token-details.js +155 -0
- package/dist/protocol/tool-call-bridge.js +10 -5
- package/dist/protocol/tools.d.ts +44 -11
- package/dist/protocol/tools.js +171 -41
- package/dist/session.d.ts +39 -11
- package/dist/shared.d.ts +3 -0
- package/dist/shared.js +3 -0
- package/dist/usage.d.ts +42 -37
- package/dist/usage.js +201 -70
- package/dist/web-search-tool.d.ts +22 -26
- package/dist/web-search-tool.js +25 -15
- package/package.json +7 -4
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commit a staged Cursor image to disk, gated by OpenCode's own permission.
|
|
3
|
+
*
|
|
4
|
+
* Host-neutral on purpose: `./image-save-tool.ts` performs the `tool()` value
|
|
5
|
+
* import from `@opencode-ai/plugin`, which the OpenCode 2.0 entrypoint must not
|
|
6
|
+
* pull in (see the note in `./web-search-tool.ts`). Only that file may import
|
|
7
|
+
* the plugin package; this module is safe on every graph.
|
|
8
|
+
*/
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { trace } from "./debug.js";
|
|
12
|
+
import { takePendingCursorImage } from "./image-staging.js";
|
|
13
|
+
/**
|
|
14
|
+
* Marks a refusal that must reach Cursor as `WriteResult.permission_denied`
|
|
15
|
+
* rather than a generic error — the variant its own executor returns and its
|
|
16
|
+
* agent branches on. Both sides of this string are ours, so matching on it is
|
|
17
|
+
* a contract, not a guess at someone else's message.
|
|
18
|
+
*/
|
|
19
|
+
export const IMAGE_PERMISSION_DENIED_PREFIX = "CursorImagePermissionDenied:";
|
|
20
|
+
/**
|
|
21
|
+
* Prove the staged target still resolves inside one of the roots it was mapped
|
|
22
|
+
* into. `remapCursorImageWritePath` already chose the location; this re-checks
|
|
23
|
+
* it against the *real* paths, because a symlink planted between mapping and
|
|
24
|
+
* commit could otherwise redirect the write. Mirrors the containment rule the
|
|
25
|
+
* correlated-edit read applies before answering with complete file content.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveContainedImagePath(target, allowedRoots) {
|
|
28
|
+
const roots = allowedRoots.filter(Boolean).map((root) => path.resolve(root));
|
|
29
|
+
if (roots.length === 0)
|
|
30
|
+
return { error: "no writable root is configured" };
|
|
31
|
+
const absolute = path.isAbsolute(target) ? path.resolve(target) : path.resolve(roots[0], target);
|
|
32
|
+
// The file need not exist yet; its closest existing ancestor must still
|
|
33
|
+
// resolve inside an allowed root.
|
|
34
|
+
let probe = absolute;
|
|
35
|
+
let realProbe;
|
|
36
|
+
while (true) {
|
|
37
|
+
try {
|
|
38
|
+
realProbe = fs.realpathSync(probe);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
const parent = path.dirname(probe);
|
|
43
|
+
if (parent === probe)
|
|
44
|
+
break;
|
|
45
|
+
probe = parent;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (realProbe === undefined)
|
|
49
|
+
return { error: "target path could not be resolved" };
|
|
50
|
+
const suffix = path.relative(probe, absolute);
|
|
51
|
+
const realTarget = suffix ? path.join(realProbe, suffix) : realProbe;
|
|
52
|
+
for (const root of roots) {
|
|
53
|
+
let realRoot;
|
|
54
|
+
try {
|
|
55
|
+
realRoot = fs.realpathSync(root);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
realRoot = root;
|
|
59
|
+
}
|
|
60
|
+
const relative = path.relative(realRoot, realTarget);
|
|
61
|
+
if (relative && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative)) {
|
|
62
|
+
// Report the root as the caller supplied it (resolved but not realpathed)
|
|
63
|
+
// so callers can compare it against their own roots — on macOS a temp dir
|
|
64
|
+
// realpaths from /var to /private/var and would never match otherwise.
|
|
65
|
+
return { path: realTarget, relative, root };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { error: "target path resolves outside the workspace and project folder" };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolve a root the same way the target was resolved. Comparing a realpath
|
|
72
|
+
* target against a lexical root misreads a symlinked project root (macOS
|
|
73
|
+
* /var → /private/var, or a worktree reached through a link) as external.
|
|
74
|
+
*/
|
|
75
|
+
function realRoot(root) {
|
|
76
|
+
const resolved = path.resolve(root);
|
|
77
|
+
try {
|
|
78
|
+
return fs.realpathSync(resolved);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return resolved;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Mirror of OpenCode's `containsPath`: a target inside the session directory or
|
|
86
|
+
* worktree is internal. A worktree of `/` is skipped there because it would
|
|
87
|
+
* match every absolute path and silently disable the external gate.
|
|
88
|
+
*/
|
|
89
|
+
function isInsideProject(target, worktree, directory) {
|
|
90
|
+
for (const root of [directory, worktree]) {
|
|
91
|
+
if (!root || root === "/")
|
|
92
|
+
continue;
|
|
93
|
+
const relative = path.relative(realRoot(root), target);
|
|
94
|
+
if (relative === "")
|
|
95
|
+
return true;
|
|
96
|
+
if (relative && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative))
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Execute the commit. `image_id` is the only input: the path and bytes come
|
|
103
|
+
* from this process's staging table, so a caller without a real handle — the
|
|
104
|
+
* model of any provider, replaying a used id, or guessing — writes nothing.
|
|
105
|
+
*/
|
|
106
|
+
export async function executeCursorImageSave(args, ctx) {
|
|
107
|
+
const imageId = typeof args.image_id === "string" ? args.image_id : "";
|
|
108
|
+
if (!imageId)
|
|
109
|
+
return "No image id was provided, so there is nothing to save.";
|
|
110
|
+
const image = takePendingCursorImage(imageId);
|
|
111
|
+
if (!image) {
|
|
112
|
+
// Expired, already committed, or never existed. Identical response for all
|
|
113
|
+
// three: nothing here should confirm whether an id was ever valid.
|
|
114
|
+
return "No pending Cursor image matches that id. It may have already been saved or expired.";
|
|
115
|
+
}
|
|
116
|
+
const workspace = ctx.worktree || ctx.directory;
|
|
117
|
+
const contained = resolveContainedImagePath(image.path, [image.projectDir, workspace].filter((root) => !!root));
|
|
118
|
+
if ("error" in contained) {
|
|
119
|
+
trace(`image save: refused path=${JSON.stringify(image.path)} reason=${contained.error}`);
|
|
120
|
+
return `Refusing to save the generated image: ${contained.error}.`;
|
|
121
|
+
}
|
|
122
|
+
// Mirror what OpenCode's own `write` tool does, in the same order: an
|
|
123
|
+
// external-directory gate for targets outside the project, then the `edit`
|
|
124
|
+
// permission. Cursor writes generated images into the project folder, which
|
|
125
|
+
// is under the host cache and therefore *outside* the worktree — exactly the
|
|
126
|
+
// case `assertExternalDirectory` exists for. Asking only for `edit` would
|
|
127
|
+
// quietly skip a boundary the host enforces for every one of its own writes.
|
|
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("\\", "/");
|
|
133
|
+
await ctx.ask({
|
|
134
|
+
permission: "external_directory",
|
|
135
|
+
patterns: [glob],
|
|
136
|
+
always: [glob],
|
|
137
|
+
metadata: { filepath: contained.path, parentDir },
|
|
138
|
+
});
|
|
139
|
+
}
|
|
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
|
+
});
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
154
|
+
trace(`image save: permission refused path=${JSON.stringify(contained.path)} reason=${reason}`);
|
|
155
|
+
throw new Error(`${IMAGE_PERMISSION_DENIED_PREFIX} ${reason}`);
|
|
156
|
+
}
|
|
157
|
+
// Cursor writes generated images into an `assets/` subdirectory that will not
|
|
158
|
+
// exist on a first generation, so the parent chain is created here — after
|
|
159
|
+
// the permission, so a refusal leaves no directories behind.
|
|
160
|
+
const directory = path.dirname(contained.path);
|
|
161
|
+
try {
|
|
162
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
163
|
+
fs.writeFileSync(contained.path, image.data);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
// Most often something already occupies a path component (EEXIST/ENOTDIR).
|
|
167
|
+
// Say which path is blocked; the raw errno alone tells the model nothing.
|
|
168
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
169
|
+
trace(`image save: write failed path=${JSON.stringify(contained.path)} reason=${reason}`);
|
|
170
|
+
throw new Error(`Could not save the generated image to ${contained.path}: ${reason}. `
|
|
171
|
+
+ `Check that ${directory} is a writable directory.`);
|
|
172
|
+
}
|
|
173
|
+
trace(`image save: wrote path=${JSON.stringify(contained.path)} ` +
|
|
174
|
+
`bytes=${image.data.length} mime=${image.mime}`);
|
|
175
|
+
// No `attachments`, deliberately. OpenCode gates media-in-tool-result on an
|
|
176
|
+
// allowlist of `model.api.npm` values (@ai-sdk/anthropic, @ai-sdk/openai, …)
|
|
177
|
+
// that no third-party provider can join, so an image attachment is always
|
|
178
|
+
// "extracted to be sent as a separate user message" (message-v2.ts:299-305).
|
|
179
|
+
// That trailing user message makes the next turn stop looking like a tool
|
|
180
|
+
// continuation, so the provider opens a fresh Run and Cursor never receives
|
|
181
|
+
// the write result it is waiting for — observed live: the Run was superseded
|
|
182
|
+
// with pending=1 and a full 1.7 MB rebase was paid. Text only keeps the
|
|
183
|
+
// held-Run contract intact; the file is on disk and Cursor holds the image
|
|
184
|
+
// server-side already.
|
|
185
|
+
return {
|
|
186
|
+
title: editPattern,
|
|
187
|
+
output: `Saved the generated image to ${contained.path} (${image.data.length} bytes).`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Staging for images Cursor generated server-side.
|
|
3
|
+
*
|
|
4
|
+
* Cursor's GenerateImage runs on its servers and hands the client finished
|
|
5
|
+
* bytes. OpenCode's `write` tool takes a string and additionally BOM-splits,
|
|
6
|
+
* diffs, and runs `Format.file()` on what it writes, so image bytes cannot pass
|
|
7
|
+
* through it — the bytes have to be written by provider code.
|
|
8
|
+
*
|
|
9
|
+
* That write still must raise OpenCode's permission, and `ctx.ask` is only
|
|
10
|
+
* reachable inside a plugin tool's `execute`. So the bytes wait here, keyed by
|
|
11
|
+
* a single-use id, and the tool that commits them takes only that id: no path
|
|
12
|
+
* and no content ever crosses the tool boundary. A tool visible in the catalog
|
|
13
|
+
* therefore cannot be used to write arbitrary bytes anywhere — it can only
|
|
14
|
+
* commit an image this provider already received, or do nothing.
|
|
15
|
+
*
|
|
16
|
+
* Approach adapted from jkalasas/opencode-antigravity-image (MIT), which
|
|
17
|
+
* established writing image bytes from inside a plugin tool rather than through
|
|
18
|
+
* the host's text `write`. No code was copied; the permission gate, the
|
|
19
|
+
* handle-only tool surface, and the containment checks are this provider's.
|
|
20
|
+
*/
|
|
21
|
+
export type PendingCursorImage = {
|
|
22
|
+
id: string;
|
|
23
|
+
/** Absolute target, already remapped onto a real location under this host. */
|
|
24
|
+
path: string;
|
|
25
|
+
/** Host project folder the target was mapped into; an allowed write root. */
|
|
26
|
+
projectDir?: string;
|
|
27
|
+
mime: string;
|
|
28
|
+
data: Uint8Array;
|
|
29
|
+
/** OpenCode session that may commit this image. */
|
|
30
|
+
sessionId?: string;
|
|
31
|
+
createdAt: number;
|
|
32
|
+
};
|
|
33
|
+
export type StageCursorImageInput = {
|
|
34
|
+
path: string;
|
|
35
|
+
projectDir?: string;
|
|
36
|
+
mime: string;
|
|
37
|
+
data: Uint8Array;
|
|
38
|
+
sessionId?: string;
|
|
39
|
+
};
|
|
40
|
+
/** Cursor's own edit ceiling is 50 MB; images are far smaller in practice. */
|
|
41
|
+
export declare const MAX_STAGED_IMAGE_BYTES: number;
|
|
42
|
+
/** A staged image is committed within one tool round-trip or it is stale. */
|
|
43
|
+
export declare const STAGED_IMAGE_TTL_MS: number;
|
|
44
|
+
export declare class StagedImageTooLargeError extends Error {
|
|
45
|
+
constructor(bytes: number);
|
|
46
|
+
}
|
|
47
|
+
/** Hold generated bytes and return the handle the commit tool will be given. */
|
|
48
|
+
export declare function stageCursorImage(input: StageCursorImageInput, now?: number): string;
|
|
49
|
+
/**
|
|
50
|
+
* Claim a staged image. Single use: a replayed or guessed id finds nothing, so
|
|
51
|
+
* the commit tool cannot be driven twice or by a caller that never had a real
|
|
52
|
+
* handle.
|
|
53
|
+
*/
|
|
54
|
+
export declare function takePendingCursorImage(id: string, now?: number): PendingCursorImage | undefined;
|
|
55
|
+
/** Drop a staged image whose turn ended without committing it. */
|
|
56
|
+
export declare function discardPendingCursorImage(id: string): void;
|
|
57
|
+
/** Test seam. */
|
|
58
|
+
export declare function clearPendingCursorImages(): void;
|
|
59
|
+
export declare function pendingCursorImageCount(): number;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Staging for images Cursor generated server-side.
|
|
3
|
+
*
|
|
4
|
+
* Cursor's GenerateImage runs on its servers and hands the client finished
|
|
5
|
+
* bytes. OpenCode's `write` tool takes a string and additionally BOM-splits,
|
|
6
|
+
* diffs, and runs `Format.file()` on what it writes, so image bytes cannot pass
|
|
7
|
+
* through it — the bytes have to be written by provider code.
|
|
8
|
+
*
|
|
9
|
+
* That write still must raise OpenCode's permission, and `ctx.ask` is only
|
|
10
|
+
* reachable inside a plugin tool's `execute`. So the bytes wait here, keyed by
|
|
11
|
+
* a single-use id, and the tool that commits them takes only that id: no path
|
|
12
|
+
* and no content ever crosses the tool boundary. A tool visible in the catalog
|
|
13
|
+
* therefore cannot be used to write arbitrary bytes anywhere — it can only
|
|
14
|
+
* commit an image this provider already received, or do nothing.
|
|
15
|
+
*
|
|
16
|
+
* Approach adapted from jkalasas/opencode-antigravity-image (MIT), which
|
|
17
|
+
* established writing image bytes from inside a plugin tool rather than through
|
|
18
|
+
* the host's text `write`. No code was copied; the permission gate, the
|
|
19
|
+
* handle-only tool surface, and the containment checks are this provider's.
|
|
20
|
+
*/
|
|
21
|
+
import { randomUUID } from "node:crypto";
|
|
22
|
+
/** Cursor's own edit ceiling is 50 MB; images are far smaller in practice. */
|
|
23
|
+
export const MAX_STAGED_IMAGE_BYTES = 50 * 1024 * 1024;
|
|
24
|
+
/** A staged image is committed within one tool round-trip or it is stale. */
|
|
25
|
+
export const STAGED_IMAGE_TTL_MS = 10 * 60_000;
|
|
26
|
+
/** Bounds memory if a turn generates several images and none are committed. */
|
|
27
|
+
const MAX_PENDING = 8;
|
|
28
|
+
const pending = new Map();
|
|
29
|
+
export class StagedImageTooLargeError extends Error {
|
|
30
|
+
constructor(bytes) {
|
|
31
|
+
super(`Generated image is ${bytes} bytes, above the ${MAX_STAGED_IMAGE_BYTES} byte limit`);
|
|
32
|
+
this.name = "StagedImageTooLargeError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function prune(now) {
|
|
36
|
+
for (const [id, image] of pending) {
|
|
37
|
+
if (now - image.createdAt > STAGED_IMAGE_TTL_MS)
|
|
38
|
+
pending.delete(id);
|
|
39
|
+
}
|
|
40
|
+
// Oldest-first eviction: Map preserves insertion order and ids are only ever
|
|
41
|
+
// appended, so the first entries are the oldest.
|
|
42
|
+
while (pending.size >= MAX_PENDING) {
|
|
43
|
+
const oldest = pending.keys().next().value;
|
|
44
|
+
if (oldest === undefined)
|
|
45
|
+
break;
|
|
46
|
+
pending.delete(oldest);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Hold generated bytes and return the handle the commit tool will be given. */
|
|
50
|
+
export function stageCursorImage(input, now = Date.now()) {
|
|
51
|
+
if (input.data.length > MAX_STAGED_IMAGE_BYTES) {
|
|
52
|
+
throw new StagedImageTooLargeError(input.data.length);
|
|
53
|
+
}
|
|
54
|
+
prune(now);
|
|
55
|
+
const id = `cursor-image-${randomUUID()}`;
|
|
56
|
+
pending.set(id, {
|
|
57
|
+
id,
|
|
58
|
+
path: input.path,
|
|
59
|
+
projectDir: input.projectDir,
|
|
60
|
+
mime: input.mime,
|
|
61
|
+
data: input.data,
|
|
62
|
+
sessionId: input.sessionId,
|
|
63
|
+
createdAt: now,
|
|
64
|
+
});
|
|
65
|
+
return id;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Claim a staged image. Single use: a replayed or guessed id finds nothing, so
|
|
69
|
+
* the commit tool cannot be driven twice or by a caller that never had a real
|
|
70
|
+
* handle.
|
|
71
|
+
*/
|
|
72
|
+
export function takePendingCursorImage(id, now = Date.now()) {
|
|
73
|
+
const image = pending.get(id);
|
|
74
|
+
if (!image)
|
|
75
|
+
return undefined;
|
|
76
|
+
pending.delete(id);
|
|
77
|
+
if (now - image.createdAt > STAGED_IMAGE_TTL_MS)
|
|
78
|
+
return undefined;
|
|
79
|
+
return image;
|
|
80
|
+
}
|
|
81
|
+
/** Drop a staged image whose turn ended without committing it. */
|
|
82
|
+
export function discardPendingCursorImage(id) {
|
|
83
|
+
pending.delete(id);
|
|
84
|
+
}
|
|
85
|
+
/** Test seam. */
|
|
86
|
+
export function clearPendingCursorImages() {
|
|
87
|
+
pending.clear();
|
|
88
|
+
}
|
|
89
|
+
export function pendingCursorImageCount() {
|
|
90
|
+
return pending.size;
|
|
91
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type CreateCursorOptions = {
|
|
|
26
26
|
/**
|
|
27
27
|
* Host cache root for Cursor project metadata + model/version caches.
|
|
28
28
|
* Prefer the host's Path.cache (Effect v2) when available; otherwise the
|
|
29
|
-
* provider resolves OpenCode
|
|
29
|
+
* provider resolves the native OpenCode cache dir, or an injected host path bridge.
|
|
30
30
|
*/
|
|
31
31
|
cacheDir?: string;
|
|
32
32
|
/** Held-stream policy. Defaults: heartbeat 5s, semantic idle 120s, tool inactivity 10m. */
|
package/dist/language-model.d.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3StreamPart } from "@ai-sdk/provider";
|
|
2
2
|
import type { CreateCursorOptions, CursorRetryOptions } from "./index.js";
|
|
3
3
|
import { type OpencodeToolDef } from "./protocol/tools.js";
|
|
4
|
+
import { cursorContextUsageMetadata, type CursorContextUsageSource, type CursorConversationTokenDetails } from "./protocol/token-details.js";
|
|
5
|
+
import { type ConversationBlobGraphStats } from "./protocol/blob-store.js";
|
|
4
6
|
import { type CursorSession } from "./session.js";
|
|
5
7
|
import { CursorProviderError } from "./errors.js";
|
|
6
8
|
import type { SeedHistoryMessage } from "./protocol/request.js";
|
|
7
9
|
export declare const MAX_TURN_STATE_SESSIONS = 256;
|
|
10
|
+
/** Cursor's own conversation export path treats state above 100 MiB as large. */
|
|
11
|
+
export declare const MAX_CHECKPOINT_BLOB_GRAPH_BYTES: number;
|
|
12
|
+
export declare function checkpointBlobGraphRequiresRebase(stats: ConversationBlobGraphStats, maxBytes?: number): boolean;
|
|
8
13
|
export type CursorRetryPolicy = {
|
|
9
14
|
maxAttempts: number;
|
|
10
15
|
baseDelayMs: number;
|
|
@@ -12,13 +17,14 @@ export type CursorRetryPolicy = {
|
|
|
12
17
|
};
|
|
13
18
|
export declare function resolveRetryPolicy(options: CursorRetryOptions | undefined): CursorRetryPolicy;
|
|
14
19
|
export declare function connectFrameError(payload: string): CursorProviderError;
|
|
20
|
+
/** Restore the last real catalog solely for lifecycle turns such as compaction. */
|
|
21
|
+
export declare function restoreTurnToolCatalog(sessionKey: string, tools: OpencodeToolDef[]): void;
|
|
15
22
|
type V3Part = LanguageModelV3StreamPart;
|
|
16
23
|
export declare function createCursorLanguageModel(modelId: string, providerId: string, options: CreateCursorOptions): LanguageModelV3;
|
|
17
24
|
export declare function pumpWithRecovery(input: {
|
|
18
25
|
initialSession: CursorSession;
|
|
19
26
|
controller: ReadableStreamDefaultController<V3Part>;
|
|
20
27
|
abortSignal?: AbortSignal;
|
|
21
|
-
promptTokens?: number;
|
|
22
28
|
retryPolicy?: CursorRetryPolicy;
|
|
23
29
|
recover: (recovery: CursorRunRecovery) => Promise<CursorSession>;
|
|
24
30
|
onSession?: (session: CursorSession) => void;
|
|
@@ -56,10 +62,6 @@ export declare function deliverContinuationResults(session: CursorSession, trail
|
|
|
56
62
|
export declare function pump(session: CursorSession, controller: ReadableStreamDefaultController<V3Part>, ids: {
|
|
57
63
|
textId: string;
|
|
58
64
|
reasoningId: string;
|
|
59
|
-
promptTokens?: number;
|
|
60
|
-
requestUsage?: {
|
|
61
|
-
outputChars: number;
|
|
62
|
-
};
|
|
63
65
|
}, abortSignal?: AbortSignal): Promise<void>;
|
|
64
66
|
type ExtractedToolResult = {
|
|
65
67
|
toolCallId: string;
|
|
@@ -83,10 +85,8 @@ export declare function extractTrailingToolResults(prompt: LanguageModelV3CallOp
|
|
|
83
85
|
export declare function buildOpenCodeInteractionGuidance(tools: OpencodeToolDef[], isCompaction: boolean, workspaceRoot: string): string | undefined;
|
|
84
86
|
/** Rough char→token estimate for mid-turn usage before TurnEnded arrives. */
|
|
85
87
|
export declare function estimateTokens(chars: number): number;
|
|
86
|
-
/**
|
|
87
|
-
export declare function
|
|
88
|
-
/** Preserve exact Cursor counters as diagnostics when they are cumulative across a multi-step Run. */
|
|
89
|
-
export declare function cursorTurnEndedProviderMetadata(te: Record<string, unknown>): {
|
|
88
|
+
/** Preserve exact request-local Cursor counters as diagnostics. */
|
|
89
|
+
export declare function cursorTurnEndedProviderMetadata(te: Record<string, unknown>, tokenDetails?: CursorConversationTokenDetails, contextSource?: CursorContextUsageSource): {
|
|
90
90
|
cursor: {
|
|
91
91
|
usageVersion: number;
|
|
92
92
|
inputTokensRaw: number;
|
|
@@ -94,6 +94,7 @@ export declare function cursorTurnEndedProviderMetadata(te: Record<string, unkno
|
|
|
94
94
|
cacheReadRaw: number;
|
|
95
95
|
cacheWriteRaw: number;
|
|
96
96
|
reasoningTokensRaw: number;
|
|
97
|
+
context?: ReturnType<typeof cursorContextUsageMetadata>;
|
|
97
98
|
};
|
|
98
99
|
};
|
|
99
100
|
/**
|
|
@@ -134,10 +135,11 @@ export declare function resolveTurnToolState(input: {
|
|
|
134
135
|
incomingTools: OpencodeToolDef[];
|
|
135
136
|
toolChoice?: LanguageModelV3CallOptions["toolChoice"];
|
|
136
137
|
isCompaction: boolean;
|
|
137
|
-
|
|
138
|
+
abortSignal?: AbortSignal;
|
|
139
|
+
}): Promise<{
|
|
138
140
|
advertisedTools: OpencodeToolDef[];
|
|
139
141
|
allowTools: boolean;
|
|
140
|
-
}
|
|
142
|
+
}>;
|
|
141
143
|
export declare function resolveTurnConversationReset(input: {
|
|
142
144
|
sessionKey?: string;
|
|
143
145
|
isCompaction: boolean;
|