cursor-opencode-provider 0.1.1

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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -0
  3. package/dist/auth.d.ts +48 -0
  4. package/dist/auth.js +200 -0
  5. package/dist/context/agents.d.ts +8 -0
  6. package/dist/context/agents.js +76 -0
  7. package/dist/context/build.d.ts +12 -0
  8. package/dist/context/build.js +83 -0
  9. package/dist/context/env.d.ts +1 -0
  10. package/dist/context/env.js +29 -0
  11. package/dist/context/git.d.ts +20 -0
  12. package/dist/context/git.js +59 -0
  13. package/dist/context/index.d.ts +2 -0
  14. package/dist/context/index.js +2 -0
  15. package/dist/context/layout.d.ts +17 -0
  16. package/dist/context/layout.js +58 -0
  17. package/dist/context/paths.d.ts +3 -0
  18. package/dist/context/paths.js +11 -0
  19. package/dist/context/plugins.d.ts +8 -0
  20. package/dist/context/plugins.js +50 -0
  21. package/dist/context/rules.d.ts +19 -0
  22. package/dist/context/rules.js +198 -0
  23. package/dist/context/skills.d.ts +11 -0
  24. package/dist/context/skills.js +104 -0
  25. package/dist/index.d.ts +15 -0
  26. package/dist/index.js +18 -0
  27. package/dist/language-model.d.ts +45 -0
  28. package/dist/language-model.js +834 -0
  29. package/dist/models.d.ts +49 -0
  30. package/dist/models.js +136 -0
  31. package/dist/plugin-v2.d.ts +2 -0
  32. package/dist/plugin-v2.js +48 -0
  33. package/dist/plugin.d.ts +2 -0
  34. package/dist/plugin.js +201 -0
  35. package/dist/protocol/blob-store.d.ts +15 -0
  36. package/dist/protocol/blob-store.js +52 -0
  37. package/dist/protocol/checkpoint.d.ts +17 -0
  38. package/dist/protocol/checkpoint.js +29 -0
  39. package/dist/protocol/checksum.d.ts +2 -0
  40. package/dist/protocol/checksum.js +23 -0
  41. package/dist/protocol/client-version.d.ts +5 -0
  42. package/dist/protocol/client-version.js +150 -0
  43. package/dist/protocol/device-id.d.ts +8 -0
  44. package/dist/protocol/device-id.js +121 -0
  45. package/dist/protocol/framing.d.ts +10 -0
  46. package/dist/protocol/framing.js +90 -0
  47. package/dist/protocol/kv.d.ts +24 -0
  48. package/dist/protocol/kv.js +81 -0
  49. package/dist/protocol/messages.d.ts +11 -0
  50. package/dist/protocol/messages.js +676 -0
  51. package/dist/protocol/request.d.ts +36 -0
  52. package/dist/protocol/request.js +90 -0
  53. package/dist/protocol/stream.d.ts +38 -0
  54. package/dist/protocol/stream.js +64 -0
  55. package/dist/protocol/struct.d.ts +19 -0
  56. package/dist/protocol/struct.js +186 -0
  57. package/dist/protocol/thinking.d.ts +15 -0
  58. package/dist/protocol/thinking.js +17 -0
  59. package/dist/protocol/tools.d.ts +94 -0
  60. package/dist/protocol/tools.js +631 -0
  61. package/dist/session.d.ts +81 -0
  62. package/dist/session.js +96 -0
  63. package/dist/shared.d.ts +15 -0
  64. package/dist/shared.js +13 -0
  65. package/dist/transport/connect.d.ts +23 -0
  66. package/dist/transport/connect.js +275 -0
  67. package/package.json +65 -0
