cursor-opencode-provider 0.2.9 → 0.3.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.
- package/README.md +139 -10
- package/dist/agent-url.js +2 -0
- package/dist/auth.d.ts +7 -0
- package/dist/auth.js +9 -0
- package/dist/compaction-marker.d.ts +14 -0
- package/dist/compaction-marker.js +36 -0
- package/dist/context/env.js +6 -2
- package/dist/context/paths.js +3 -2
- package/dist/index.d.ts +1 -3
- package/dist/index.js +2 -8
- package/dist/language-model.js +51 -35
- package/dist/model-config.d.ts +15 -0
- package/dist/model-config.js +206 -0
- package/dist/opencode2/catalog.d.ts +33 -0
- package/dist/opencode2/catalog.js +85 -0
- package/dist/opencode2/integration.d.ts +23 -0
- package/dist/opencode2/integration.js +124 -0
- package/dist/opencode2/types.d.ts +316 -0
- package/dist/opencode2/types.js +18 -0
- package/dist/plugin-core.d.ts +27 -0
- package/dist/plugin-core.js +31 -0
- package/dist/plugin-opencode2.d.ts +3 -0
- package/dist/plugin-opencode2.js +293 -0
- package/dist/plugin-v2.d.ts +7 -0
- package/dist/plugin-v2.js +1 -15
- package/dist/plugin.d.ts +1 -15
- package/dist/plugin.js +11 -191
- package/dist/protocol/apply-patch.d.ts +109 -0
- package/dist/protocol/apply-patch.js +163 -0
- package/dist/protocol/messages.d.ts +10 -0
- package/dist/protocol/messages.js +83 -1
- package/dist/protocol/tools.d.ts +24 -1
- package/dist/protocol/tools.js +262 -19
- package/dist/session-directory.d.ts +17 -0
- package/dist/session-directory.js +35 -0
- package/dist/session.d.ts +13 -1
- package/dist/session.js +70 -1
- package/dist/shell-timeout.d.ts +3 -1
- package/dist/shell-timeout.js +12 -4
- package/dist/usage.d.ts +30 -0
- package/dist/usage.js +77 -0
- package/dist/web-search-tool.d.ts +33 -0
- package/dist/web-search-tool.js +22 -0
- package/dist/web-tools.d.ts +9 -25
- package/dist/web-tools.js +34 -36
- package/package.json +14 -9
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { CURSOR_WEBSITE_HOST } from "../shared.js";
|
|
2
|
+
import { cursorApiBaseURL } from "../plugin-core.js";
|
|
3
|
+
import { buildLoginUrl, decodeJwtExpiryMs, exchangeApiKey, generatePkceChallenge, generatePkceParams, isExpiringSoon, pollForTokens, refreshAccessToken, } from "../auth.js";
|
|
4
|
+
import { CURSOR_INTEGRATION_ID } from "./catalog.js";
|
|
5
|
+
/**
|
|
6
|
+
* Integration registration for the OpenCode 2.0 plugin — the replacement for the
|
|
7
|
+
* classic plugin's `auth` hook (`methods` + `loader`).
|
|
8
|
+
*
|
|
9
|
+
* Unlike the 1.18 v2 API (where OAuth registration is Effect-valued), 2.0 takes
|
|
10
|
+
* plain Promises, so the PKCE flow from `src/auth.ts` ports across unchanged.
|
|
11
|
+
*/
|
|
12
|
+
export const CURSOR_OAUTH_METHOD_ID = "oauth";
|
|
13
|
+
/** Env vars that can supply a Cursor API key without running /connect. */
|
|
14
|
+
export const CURSOR_ENV_NAMES = ["CURSOR_API_KEY"];
|
|
15
|
+
function websiteURL() {
|
|
16
|
+
return process.env.CURSOR_WEBSITE_URL ?? `https://${CURSOR_WEBSITE_HOST}`;
|
|
17
|
+
}
|
|
18
|
+
/** Browser (PKCE) login: open URL, then poll until Cursor hands back tokens. */
|
|
19
|
+
async function authorizeOAuth() {
|
|
20
|
+
const params = generatePkceParams();
|
|
21
|
+
const challenge = await generatePkceChallenge(params.verifier);
|
|
22
|
+
const apiBaseURL = cursorApiBaseURL();
|
|
23
|
+
const url = buildLoginUrl(challenge, params.uuid, websiteURL());
|
|
24
|
+
return {
|
|
25
|
+
url,
|
|
26
|
+
instructions: "Open this URL in a browser to sign in to Cursor",
|
|
27
|
+
mode: "auto",
|
|
28
|
+
callback: pollForTokens(params.uuid, params.verifier, apiBaseURL).then((result) => ({
|
|
29
|
+
type: "oauth",
|
|
30
|
+
methodID: CURSOR_OAUTH_METHOD_ID,
|
|
31
|
+
access: result.accessToken,
|
|
32
|
+
refresh: result.refreshToken,
|
|
33
|
+
expires: decodeJwtExpiryMs(result.accessToken) ?? Date.now(),
|
|
34
|
+
})),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Renew an expiring Cursor JWT. The host calls this lazily when the stored
|
|
39
|
+
* credential is close to expiry, so it must not assume it runs on every request.
|
|
40
|
+
*/
|
|
41
|
+
async function refreshOAuth(credential) {
|
|
42
|
+
const tokens = await refreshAccessToken(credential.refresh, cursorApiBaseURL());
|
|
43
|
+
return {
|
|
44
|
+
type: "oauth",
|
|
45
|
+
methodID: credential.methodID || CURSOR_OAUTH_METHOD_ID,
|
|
46
|
+
access: tokens.accessToken,
|
|
47
|
+
refresh: tokens.refreshToken,
|
|
48
|
+
expires: decodeJwtExpiryMs(tokens.accessToken) ?? Date.now(),
|
|
49
|
+
...(credential.metadata === undefined ? {} : { metadata: credential.metadata }),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Register the Cursor integration and its three connection methods. */
|
|
53
|
+
export function applyCursorIntegration(draft) {
|
|
54
|
+
draft.update(CURSOR_INTEGRATION_ID, (integration) => {
|
|
55
|
+
integration.id = CURSOR_INTEGRATION_ID;
|
|
56
|
+
integration.name = "Cursor";
|
|
57
|
+
});
|
|
58
|
+
draft.method.update({
|
|
59
|
+
integrationID: CURSOR_INTEGRATION_ID,
|
|
60
|
+
method: {
|
|
61
|
+
id: CURSOR_OAUTH_METHOD_ID,
|
|
62
|
+
type: "oauth",
|
|
63
|
+
label: "Cursor account (browser login)",
|
|
64
|
+
},
|
|
65
|
+
authorize: authorizeOAuth,
|
|
66
|
+
refresh: refreshOAuth,
|
|
67
|
+
});
|
|
68
|
+
draft.method.update({
|
|
69
|
+
integrationID: CURSOR_INTEGRATION_ID,
|
|
70
|
+
method: { type: "key", label: "API key (cursor.com/settings)" },
|
|
71
|
+
});
|
|
72
|
+
draft.method.update({
|
|
73
|
+
integrationID: CURSOR_INTEGRATION_ID,
|
|
74
|
+
method: { type: "env", names: CURSOR_ENV_NAMES },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Turn a stored credential into a Cursor access token.
|
|
79
|
+
*
|
|
80
|
+
* OAuth credentials already hold a JWT (the host refreshes them via `refresh`
|
|
81
|
+
* above). A `key` credential is the raw `crsr_…` API key, which Cursor requires us
|
|
82
|
+
* to exchange for a short-lived JWT — mirroring the classic plugin's behavior.
|
|
83
|
+
*/
|
|
84
|
+
export async function accessTokenFromCredential(credential) {
|
|
85
|
+
if (!credential)
|
|
86
|
+
return undefined;
|
|
87
|
+
if (credential.type === "oauth") {
|
|
88
|
+
if (credential.access && !isExpiringSoon(credential.access))
|
|
89
|
+
return credential.access;
|
|
90
|
+
if (!credential.refresh)
|
|
91
|
+
return credential.access || undefined;
|
|
92
|
+
try {
|
|
93
|
+
return (await refreshOAuth(credential)).access;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Fall back to the existing token; the call may still succeed.
|
|
97
|
+
return credential.access || undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (credential.type === "key") {
|
|
101
|
+
// Already a JWT (e.g. exchanged by an earlier run) — use it directly.
|
|
102
|
+
if (!credential.key.startsWith("crsr_"))
|
|
103
|
+
return credential.key;
|
|
104
|
+
try {
|
|
105
|
+
return (await exchangeApiKey(credential.key, cursorApiBaseURL())).accessToken;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
/** Resolve the active Cursor connection into an access token, if any. */
|
|
114
|
+
export async function resolveCursorAccessToken(integration) {
|
|
115
|
+
try {
|
|
116
|
+
const connection = await integration.connection.active(CURSOR_INTEGRATION_ID);
|
|
117
|
+
if (!connection)
|
|
118
|
+
return undefined;
|
|
119
|
+
return await accessTokenFromCredential(await integration.connection.resolve(connection));
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local structural types for the OpenCode 2.0 beta plugin API.
|
|
3
|
+
*
|
|
4
|
+
* Why not import from `@opencode-ai/plugin`?
|
|
5
|
+
* • The 2.0 types live on the `@next` dist-tag (`0.0.0-next-*`), which cannot
|
|
6
|
+
* coexist with this package's existing `@opencode-ai/plugin@^1.17.13`
|
|
7
|
+
* dependency under the same module specifier.
|
|
8
|
+
* • 2.0 is beta and its schema churns; a hand-maintained subset of only what
|
|
9
|
+
* we touch is more stable than pinning a moving target.
|
|
10
|
+
*
|
|
11
|
+
* These mirror `@opencode-ai/plugin@next` `dist/promise/*.d.ts` as of
|
|
12
|
+
* `0.0.0-next-16281`. `test/opencode2-conformance.test-d.ts` asserts our
|
|
13
|
+
* default export still satisfies the real 2.0 `Plugin` interface.
|
|
14
|
+
*
|
|
15
|
+
* Effect `Schema` brands (Provider.ID, Model.ID, …) are modelled as plain
|
|
16
|
+
* `string`; brands are compile-time only and erase at runtime.
|
|
17
|
+
*/
|
|
18
|
+
export type Registration = {
|
|
19
|
+
readonly dispose: () => Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
export type Hooks<Spec> = <Name extends keyof Spec>(name: Name, callback: (input: Spec[Name]) => Promise<void> | void) => Promise<Registration>;
|
|
22
|
+
export type Transform<Input> = (callback: (input: Input) => void) => Promise<Registration>;
|
|
23
|
+
export type ProviderInfo = {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
/** `"aisdk:<pkg>"` selects the AI SDK path; a bare specifier selects native. */
|
|
27
|
+
package: string;
|
|
28
|
+
integrationID?: string;
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
settings?: Record<string, any>;
|
|
31
|
+
headers?: Record<string, string>;
|
|
32
|
+
body?: Record<string, any>;
|
|
33
|
+
};
|
|
34
|
+
export type ModelVariantInfo = {
|
|
35
|
+
id: string;
|
|
36
|
+
settings?: Record<string, any>;
|
|
37
|
+
headers?: Record<string, string>;
|
|
38
|
+
body?: Record<string, any>;
|
|
39
|
+
};
|
|
40
|
+
export type ModelInfo2 = {
|
|
41
|
+
id: string;
|
|
42
|
+
/** Wire id sent to the provider. Lets one Cursor model back several entries. */
|
|
43
|
+
modelID: string;
|
|
44
|
+
providerID: string;
|
|
45
|
+
name: string;
|
|
46
|
+
family?: string;
|
|
47
|
+
capabilities: {
|
|
48
|
+
tools: boolean;
|
|
49
|
+
input: readonly string[];
|
|
50
|
+
output: readonly string[];
|
|
51
|
+
};
|
|
52
|
+
variants: readonly ModelVariantInfo[];
|
|
53
|
+
time: {
|
|
54
|
+
released: number;
|
|
55
|
+
};
|
|
56
|
+
cost: readonly unknown[];
|
|
57
|
+
status: "alpha" | "beta" | "deprecated" | "active";
|
|
58
|
+
enabled: boolean;
|
|
59
|
+
limit: {
|
|
60
|
+
context: number;
|
|
61
|
+
input?: number;
|
|
62
|
+
output: number;
|
|
63
|
+
};
|
|
64
|
+
settings?: Record<string, any>;
|
|
65
|
+
headers?: Record<string, string>;
|
|
66
|
+
body?: Record<string, any>;
|
|
67
|
+
package?: string;
|
|
68
|
+
};
|
|
69
|
+
export type CredentialOAuth = {
|
|
70
|
+
type: "oauth";
|
|
71
|
+
methodID: string;
|
|
72
|
+
refresh: string;
|
|
73
|
+
access: string;
|
|
74
|
+
expires: number;
|
|
75
|
+
metadata?: Record<string, unknown>;
|
|
76
|
+
};
|
|
77
|
+
export type CredentialKey = {
|
|
78
|
+
type: "key";
|
|
79
|
+
key: string;
|
|
80
|
+
metadata?: Record<string, unknown>;
|
|
81
|
+
};
|
|
82
|
+
export type CredentialValue = CredentialOAuth | CredentialKey;
|
|
83
|
+
export type ConnectionInfo = {
|
|
84
|
+
type: "credential";
|
|
85
|
+
id: string;
|
|
86
|
+
label: string;
|
|
87
|
+
} | {
|
|
88
|
+
type: "env";
|
|
89
|
+
name: string;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Read accessors we never call are intentionally loose (`any`). Modelling the
|
|
93
|
+
* host's Effect-schema records exactly would make the conformance guard churn on
|
|
94
|
+
* unrelated upstream edits without protecting anything we depend on. The
|
|
95
|
+
* `update` signatures — the ones we actually build the catalog with — stay precise.
|
|
96
|
+
*/
|
|
97
|
+
export type CatalogDraft = {
|
|
98
|
+
readonly provider: {
|
|
99
|
+
list(): readonly any[];
|
|
100
|
+
get(providerID: string): any;
|
|
101
|
+
/** Upsert: creates the provider when absent. */
|
|
102
|
+
update(providerID: string, update: (provider: ProviderInfo) => void): void;
|
|
103
|
+
remove(providerID: string): void;
|
|
104
|
+
};
|
|
105
|
+
readonly model: {
|
|
106
|
+
get(providerID: string, modelID: string): any;
|
|
107
|
+
/** Upsert: creates provider and/or model when absent. */
|
|
108
|
+
update(providerID: string, modelID: string, update: (model: ModelInfo2) => void): void;
|
|
109
|
+
remove(providerID: string, modelID: string): void;
|
|
110
|
+
readonly default: {
|
|
111
|
+
get(): {
|
|
112
|
+
providerID: string;
|
|
113
|
+
modelID: string;
|
|
114
|
+
} | undefined;
|
|
115
|
+
set(providerID: string, modelID: string): void;
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
export type CatalogDomain = {
|
|
120
|
+
readonly transform: Transform<CatalogDraft>;
|
|
121
|
+
readonly reload: () => Promise<void>;
|
|
122
|
+
};
|
|
123
|
+
export type IntegrationTextPrompt = {
|
|
124
|
+
type: "text";
|
|
125
|
+
key: string;
|
|
126
|
+
message: string;
|
|
127
|
+
placeholder?: string;
|
|
128
|
+
};
|
|
129
|
+
export type IntegrationOAuthMethod = {
|
|
130
|
+
id: string;
|
|
131
|
+
type: "oauth";
|
|
132
|
+
label: string;
|
|
133
|
+
prompts?: IntegrationTextPrompt[];
|
|
134
|
+
};
|
|
135
|
+
export type IntegrationKeyMethod = {
|
|
136
|
+
type: "key";
|
|
137
|
+
label?: string;
|
|
138
|
+
};
|
|
139
|
+
export type IntegrationEnvMethod = {
|
|
140
|
+
type: "env";
|
|
141
|
+
names: string[];
|
|
142
|
+
};
|
|
143
|
+
export type IntegrationMethod = IntegrationOAuthMethod | IntegrationKeyMethod | IntegrationEnvMethod;
|
|
144
|
+
export type IntegrationOAuthAuthorization = {
|
|
145
|
+
readonly url: string;
|
|
146
|
+
readonly instructions: string;
|
|
147
|
+
readonly expiresAt?: number;
|
|
148
|
+
} & ({
|
|
149
|
+
readonly mode: "auto";
|
|
150
|
+
readonly callback: Promise<CredentialOAuth>;
|
|
151
|
+
} | {
|
|
152
|
+
readonly mode: "code";
|
|
153
|
+
readonly callback: (code: string) => Promise<CredentialOAuth>;
|
|
154
|
+
});
|
|
155
|
+
export type IntegrationOAuthMethodRegistration = {
|
|
156
|
+
readonly integrationID: string;
|
|
157
|
+
readonly method: IntegrationOAuthMethod;
|
|
158
|
+
readonly authorize: (inputs: Record<string, string>) => Promise<IntegrationOAuthAuthorization>;
|
|
159
|
+
readonly refresh?: (credential: CredentialOAuth) => Promise<CredentialOAuth>;
|
|
160
|
+
readonly label?: (credential: CredentialOAuth) => string | undefined;
|
|
161
|
+
};
|
|
162
|
+
export type IntegrationMethodRegistration = IntegrationOAuthMethodRegistration | {
|
|
163
|
+
readonly integrationID: string;
|
|
164
|
+
readonly method: IntegrationKeyMethod;
|
|
165
|
+
} | {
|
|
166
|
+
readonly integrationID: string;
|
|
167
|
+
readonly method: IntegrationEnvMethod;
|
|
168
|
+
};
|
|
169
|
+
export type IntegrationRef = {
|
|
170
|
+
id: string;
|
|
171
|
+
name: string;
|
|
172
|
+
};
|
|
173
|
+
export type IntegrationDraft = {
|
|
174
|
+
list(): readonly any[];
|
|
175
|
+
get(id: string): any;
|
|
176
|
+
update(id: string, update: (integration: IntegrationRef) => void): void;
|
|
177
|
+
remove(id: string): void;
|
|
178
|
+
readonly method: {
|
|
179
|
+
list(integrationID: string): readonly any[];
|
|
180
|
+
update(input: IntegrationMethodRegistration): void;
|
|
181
|
+
remove(integrationID: string, method: any): void;
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
export type IntegrationDomain = {
|
|
185
|
+
readonly transform: Transform<IntegrationDraft>;
|
|
186
|
+
readonly reload: () => Promise<void>;
|
|
187
|
+
readonly connection: {
|
|
188
|
+
readonly active: (integrationID: string) => Promise<ConnectionInfo | undefined>;
|
|
189
|
+
readonly resolve: (connection: ConnectionInfo) => Promise<CredentialValue | undefined>;
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
export type AISDKHooks = {
|
|
193
|
+
sdk: {
|
|
194
|
+
readonly model: ModelInfo2;
|
|
195
|
+
readonly package: string;
|
|
196
|
+
readonly options: Record<string, any>;
|
|
197
|
+
sdk?: any;
|
|
198
|
+
};
|
|
199
|
+
language: {
|
|
200
|
+
readonly model: ModelInfo2;
|
|
201
|
+
readonly sdk: any;
|
|
202
|
+
readonly options: Record<string, any>;
|
|
203
|
+
language?: unknown;
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
export type AISDKDomain = {
|
|
207
|
+
readonly hook: Hooks<AISDKHooks>;
|
|
208
|
+
};
|
|
209
|
+
export type ToolDefinition = {
|
|
210
|
+
readonly name: string;
|
|
211
|
+
readonly description: string;
|
|
212
|
+
readonly input: unknown;
|
|
213
|
+
readonly output?: unknown;
|
|
214
|
+
readonly execute: (input: any, context: any) => Promise<any>;
|
|
215
|
+
};
|
|
216
|
+
export type ToolDraft = {
|
|
217
|
+
add(tool: ToolDefinition): void;
|
|
218
|
+
};
|
|
219
|
+
export type ToolHookBase = {
|
|
220
|
+
readonly tool: string;
|
|
221
|
+
readonly sessionID: string;
|
|
222
|
+
readonly agent: string;
|
|
223
|
+
readonly messageID: string;
|
|
224
|
+
readonly callID: string;
|
|
225
|
+
};
|
|
226
|
+
export type ToolHooks = {
|
|
227
|
+
readonly "execute.before": ToolHookBase & {
|
|
228
|
+
input: unknown;
|
|
229
|
+
};
|
|
230
|
+
readonly "execute.after": ToolHookBase & {
|
|
231
|
+
readonly input: unknown;
|
|
232
|
+
} & ({
|
|
233
|
+
readonly status: "completed";
|
|
234
|
+
result: any;
|
|
235
|
+
} | {
|
|
236
|
+
readonly status: "error";
|
|
237
|
+
error: any;
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
export type ToolDomain = {
|
|
241
|
+
readonly transform: Transform<ToolDraft>;
|
|
242
|
+
readonly hook: Hooks<ToolHooks>;
|
|
243
|
+
};
|
|
244
|
+
export type SessionContext = {
|
|
245
|
+
readonly sessionID: string;
|
|
246
|
+
readonly agent: string;
|
|
247
|
+
readonly model: {
|
|
248
|
+
providerID: string;
|
|
249
|
+
id: string;
|
|
250
|
+
variant?: string;
|
|
251
|
+
};
|
|
252
|
+
system: Array<any>;
|
|
253
|
+
messages: Array<any>;
|
|
254
|
+
tools: Record<string, {
|
|
255
|
+
description: string;
|
|
256
|
+
input: unknown;
|
|
257
|
+
}>;
|
|
258
|
+
};
|
|
259
|
+
export type SessionHooks = {
|
|
260
|
+
readonly context: SessionContext;
|
|
261
|
+
};
|
|
262
|
+
/** Only the `location.directory` field we actually read; the rest of the real `SessionInfo` is loose. */
|
|
263
|
+
export type SessionInfo = {
|
|
264
|
+
readonly id: string;
|
|
265
|
+
readonly location: {
|
|
266
|
+
readonly directory: string;
|
|
267
|
+
};
|
|
268
|
+
};
|
|
269
|
+
export type SessionDomain = {
|
|
270
|
+
readonly hook: Hooks<SessionHooks>;
|
|
271
|
+
readonly get: (input: {
|
|
272
|
+
sessionID: string;
|
|
273
|
+
}) => Promise<SessionInfo>;
|
|
274
|
+
};
|
|
275
|
+
export type WebSearchDefinition = {
|
|
276
|
+
readonly id: string;
|
|
277
|
+
readonly name: string;
|
|
278
|
+
readonly execute: (input: any, context: {
|
|
279
|
+
readonly signal: AbortSignal;
|
|
280
|
+
}) => Promise<readonly any[]>;
|
|
281
|
+
};
|
|
282
|
+
export type WebSearchDraft = {
|
|
283
|
+
add(definition: WebSearchDefinition): void;
|
|
284
|
+
readonly default: {
|
|
285
|
+
get(): string | undefined;
|
|
286
|
+
set(providerID: string): void;
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
export type WebSearchDomain = {
|
|
290
|
+
readonly transform: Transform<WebSearchDraft>;
|
|
291
|
+
readonly reload: () => Promise<void>;
|
|
292
|
+
};
|
|
293
|
+
export type EventDomain = {
|
|
294
|
+
readonly subscribe: (...args: any[]) => any;
|
|
295
|
+
};
|
|
296
|
+
export type App = {
|
|
297
|
+
readonly name: string;
|
|
298
|
+
readonly version: string;
|
|
299
|
+
readonly channel: string;
|
|
300
|
+
};
|
|
301
|
+
export type PluginContext = {
|
|
302
|
+
readonly app: App;
|
|
303
|
+
readonly options: Readonly<Record<string, any>>;
|
|
304
|
+
readonly aisdk: AISDKDomain;
|
|
305
|
+
readonly catalog: CatalogDomain;
|
|
306
|
+
readonly event: EventDomain;
|
|
307
|
+
readonly integration: IntegrationDomain;
|
|
308
|
+
readonly session: SessionDomain;
|
|
309
|
+
readonly tool: ToolDomain;
|
|
310
|
+
readonly websearch: WebSearchDomain;
|
|
311
|
+
};
|
|
312
|
+
export type Cleanup = () => Promise<void> | void;
|
|
313
|
+
export type Plugin2 = {
|
|
314
|
+
readonly id: string;
|
|
315
|
+
readonly setup: (context: PluginContext) => Promise<Cleanup | void> | Cleanup | void;
|
|
316
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local structural types for the OpenCode 2.0 beta plugin API.
|
|
3
|
+
*
|
|
4
|
+
* Why not import from `@opencode-ai/plugin`?
|
|
5
|
+
* • The 2.0 types live on the `@next` dist-tag (`0.0.0-next-*`), which cannot
|
|
6
|
+
* coexist with this package's existing `@opencode-ai/plugin@^1.17.13`
|
|
7
|
+
* dependency under the same module specifier.
|
|
8
|
+
* • 2.0 is beta and its schema churns; a hand-maintained subset of only what
|
|
9
|
+
* we touch is more stable than pinning a moving target.
|
|
10
|
+
*
|
|
11
|
+
* These mirror `@opencode-ai/plugin@next` `dist/promise/*.d.ts` as of
|
|
12
|
+
* `0.0.0-next-16281`. `test/opencode2-conformance.test-d.ts` asserts our
|
|
13
|
+
* default export still satisfies the real 2.0 `Plugin` interface.
|
|
14
|
+
*
|
|
15
|
+
* Effect `Schema` brands (Provider.ID, Model.ID, …) are modelled as plain
|
|
16
|
+
* `string`; brands are compile-time only and erase at runtime.
|
|
17
|
+
*/
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createCursorLanguageModel } from "./language-model.js";
|
|
2
|
+
import type { CreateCursorOptions } from "./index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Runtime-agnostic pieces shared by every plugin surface:
|
|
5
|
+
* • classic Hooks plugin (`plugin.ts`)
|
|
6
|
+
* • OpenCode 1.18 v2 plugin (`plugin-v2.ts`)
|
|
7
|
+
* • OpenCode 2.0 beta plugin (`plugin-opencode2.ts`)
|
|
8
|
+
*
|
|
9
|
+
* Nothing here may import a host plugin API — the 1.18 and 2.0 APIs are
|
|
10
|
+
* source-incompatible, so anything host-shaped belongs in the entrypoint.
|
|
11
|
+
*/
|
|
12
|
+
/** Minimal AI SDK provider surface OpenCode expects from an `aisdk` package. */
|
|
13
|
+
export type CursorSdk = {
|
|
14
|
+
languageModel(modelId: string): ReturnType<typeof createCursorLanguageModel>;
|
|
15
|
+
};
|
|
16
|
+
export declare function createSdk(options: CreateCursorOptions): CursorSdk;
|
|
17
|
+
/**
|
|
18
|
+
* Whether an OpenCode `aisdk` package string refers to this provider.
|
|
19
|
+
*
|
|
20
|
+
* Matches on the provider id first (the common case once the catalog entry is
|
|
21
|
+
* ours), then on the package specifier so a bare `cursor-opencode-provider`,
|
|
22
|
+
* an `aisdk:`-prefixed form, or a resolved `dist/index.js` file URL all hit.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isCursorPackage(pkg: string, providerID: string): boolean;
|
|
25
|
+
/** API base for auth, model discovery, and GetServerConfig. */
|
|
26
|
+
export declare function cursorApiBaseURL(): string;
|
|
27
|
+
export declare function cursorGetServerConfigTelemetryEnabled(): boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CURSOR_API_HOST, CURSOR_PROVIDER_ID } from "./shared.js";
|
|
2
|
+
import { createCursorLanguageModel } from "./language-model.js";
|
|
3
|
+
export function createSdk(options) {
|
|
4
|
+
const providerId = options.name || CURSOR_PROVIDER_ID;
|
|
5
|
+
return {
|
|
6
|
+
languageModel(modelId) {
|
|
7
|
+
return createCursorLanguageModel(modelId, providerId, options);
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Whether an OpenCode `aisdk` package string refers to this provider.
|
|
13
|
+
*
|
|
14
|
+
* Matches on the provider id first (the common case once the catalog entry is
|
|
15
|
+
* ours), then on the package specifier so a bare `cursor-opencode-provider`,
|
|
16
|
+
* an `aisdk:`-prefixed form, or a resolved `dist/index.js` file URL all hit.
|
|
17
|
+
*/
|
|
18
|
+
export function isCursorPackage(pkg, providerID) {
|
|
19
|
+
if (providerID === CURSOR_PROVIDER_ID)
|
|
20
|
+
return true;
|
|
21
|
+
return (pkg.includes("cursor-opencode-provider") ||
|
|
22
|
+
/cursor-opencode-provider[/\\]dist[/\\]index\.js/.test(pkg));
|
|
23
|
+
}
|
|
24
|
+
/** API base for auth, model discovery, and GetServerConfig. */
|
|
25
|
+
export function cursorApiBaseURL() {
|
|
26
|
+
return process.env.CURSOR_API_BASE_URL ?? `https://${CURSOR_API_HOST}`;
|
|
27
|
+
}
|
|
28
|
+
export function cursorGetServerConfigTelemetryEnabled() {
|
|
29
|
+
return (process.env.CURSOR_GET_SERVER_CONFIG_TELEMETRY === "1" ||
|
|
30
|
+
process.env.CURSOR_GET_SERVER_CONFIG_TELEMETRY === "true");
|
|
31
|
+
}
|