opencode-cursor-provider 0.1.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/LICENSE +55 -0
- package/README.md +112 -0
- package/dist/auth/credential.d.ts +44 -0
- package/dist/auth/credential.js +85 -0
- package/dist/auth/link.d.ts +14 -0
- package/dist/auth/link.js +120 -0
- package/dist/auth/state.d.ts +54 -0
- package/dist/auth/state.js +54 -0
- package/dist/bridge/agent.d.ts +17 -0
- package/dist/bridge/agent.js +39 -0
- package/dist/bridge/binding.d.ts +35 -0
- package/dist/bridge/binding.js +59 -0
- package/dist/bridge/bridge.d.ts +16 -0
- package/dist/bridge/bridge.js +21 -0
- package/dist/bridge/conversation.d.ts +64 -0
- package/dist/bridge/conversation.js +86 -0
- package/dist/bridge/correlation.d.ts +10 -0
- package/dist/bridge/correlation.js +52 -0
- package/dist/bridge/lock.d.ts +4 -0
- package/dist/bridge/lock.js +24 -0
- package/dist/bridge/response-journal.d.ts +7 -0
- package/dist/bridge/response-journal.js +49 -0
- package/dist/bridge/translate.d.ts +60 -0
- package/dist/bridge/translate.js +188 -0
- package/dist/bridge/turn.d.ts +29 -0
- package/dist/bridge/turn.js +219 -0
- package/dist/catalog/catalog.d.ts +20 -0
- package/dist/catalog/catalog.js +192 -0
- package/dist/catalog/source.d.ts +8 -0
- package/dist/catalog/source.js +30 -0
- package/dist/errors.d.ts +35 -0
- package/dist/errors.js +36 -0
- package/dist/ids.d.ts +33 -0
- package/dist/ids.js +52 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/model/language-model.d.ts +10 -0
- package/dist/model/language-model.js +539 -0
- package/dist/model/provider-options.d.ts +19 -0
- package/dist/model/provider-options.js +84 -0
- package/dist/plugin.d.ts +2 -0
- package/dist/plugin.js +127 -0
- package/dist/runtime.d.ts +8 -0
- package/dist/runtime.js +25 -0
- package/package.json +33 -0
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
import { createCursorLink } from "./auth/link.js";
|
|
3
|
+
import { createSessionAgentBridge } from "./bridge/bridge.js";
|
|
4
|
+
import { sessionFromId } from "./bridge/correlation.js";
|
|
5
|
+
import { applyModels, applyProvider, modelParamsFromOptions } from "./catalog/catalog.js";
|
|
6
|
+
import { createModelSource } from "./catalog/source.js";
|
|
7
|
+
import { asCatalogModelID, PROVIDER_ID } from "./ids.js";
|
|
8
|
+
import { toLanguageModel } from "./model/language-model.js";
|
|
9
|
+
import { bindRuntime } from "./runtime.js";
|
|
10
|
+
const REFRESH_EVERY_MS = 5_000;
|
|
11
|
+
const REFRESH_FOR_MS = 300_000;
|
|
12
|
+
export const plugin = Plugin.define({
|
|
13
|
+
id: "opencode-cursor-provider",
|
|
14
|
+
async setup(ctx) {
|
|
15
|
+
const link = createCursorLink();
|
|
16
|
+
const models = createModelSource(link);
|
|
17
|
+
const bridge = createSessionAgentBridge({
|
|
18
|
+
link,
|
|
19
|
+
models: () => models.list(),
|
|
20
|
+
});
|
|
21
|
+
bindRuntime({
|
|
22
|
+
bridge,
|
|
23
|
+
models: () => models.list(),
|
|
24
|
+
});
|
|
25
|
+
const languageModel = (model, fallbackID, options) => toLanguageModel({
|
|
26
|
+
bridge,
|
|
27
|
+
modelID: asCatalogModelID(model.id),
|
|
28
|
+
wireID: model.modelID || fallbackID,
|
|
29
|
+
...paramsOption(options),
|
|
30
|
+
});
|
|
31
|
+
const registrations = [
|
|
32
|
+
await ctx.integration.transform((draft) => {
|
|
33
|
+
link.register(draft);
|
|
34
|
+
}),
|
|
35
|
+
await ctx.catalog.transform((draft) => {
|
|
36
|
+
applyProvider(draft);
|
|
37
|
+
applyModels(draft, models.list());
|
|
38
|
+
}),
|
|
39
|
+
await ctx.session.hook("context", async (event) => {
|
|
40
|
+
event.tools = {};
|
|
41
|
+
if (String(event.agent) === "compaction" || String(event.agent) === "title")
|
|
42
|
+
return;
|
|
43
|
+
const stamped = bridge.annotate(event.system.map((part) => part.text), sessionFromId(event.sessionID, await sessionCwd(ctx, event.sessionID)));
|
|
44
|
+
writeSystem(event.system, stamped);
|
|
45
|
+
}, { providerID: PROVIDER_ID }),
|
|
46
|
+
await ctx.aisdk.hook("sdk", (event) => {
|
|
47
|
+
if (event.sdk)
|
|
48
|
+
return;
|
|
49
|
+
event.sdk = {
|
|
50
|
+
languageModel: (modelID) => languageModel(event.model, modelID, event.options),
|
|
51
|
+
};
|
|
52
|
+
}, { providerID: PROVIDER_ID }),
|
|
53
|
+
await ctx.aisdk.hook("language", (event) => {
|
|
54
|
+
event.language = languageModel(event.model, event.model.id, event.options);
|
|
55
|
+
}, { providerID: PROVIDER_ID }),
|
|
56
|
+
];
|
|
57
|
+
const stopRefresh = watchModels(ctx, link, models);
|
|
58
|
+
return async () => {
|
|
59
|
+
stopRefresh();
|
|
60
|
+
for (const registration of registrations.toReversed()) {
|
|
61
|
+
await registration.dispose();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
function paramsOption(options) {
|
|
67
|
+
const params = modelParamsFromOptions(options);
|
|
68
|
+
if (params === undefined)
|
|
69
|
+
return {};
|
|
70
|
+
return { params };
|
|
71
|
+
}
|
|
72
|
+
async function sessionCwd(ctx, sessionID) {
|
|
73
|
+
try {
|
|
74
|
+
const info = await ctx.session.get({ sessionID });
|
|
75
|
+
if (info.location.directory.length > 0)
|
|
76
|
+
return info.location.directory;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Session lookup is best-effort. Plugin location is the fallback cwd.
|
|
80
|
+
}
|
|
81
|
+
return ctx.location.directory;
|
|
82
|
+
}
|
|
83
|
+
function writeSystem(system, stamped) {
|
|
84
|
+
for (let index = 0; index < stamped.length; index += 1) {
|
|
85
|
+
const text = stamped[index];
|
|
86
|
+
if (text === undefined)
|
|
87
|
+
continue;
|
|
88
|
+
const existing = system[index];
|
|
89
|
+
if (existing === undefined) {
|
|
90
|
+
system.push({ type: "text", text });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (existing.text !== text)
|
|
94
|
+
system[index] = { type: "text", text };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function watchModels(ctx, link, models) {
|
|
98
|
+
const events = new AbortController();
|
|
99
|
+
const refresh = () => {
|
|
100
|
+
void models.refresh(ctx);
|
|
101
|
+
};
|
|
102
|
+
const stopLinked = link.onLinked(refresh);
|
|
103
|
+
refresh();
|
|
104
|
+
void (async () => {
|
|
105
|
+
try {
|
|
106
|
+
for await (const event of ctx.event.subscribe({ signal: events.signal })) {
|
|
107
|
+
if (event.type === "credential.updated" || event.type === "credential.switched")
|
|
108
|
+
refresh();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Subscription ends when the plugin unloads.
|
|
113
|
+
}
|
|
114
|
+
})();
|
|
115
|
+
const retry = setInterval(refresh, REFRESH_EVERY_MS);
|
|
116
|
+
retry.unref?.();
|
|
117
|
+
const stop = setTimeout(() => {
|
|
118
|
+
clearInterval(retry);
|
|
119
|
+
}, REFRESH_FOR_MS);
|
|
120
|
+
stop.unref?.();
|
|
121
|
+
return () => {
|
|
122
|
+
stopLinked();
|
|
123
|
+
events.abort();
|
|
124
|
+
clearInterval(retry);
|
|
125
|
+
clearTimeout(stop);
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SessionAgentBridge } from "./bridge/bridge.ts";
|
|
2
|
+
import { type CursorModelDescriptor } from "./catalog/catalog.ts";
|
|
3
|
+
export interface CursorRuntime {
|
|
4
|
+
readonly bridge: SessionAgentBridge;
|
|
5
|
+
readonly models: () => readonly CursorModelDescriptor[];
|
|
6
|
+
}
|
|
7
|
+
export declare function bindRuntime(next: CursorRuntime): void;
|
|
8
|
+
export declare function model(modelID: string, settings?: unknown): import("@ai-sdk/provider").LanguageModelV3;
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { modelParamsFromOptions, resolveWireId } from "./catalog/catalog.js";
|
|
2
|
+
import { asCatalogModelID, asCursorModelID } from "./ids.js";
|
|
3
|
+
import { toLanguageModel } from "./model/language-model.js";
|
|
4
|
+
let runtime;
|
|
5
|
+
export function bindRuntime(next) {
|
|
6
|
+
runtime = next;
|
|
7
|
+
}
|
|
8
|
+
export function model(modelID, settings) {
|
|
9
|
+
if (runtime === undefined) {
|
|
10
|
+
throw new Error("Cursor is not loaded. Add opencode-cursor-provider to plugins and restart OpenCode.");
|
|
11
|
+
}
|
|
12
|
+
const wireID = resolveWireId(runtime.models(), modelID) ?? asCursorModelID(modelID);
|
|
13
|
+
return toLanguageModel({
|
|
14
|
+
bridge: runtime.bridge,
|
|
15
|
+
modelID: asCatalogModelID(modelID),
|
|
16
|
+
wireID,
|
|
17
|
+
...paramsOption(settings),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function paramsOption(settings) {
|
|
21
|
+
const params = modelParamsFromOptions(settings);
|
|
22
|
+
if (params === undefined)
|
|
23
|
+
return {};
|
|
24
|
+
return { params };
|
|
25
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-cursor-provider",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenCode 2 plugin that connects Cursor via the official SDK",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.13"
|
|
9
|
+
},
|
|
10
|
+
"main": "./src/index.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./src/index.ts",
|
|
13
|
+
"./server": "./src/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.build.json",
|
|
20
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
21
|
+
"test": "bun test"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@ai-sdk/provider": "3.0.8",
|
|
25
|
+
"@cursor/sdk": "^1.0.31",
|
|
26
|
+
"@opencode-ai/plugin": "beta"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/bun": "^1.3.0",
|
|
30
|
+
"@types/node": "^22.20.1",
|
|
31
|
+
"typescript": "^5.9.3"
|
|
32
|
+
}
|
|
33
|
+
}
|