@@ -0,0 +1,20 @@
1
+ export type RepoInfo = {
2
+ relative_workspace_path: string;
3
+ remote_urls: string[];
4
+ remote_names: string[];
5
+ repo_name: string;
6
+ repo_owner: string;
7
+ is_tracked: boolean;
8
+ is_local: boolean;
9
+ workspace_uri: string;
10
+ };
11
+ export type GitRepoInfo = {
12
+ path: string;
13
+ status: string;
14
+ branch_name: string;
15
+ remote_url?: string;
16
+ };
17
+ export declare function collectGit(workspaceRoot: string): Promise<{
18
+ repositoryInfo: RepoInfo[];
19
+ gitRepos: GitRepoInfo[];
20
+ }>;
@@ -0,0 +1,59 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import path from "node:path";
4
+ const execFileAsync = promisify(execFile);
5
+ async function git(cwd, args) {
6
+ try {
7
+ const { stdout } = await execFileAsync("git", args, { cwd, encoding: "utf-8", timeout: 5000 });
8
+ return stdout.trim();
9
+ }
10
+ catch {
11
+ return "";
12
+ }
13
+ }
14
+ export async function collectGit(workspaceRoot) {
15
+ const root = await git(workspaceRoot, ["rev-parse", "--show-toplevel"]);
16
+ if (!root)
17
+ return { repositoryInfo: [], gitRepos: [] };
18
+ const remotesRaw = await git(root, ["remote", "-v"]);
19
+ const remote_urls = [];
20
+ const remote_names = [];
21
+ for (const line of remotesRaw.split("\n")) {
22
+ const m = line.match(/^(\S+)\s+(\S+)\s+\(fetch\)/);
23
+ if (!m)
24
+ continue;
25
+ remote_names.push(m[1]);
26
+ remote_urls.push(m[2]);
27
+ }
28
+ const primary = remote_urls[0] ?? "";
29
+ let repo_owner = "";
30
+ let repo_name = path.basename(root);
31
+ const gh = primary.match(/[:/]([^/]+)\/([^/]+?)(?:\.git)?$/);
32
+ if (gh) {
33
+ repo_owner = gh[1];
34
+ repo_name = gh[2];
35
+ }
36
+ const branch = (await git(root, ["rev-parse", "--abbrev-ref", "HEAD"])) || "HEAD";
37
+ const status = await git(root, ["status", "--porcelain", "-b"]);
38
+ const repositoryInfo = [
39
+ {
40
+ relative_workspace_path: ".",
41
+ remote_urls,
42
+ remote_names,
43
+ repo_name,
44
+ repo_owner,
45
+ is_tracked: remote_urls.length > 0,
46
+ is_local: remote_urls.length === 0,
47
+ workspace_uri: `file://${root}`,
48
+ },
49
+ ];
50
+ const gitRepos = [
51
+ {
52
+ path: root,
53
+ status: status.slice(0, 4000),
54
+ branch_name: branch,
55
+ ...(primary ? { remote_url: primary } : {}),
56
+ },
57
+ ];
58
+ return { repositoryInfo, gitRepos };
59
+ }
@@ -0,0 +1,2 @@
1
+ export { buildRequestContext, type BuildRequestContextInput } from "./build.js";
2
+ export { opencodeGlobalConfigDir } from "./paths.js";
@@ -0,0 +1,2 @@
1
+ export { buildRequestContext } from "./build.js";
2
+ export { opencodeGlobalConfigDir } from "./paths.js";
@@ -0,0 +1,17 @@
1
+ export type LayoutNode = {
2
+ abs_path: string;
3
+ children_dirs: LayoutNode[];
4
+ children_files: Array<{
5
+ name: string;
6
+ }>;
7
+ children_were_processed: boolean;
8
+ num_files: number;
9
+ };
10
+ /**
11
+ * Shallow project layout for RequestContext.project_layouts.
12
+ * Caps depth and entries to keep the payload small.
13
+ */
14
+ export declare function collectProjectLayout(workspaceRoot: string, opts?: {
15
+ maxDepth?: number;
16
+ maxEntries?: number;
17
+ }): Promise<LayoutNode>;
@@ -0,0 +1,58 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ const SKIP = new Set(["node_modules", ".git", "dist", "build", ".next", "coverage", ".turbo"]);
4
+ /**
5
+ * Shallow project layout for RequestContext.project_layouts.
6
+ * Caps depth and entries to keep the payload small.
7
+ */
8
+ export async function collectProjectLayout(workspaceRoot, opts = {}) {
9
+ const maxDepth = opts.maxDepth ?? 2;
10
+ const maxEntries = opts.maxEntries ?? 80;
11
+ return walk(path.resolve(workspaceRoot), 0, maxDepth, maxEntries);
12
+ }
13
+ async function walk(dir, depth, maxDepth, maxEntries) {
14
+ const node = {
15
+ abs_path: dir,
16
+ children_dirs: [],
17
+ children_files: [],
18
+ children_were_processed: depth < maxDepth,
19
+ num_files: 0,
20
+ };
21
+ if (depth >= maxDepth)
22
+ return node;
23
+ let names;
24
+ try {
25
+ names = await readdir(dir);
26
+ }
27
+ catch {
28
+ node.children_were_processed = false;
29
+ return node;
30
+ }
31
+ names.sort();
32
+ let count = 0;
33
+ for (const name of names) {
34
+ if (count >= maxEntries)
35
+ break;
36
+ if (name.startsWith(".") && name !== ".opencode" && name !== ".claude" && name !== ".agents")
37
+ continue;
38
+ if (SKIP.has(name))
39
+ continue;
40
+ const full = path.join(dir, name);
41
+ let st;
42
+ try {
43
+ st = await stat(full);
44
+ }
45
+ catch {
46
+ continue;
47
+ }
48
+ count++;
49
+ if (st.isDirectory()) {
50
+ node.children_dirs.push(await walk(full, depth + 1, maxDepth, maxEntries));
51
+ }
52
+ else {
53
+ node.children_files.push({ name });
54
+ node.num_files++;
55
+ }
56
+ }
57
+ return node;
58
+ }
@@ -0,0 +1,3 @@
1
+ /** OpenCode global config dir (`~/.config/opencode`). */
2
+ export declare function opencodeGlobalConfigDir(): string;
3
+ export declare function resolveHomeRelative(p: string): string;
@@ -0,0 +1,11 @@
1
+ import { homedir } from "node:os";
2
+ import path from "node:path";
3
+ /** OpenCode global config dir (`~/.config/opencode`). */
4
+ export function opencodeGlobalConfigDir() {
5
+ return path.join(homedir(), ".config", "opencode");
6
+ }
7
+ export function resolveHomeRelative(p) {
8
+ if (p.startsWith("~/"))
9
+ return path.join(homedir(), p.slice(2));
10
+ return p;
11
+ }
@@ -0,0 +1,8 @@
1
+ import type { OpencodeJson } from "./rules.js";
2
+ export type CollectedPlugin = {
3
+ id: string;
4
+ source: "npm" | "local";
5
+ path?: string;
6
+ };
7
+ /** OpenCode plugins from config + local plugin directories (metadata only). */
8
+ export declare function collectPlugins(workspaceRoot: string, config: OpencodeJson): Promise<CollectedPlugin[]>;
@@ -0,0 +1,50 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { opencodeGlobalConfigDir } from "./paths.js";
4
+ async function listLocalPlugins(dir) {
5
+ try {
6
+ await stat(dir);
7
+ }
8
+ catch {
9
+ return [];
10
+ }
11
+ const out = [];
12
+ let entries;
13
+ try {
14
+ entries = await readdir(dir);
15
+ }
16
+ catch {
17
+ return [];
18
+ }
19
+ for (const name of entries) {
20
+ if (!/\.(m?[jt]s)$/.test(name))
21
+ continue;
22
+ const full = path.join(dir, name);
23
+ out.push({ id: name.replace(/\.(m?[jt]s)$/, ""), source: "local", path: full });
24
+ }
25
+ return out;
26
+ }
27
+ /** OpenCode plugins from config + local plugin directories (metadata only). */
28
+ export async function collectPlugins(workspaceRoot, config) {
29
+ const out = [];
30
+ const seen = new Set();
31
+ for (const id of [...(config.plugin ?? []), ...(config.plugins ?? [])]) {
32
+ if (!id || seen.has(id))
33
+ continue;
34
+ seen.add(id);
35
+ out.push({ id, source: "npm" });
36
+ }
37
+ for (const p of await listLocalPlugins(path.join(workspaceRoot, ".opencode", "plugins"))) {
38
+ if (seen.has(p.id))
39
+ continue;
40
+ seen.add(p.id);
41
+ out.push(p);
42
+ }
43
+ for (const p of await listLocalPlugins(path.join(opencodeGlobalConfigDir(), "plugins"))) {
44
+ if (seen.has(p.id))
45
+ continue;
46
+ seen.add(p.id);
47
+ out.push(p);
48
+ }
49
+ return out;
50
+ }
@@ -0,0 +1,19 @@
1
+ export type CollectedRule = {
2
+ fullPath: string;
3
+ content: string;
4
+ };
5
+ export type OpencodeJson = {
6
+ instructions?: string[];
7
+ permission?: unknown;
8
+ plugin?: string[];
9
+ plugins?: string[];
10
+ };
11
+ export declare function loadMergedConfig(workspaceRoot: string): Promise<OpencodeJson>;
12
+ /**
13
+ * Collect OpenCode instruction files.
14
+ */
15
+ export declare function collectRules(workspaceRoot: string): Promise<{
16
+ rules: CollectedRule[];
17
+ config: OpencodeJson;
18
+ worktree: string;
19
+ }>;
@@ -0,0 +1,198 @@
1
+ import { readFile, readdir, stat } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import path from "node:path";
4
+ import { opencodeGlobalConfigDir, resolveHomeRelative } from "./paths.js";
5
+ async function exists(file) {
6
+ try {
7
+ await stat(file);
8
+ return true;
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
14
+ async function readJsonConfig(dir) {
15
+ for (const name of ["opencode.json", "opencode.jsonc"]) {
16
+ const file = path.join(dir, name);
17
+ if (!(await exists(file)))
18
+ continue;
19
+ try {
20
+ const raw = await readFile(file, "utf-8");
21
+ const stripped = raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
22
+ return JSON.parse(stripped);
23
+ }
24
+ catch {
25
+ return {};
26
+ }
27
+ }
28
+ return {};
29
+ }
30
+ 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
+ for (const name of entries) {
113
+ if (name === "node_modules" || name === ".git")
114
+ continue;
115
+ const full = path.join(dir, name);
116
+ let st;
117
+ try {
118
+ st = await stat(full);
119
+ }
120
+ catch {
121
+ continue;
122
+ }
123
+ if (st.isDirectory())
124
+ await walk(full, depth + 1);
125
+ else if (regex.test(full.replace(/\\/g, "/")))
126
+ out.push(full);
127
+ }
128
+ }
129
+ await walk(startDir, 0);
130
+ return out;
131
+ }
132
+ export async function loadMergedConfig(workspaceRoot) {
133
+ const globalConfig = await readJsonConfig(opencodeGlobalConfigDir());
134
+ const projectConfig = await readJsonConfig(workspaceRoot);
135
+ return {
136
+ ...globalConfig,
137
+ ...projectConfig,
138
+ instructions: [...(globalConfig.instructions ?? []), ...(projectConfig.instructions ?? [])],
139
+ plugin: [...new Set([...(globalConfig.plugin ?? []), ...(projectConfig.plugin ?? [])])],
140
+ plugins: [...new Set([...(globalConfig.plugins ?? []), ...(projectConfig.plugins ?? [])])],
141
+ permission: projectConfig.permission ?? globalConfig.permission,
142
+ };
143
+ }
144
+ /**
145
+ * Collect OpenCode instruction files.
146
+ */
147
+ export async function collectRules(workspaceRoot) {
148
+ const worktree = await findGitWorktree(workspaceRoot);
149
+ const rules = [];
150
+ const seen = new Set();
151
+ const config = await loadMergedConfig(workspaceRoot);
152
+ const add = async (file) => {
153
+ if (!file)
154
+ return;
155
+ const resolved = path.resolve(file);
156
+ if (seen.has(resolved))
157
+ return;
158
+ const rule = await readRule(resolved);
159
+ if (!rule)
160
+ return;
161
+ seen.add(resolved);
162
+ rules.push(rule);
163
+ };
164
+ for (const name of ["AGENTS.md", "CLAUDE.md", "CONTEXT.md"]) {
165
+ const hit = await findUp(name, workspaceRoot, worktree);
166
+ if (hit) {
167
+ await add(hit);
168
+ break;
169
+ }
170
+ }
171
+ await add(path.join(opencodeGlobalConfigDir(), "AGENTS.md"));
172
+ await add(path.join(homedir(), ".claude", "CLAUDE.md"));
173
+ for (const raw of config.instructions ?? []) {
174
+ if (raw.startsWith("http://") || raw.startsWith("https://")) {
175
+ try {
176
+ const ctrl = new AbortController();
177
+ const t = setTimeout(() => ctrl.abort(), 5000);
178
+ const res = await fetch(raw, { signal: ctrl.signal });
179
+ clearTimeout(t);
180
+ if (!res.ok)
181
+ continue;
182
+ const content = await res.text();
183
+ if (!content.trim() || seen.has(raw))
184
+ continue;
185
+ seen.add(raw);
186
+ rules.push({ fullPath: raw, content });
187
+ }
188
+ catch {
189
+ /* ignore */
190
+ }
191
+ continue;
192
+ }
193
+ const expanded = resolveHomeRelative(raw);
194
+ for (const m of await expandGlob(expanded, workspaceRoot))
195
+ await add(m);
196
+ }
197
+ return { rules, config, worktree };
198
+ }
@@ -0,0 +1,11 @@
1
+ export type CollectedSkill = {
2
+ fullPath: string;
3
+ name: string;
4
+ description: string;
5
+ content: string;
6
+ };
7
+ /**
8
+ * Discover skills the way OpenCode does: `.opencode/skills`, global opencode,
9
+ * plus `.claude/skills` and `.agents/skills` (project + home).
10
+ */
11
+ export declare function collectSkills(workspaceRoot: string, worktree: string): Promise<CollectedSkill[]>;
@@ -0,0 +1,104 @@
1
+ import { readdir, readFile, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { opencodeGlobalConfigDir } from "./paths.js";
5
+ async function exists(p) {
6
+ try {
7
+ await stat(p);
8
+ return true;
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
14
+ function parseFrontmatter(raw) {
15
+ if (!raw.startsWith("---"))
16
+ return { body: raw };
17
+ const end = raw.indexOf("\n---", 3);
18
+ if (end < 0)
19
+ return { body: raw };
20
+ const fm = raw.slice(3, end).trim();
21
+ const body = raw.slice(end + 4).replace(/^\n/, "");
22
+ let name;
23
+ let description;
24
+ for (const line of fm.split("\n")) {
25
+ const m = line.match(/^(\w+):\s*(.*)$/);
26
+ if (!m)
27
+ continue;
28
+ const key = m[1];
29
+ let val = m[2].trim();
30
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
31
+ val = val.slice(1, -1);
32
+ }
33
+ if (key === "name")
34
+ name = val;
35
+ if (key === "description")
36
+ description = val;
37
+ }
38
+ return { name, description, body };
39
+ }
40
+ async function loadSkillFile(file) {
41
+ try {
42
+ const raw = await readFile(file, "utf-8");
43
+ const { name, description, body } = parseFrontmatter(raw);
44
+ const dirName = path.basename(path.dirname(file));
45
+ return {
46
+ fullPath: path.resolve(file),
47
+ name: name || dirName,
48
+ description: description || "",
49
+ content: body.slice(0, 20_000),
50
+ };
51
+ }
52
+ catch {
53
+ return undefined;
54
+ }
55
+ }
56
+ async function scanSkillsRoot(root, out) {
57
+ if (!(await exists(root)))
58
+ return;
59
+ let entries;
60
+ try {
61
+ entries = await readdir(root);
62
+ }
63
+ catch {
64
+ return;
65
+ }
66
+ for (const name of entries) {
67
+ const skillMd = path.join(root, name, "SKILL.md");
68
+ if (!(await exists(skillMd)))
69
+ continue;
70
+ const skill = await loadSkillFile(skillMd);
71
+ if (!skill)
72
+ continue;
73
+ if (!out.has(skill.name))
74
+ out.set(skill.name, skill);
75
+ }
76
+ }
77
+ async function walkAncestorsFor(dir, rel, stop, out) {
78
+ let cur = path.resolve(dir);
79
+ const root = path.resolve(stop);
80
+ for (;;) {
81
+ await scanSkillsRoot(path.join(cur, rel), out);
82
+ if (cur === root)
83
+ break;
84
+ const parent = path.dirname(cur);
85
+ if (parent === cur)
86
+ break;
87
+ cur = parent;
88
+ }
89
+ }
90
+ /**
91
+ * Discover skills the way OpenCode does: `.opencode/skills`, global opencode,
92
+ * plus `.claude/skills` and `.agents/skills` (project + home).
93
+ */
94
+ export async function collectSkills(workspaceRoot, worktree) {
95
+ const out = new Map();
96
+ const home = homedir();
97
+ await walkAncestorsFor(workspaceRoot, path.join(".opencode", "skills"), worktree, out);
98
+ await scanSkillsRoot(path.join(opencodeGlobalConfigDir(), "skills"), out);
99
+ await walkAncestorsFor(workspaceRoot, path.join(".claude", "skills"), worktree, out);
100
+ await scanSkillsRoot(path.join(home, ".claude", "skills"), out);
101
+ await walkAncestorsFor(workspaceRoot, path.join(".agents", "skills"), worktree, out);
102
+ await scanSkillsRoot(path.join(home, ".agents", "skills"), out);
103
+ return [...out.values()];
104
+ }
@@ -0,0 +1,15 @@
1
+ import { CursorPlugin } from "./plugin.js";
2
+ export type CreateCursorOptions = {
3
+ name: string;
4
+ accessToken?: string;
5
+ apiKey?: string;
6
+ baseURL?: string;
7
+ headers?: Record<string, string>;
8
+ /** OpenCode project / worktree directory for request_context collectors. */
9
+ workspaceRoot?: string;
10
+ };
11
+ export declare function createCursor(options: CreateCursorOptions): {
12
+ languageModel(modelId: string): import("@ai-sdk/provider").LanguageModelV3;
13
+ };
14
+ export { CursorPlugin };
15
+ export default CursorPlugin;
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ import { CURSOR_PROVIDER_ID } from "./shared.js";
2
+ import { createCursorLanguageModel } from "./language-model.js";
3
+ import { CursorPlugin } from "./plugin.js";
4
+ export function createCursor(options) {
5
+ const providerId = options.name || CURSOR_PROVIDER_ID;
6
+ return {
7
+ languageModel(modelId) {
8
+ return createCursorLanguageModel(modelId, providerId, options);
9
+ },
10
+ };
11
+ }
12
+ export { CursorPlugin };
13
+ export default CursorPlugin;
14
+ // CursorPluginV2 is NOT re-exported here — see plugin-v2.ts.
15
+ // OpenCode's legacy plugin loader (getLegacyPlugins) iterates all exports
16
+ // and calls getServerPlugin on each; the v2 define() return is not a
17
+ // function, causing "Plugin export is not a function". Load it via
18
+ // the separate "cursor-opencode-provider/plugin/v2" export path.
@@ -0,0 +1,45 @@
1
+ import type { LanguageModelV3, LanguageModelV3CallOptions } from "@ai-sdk/provider";
2
+ import type { CreateCursorOptions } from "./index.js";
3
+ import { type CursorSession } from "./session.js";
4
+ export declare function createCursorLanguageModel(modelId: string, providerId: string, options: CreateCursorOptions): LanguageModelV3;
5
+ /**
6
+ * OpenCode re-sends the full tool-result history on every continuation. Prefer
7
+ * the newest result that still has a live pending exec on its tagged session.
8
+ */
9
+ export declare function findContinuationSession(toolResults: Array<{
10
+ sessionId: string;
11
+ execId: number;
12
+ }>): CursorSession | undefined;
13
+ type ExtractedToolResult = {
14
+ sessionId: string;
15
+ execId: number;
16
+ toolName: string;
17
+ output: string;
18
+ error?: string;
19
+ };
20
+ /**
21
+ * Tool results that form a live continuation: only the trailing run of `tool`
22
+ * messages after the last non-tool message. Mid-prompt historical tool results
23
+ * are ignored — they are conversation history, not replies for a held-open Run.
24
+ */
25
+ export declare function extractTrailingToolResults(prompt: LanguageModelV3CallOptions["prompt"]): ExtractedToolResult[];
26
+ /**
27
+ * Map OpenCode's session id header to a stable Cursor conversation_id (UUID).
28
+ * Falls back to a random UUID when headers are absent (direct SDK use).
29
+ */
30
+ export declare function resolveConversationId(callOptions: LanguageModelV3CallOptions): string;
31
+ /** Deterministic UUID (version-4 shape) from an arbitrary session key. */
32
+ export declare function sessionIdToUuid(sessionId: string): string;
33
+ /** Exported for tests — AI SDK V3 span ends that must precede finish / tool-call. */
34
+ export declare function spanEndParts(opts: {
35
+ textStarted: boolean;
36
+ reasoningStarted: boolean;
37
+ textId: string;
38
+ reasoningId: string;
39
+ }): Array<{
40
+ type: "text-end" | "reasoning-end";
41
+ id: string;
42
+ }>;
43
+ /** Exported for tests — false for compaction/summary (no tools) and toolChoice none. */
44
+ export declare function computeAllowTools(toolCount: number, toolChoice: LanguageModelV3CallOptions["toolChoice"] | undefined): boolean;
45
+ export {};