opencode-cursor-proxy 1.0.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/LICENSE +21 -0
- package/README.md +139 -0
- package/README.zh-CN.md +136 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api/agent-service.d.ts +136 -0
- package/dist/lib/api/agent-service.js +938 -0
- package/dist/lib/api/agent-service.js.map +1 -0
- package/dist/lib/api/ai-service.d.ts +26 -0
- package/dist/lib/api/ai-service.js +38 -0
- package/dist/lib/api/ai-service.js.map +1 -0
- package/dist/lib/api/cursor-client.d.ts +119 -0
- package/dist/lib/api/cursor-client.js +511 -0
- package/dist/lib/api/cursor-client.js.map +1 -0
- package/dist/lib/api/cursor-models.d.ts +13 -0
- package/dist/lib/api/cursor-models.js +34 -0
- package/dist/lib/api/cursor-models.js.map +1 -0
- package/dist/lib/api/openai-compat.d.ts +10 -0
- package/dist/lib/api/openai-compat.js +262 -0
- package/dist/lib/api/openai-compat.js.map +1 -0
- package/dist/lib/api/proto/agent-messages.d.ts +25 -0
- package/dist/lib/api/proto/agent-messages.js +132 -0
- package/dist/lib/api/proto/agent-messages.js.map +1 -0
- package/dist/lib/api/proto/bidi.d.ts +17 -0
- package/dist/lib/api/proto/bidi.js +24 -0
- package/dist/lib/api/proto/bidi.js.map +1 -0
- package/dist/lib/api/proto/decoding.d.ts +19 -0
- package/dist/lib/api/proto/decoding.js +118 -0
- package/dist/lib/api/proto/decoding.js.map +1 -0
- package/dist/lib/api/proto/encoding.d.ts +64 -0
- package/dist/lib/api/proto/encoding.js +180 -0
- package/dist/lib/api/proto/encoding.js.map +1 -0
- package/dist/lib/api/proto/exec.d.ts +12 -0
- package/dist/lib/api/proto/exec.js +383 -0
- package/dist/lib/api/proto/exec.js.map +1 -0
- package/dist/lib/api/proto/index.d.ts +13 -0
- package/dist/lib/api/proto/index.js +10 -0
- package/dist/lib/api/proto/index.js.map +1 -0
- package/dist/lib/api/proto/interaction.d.ts +15 -0
- package/dist/lib/api/proto/interaction.js +99 -0
- package/dist/lib/api/proto/interaction.js.map +1 -0
- package/dist/lib/api/proto/kv.d.ts +52 -0
- package/dist/lib/api/proto/kv.js +156 -0
- package/dist/lib/api/proto/kv.js.map +1 -0
- package/dist/lib/api/proto/tool-calls.d.ts +9 -0
- package/dist/lib/api/proto/tool-calls.js +144 -0
- package/dist/lib/api/proto/tool-calls.js.map +1 -0
- package/dist/lib/api/proto/types.d.ts +201 -0
- package/dist/lib/api/proto/types.js +10 -0
- package/dist/lib/api/proto/types.js.map +1 -0
- package/dist/lib/auth/helpers.d.ts +40 -0
- package/dist/lib/auth/helpers.js +103 -0
- package/dist/lib/auth/helpers.js.map +1 -0
- package/dist/lib/auth/index.d.ts +7 -0
- package/dist/lib/auth/index.js +10 -0
- package/dist/lib/auth/index.js.map +1 -0
- package/dist/lib/auth/login.d.ts +55 -0
- package/dist/lib/auth/login.js +184 -0
- package/dist/lib/auth/login.js.map +1 -0
- package/dist/lib/config.d.ts +153 -0
- package/dist/lib/config.js +182 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/openai-compat/handler.d.ts +40 -0
- package/dist/lib/openai-compat/handler.js +808 -0
- package/dist/lib/openai-compat/handler.js.map +1 -0
- package/dist/lib/openai-compat/index.d.ts +9 -0
- package/dist/lib/openai-compat/index.js +13 -0
- package/dist/lib/openai-compat/index.js.map +1 -0
- package/dist/lib/openai-compat/types.d.ts +127 -0
- package/dist/lib/openai-compat/types.js +6 -0
- package/dist/lib/openai-compat/types.js.map +1 -0
- package/dist/lib/openai-compat/utils.d.ts +143 -0
- package/dist/lib/openai-compat/utils.js +348 -0
- package/dist/lib/openai-compat/utils.js.map +1 -0
- package/dist/lib/session-reuse.d.ts +88 -0
- package/dist/lib/session-reuse.js +198 -0
- package/dist/lib/session-reuse.js.map +1 -0
- package/dist/lib/storage.d.ts +55 -0
- package/dist/lib/storage.js +159 -0
- package/dist/lib/storage.js.map +1 -0
- package/dist/lib/utils/cache.d.ts +131 -0
- package/dist/lib/utils/cache.js +297 -0
- package/dist/lib/utils/cache.js.map +1 -0
- package/dist/lib/utils/fetch.d.ts +84 -0
- package/dist/lib/utils/fetch.js +261 -0
- package/dist/lib/utils/fetch.js.map +1 -0
- package/dist/lib/utils/index.d.ts +13 -0
- package/dist/lib/utils/index.js +22 -0
- package/dist/lib/utils/index.js.map +1 -0
- package/dist/lib/utils/jwt.d.ts +40 -0
- package/dist/lib/utils/jwt.js +102 -0
- package/dist/lib/utils/jwt.js.map +1 -0
- package/dist/lib/utils/logger.d.ts +107 -0
- package/dist/lib/utils/logger.js +227 -0
- package/dist/lib/utils/logger.js.map +1 -0
- package/dist/lib/utils/model-resolver.d.ts +49 -0
- package/dist/lib/utils/model-resolver.js +503 -0
- package/dist/lib/utils/model-resolver.js.map +1 -0
- package/dist/lib/utils/request-pool.d.ts +38 -0
- package/dist/lib/utils/request-pool.js +105 -0
- package/dist/lib/utils/request-pool.js.map +1 -0
- package/dist/lib/utils/request-transformer.d.ts +87 -0
- package/dist/lib/utils/request-transformer.js +154 -0
- package/dist/lib/utils/request-transformer.js.map +1 -0
- package/dist/lib/utils/tokenizer.d.ts +14 -0
- package/dist/lib/utils/tokenizer.js +76 -0
- package/dist/lib/utils/tokenizer.js.map +1 -0
- package/dist/plugin/index.d.ts +8 -0
- package/dist/plugin/index.js +9 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/plugin.d.ts +21 -0
- package/dist/plugin/plugin.js +309 -0
- package/dist/plugin/plugin.js.map +1 -0
- package/dist/plugin/types.d.ts +120 -0
- package/dist/plugin/types.js +7 -0
- package/dist/plugin/types.js.map +1 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.js +95 -0
- package/dist/server.js.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Cursor Auth Plugin
|
|
3
|
+
*
|
|
4
|
+
* An OpenCode plugin that provides OAuth authentication for Cursor's AI backend,
|
|
5
|
+
* following the architecture established by opencode-gemini-auth.
|
|
6
|
+
*
|
|
7
|
+
* This plugin uses a custom fetch function to intercept OpenAI API requests
|
|
8
|
+
* and route them through Cursor's Agent API.
|
|
9
|
+
*/
|
|
10
|
+
import { ModelInfoMap } from "llm-info";
|
|
11
|
+
import { LoginManager, CURSOR_API_BASE_URL, openBrowser, } from "../lib/auth/login";
|
|
12
|
+
import { CursorClient } from "../lib/api/cursor-client";
|
|
13
|
+
import { listCursorModels } from "../lib/api/cursor-models";
|
|
14
|
+
import { decodeJwtPayload } from "../lib/utils/jwt";
|
|
15
|
+
import { refreshAccessToken } from "../lib/auth/helpers";
|
|
16
|
+
import { createPluginFetch } from "../lib/openai-compat";
|
|
17
|
+
// --- Constants ---
|
|
18
|
+
export const CURSOR_PROVIDER_ID = "cursor";
|
|
19
|
+
const CURSOR_TO_LLM_INFO_MAP = {
|
|
20
|
+
"sonnet-4.5": "claude-sonnet-4-5-20250929",
|
|
21
|
+
"sonnet-4.5-thinking": "claude-sonnet-4-5-20250929",
|
|
22
|
+
"opus-4.5": "claude-opus-4-5-20251101",
|
|
23
|
+
"opus-4.5-thinking": "claude-opus-4-5-20251101",
|
|
24
|
+
"opus-4.1": "claude-opus-4-1-20250805",
|
|
25
|
+
"gemini-3-pro": "gemini-3-pro-preview",
|
|
26
|
+
"gemini-3-flash": "gemini-2.5-flash",
|
|
27
|
+
"gpt-5.2": "gpt-5.2",
|
|
28
|
+
"gpt-5.2-high": "gpt-5.2",
|
|
29
|
+
"gpt-5.1": "gpt-5",
|
|
30
|
+
"gpt-5.1-high": "gpt-5",
|
|
31
|
+
"gpt-5.1-codex": "gpt-5",
|
|
32
|
+
"gpt-5.1-codex-high": "gpt-5",
|
|
33
|
+
"gpt-5.1-codex-max": "gpt-5",
|
|
34
|
+
"gpt-5.1-codex-max-high": "gpt-5",
|
|
35
|
+
"grok": "grok-4",
|
|
36
|
+
};
|
|
37
|
+
const DEFAULT_LIMITS = { context: 128000, output: 16384 };
|
|
38
|
+
function getModelLimits(cursorModelId) {
|
|
39
|
+
const llmInfoId = CURSOR_TO_LLM_INFO_MAP[cursorModelId];
|
|
40
|
+
if (!llmInfoId)
|
|
41
|
+
return DEFAULT_LIMITS;
|
|
42
|
+
const info = ModelInfoMap[llmInfoId];
|
|
43
|
+
if (!info)
|
|
44
|
+
return DEFAULT_LIMITS;
|
|
45
|
+
return {
|
|
46
|
+
context: info.contextWindowTokenLimit ?? DEFAULT_LIMITS.context,
|
|
47
|
+
output: info.outputTokenLimit ?? DEFAULT_LIMITS.output,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// --- Auth Helpers ---
|
|
51
|
+
/**
|
|
52
|
+
* Check if auth details are OAuth type
|
|
53
|
+
*/
|
|
54
|
+
function isOAuthAuth(auth) {
|
|
55
|
+
return auth.type === "oauth";
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Check if access token has expired or is missing
|
|
59
|
+
*/
|
|
60
|
+
function accessTokenExpired(auth) {
|
|
61
|
+
if (!auth.access || typeof auth.expires !== "number") {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
// Add 60 second buffer
|
|
65
|
+
return auth.expires <= Date.now() + 60 * 1000;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Parse stored refresh token parts (format: "refreshToken|apiKey")
|
|
69
|
+
*/
|
|
70
|
+
function parseRefreshParts(refresh) {
|
|
71
|
+
const [refreshToken = "", apiKey = ""] = (refresh ?? "").split("|");
|
|
72
|
+
return {
|
|
73
|
+
refreshToken,
|
|
74
|
+
apiKey: apiKey || undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Format refresh token parts for storage
|
|
79
|
+
*/
|
|
80
|
+
function formatRefreshParts(refreshToken, apiKey) {
|
|
81
|
+
return apiKey ? `${refreshToken}|${apiKey}` : refreshToken;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Refresh an access token using the refresh token
|
|
85
|
+
*/
|
|
86
|
+
async function refreshCursorAccessToken(auth, client) {
|
|
87
|
+
const parts = parseRefreshParts(auth.refresh);
|
|
88
|
+
if (!parts.refreshToken) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const result = await refreshAccessToken(parts.refreshToken, CURSOR_API_BASE_URL);
|
|
93
|
+
if (!result) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
const updatedAuth = {
|
|
97
|
+
type: "oauth",
|
|
98
|
+
refresh: formatRefreshParts(result.refreshToken, parts.apiKey),
|
|
99
|
+
access: result.accessToken,
|
|
100
|
+
expires: Date.now() + 3600 * 1000, // 1 hour default
|
|
101
|
+
};
|
|
102
|
+
// Try to get actual expiration from token
|
|
103
|
+
const payload = decodeJwtPayload(result.accessToken);
|
|
104
|
+
if (payload?.exp && typeof payload.exp === "number") {
|
|
105
|
+
updatedAuth.expires = payload.exp * 1000;
|
|
106
|
+
}
|
|
107
|
+
// Persist the updated auth
|
|
108
|
+
try {
|
|
109
|
+
await client.auth.set({
|
|
110
|
+
path: { id: CURSOR_PROVIDER_ID },
|
|
111
|
+
body: updatedAuth,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
console.error("Failed to persist refreshed Cursor credentials:", e);
|
|
116
|
+
}
|
|
117
|
+
return updatedAuth;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
console.error("Failed to refresh Cursor access token:", error);
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// --- Main Plugin ---
|
|
125
|
+
/**
|
|
126
|
+
* Cursor OAuth Plugin for OpenCode
|
|
127
|
+
*
|
|
128
|
+
* Provides authentication for Cursor's AI backend using:
|
|
129
|
+
* - Browser-based OAuth flow with PKCE
|
|
130
|
+
* - API key authentication
|
|
131
|
+
* - Automatic token refresh
|
|
132
|
+
* - Custom fetch function (no proxy server needed)
|
|
133
|
+
*/
|
|
134
|
+
export const CursorOAuthPlugin = async ({ client, }) => ({
|
|
135
|
+
auth: {
|
|
136
|
+
provider: CURSOR_PROVIDER_ID,
|
|
137
|
+
loader: async (getAuth, providerArg) => {
|
|
138
|
+
const auth = await getAuth();
|
|
139
|
+
if (!isOAuthAuth(auth)) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
// Refresh token if needed
|
|
143
|
+
let authRecord = auth;
|
|
144
|
+
if (accessTokenExpired(authRecord)) {
|
|
145
|
+
const refreshed = await refreshCursorAccessToken(authRecord, client);
|
|
146
|
+
if (refreshed) {
|
|
147
|
+
authRecord = refreshed;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const accessToken = authRecord.access;
|
|
151
|
+
if (!accessToken) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
// Ensure provider and provider.models exist
|
|
155
|
+
const provider = providerArg ?? {};
|
|
156
|
+
provider.models = provider.models ?? {};
|
|
157
|
+
// Set model costs to 0 (Cursor handles billing)
|
|
158
|
+
for (const model of Object.values(provider.models)) {
|
|
159
|
+
if (model) {
|
|
160
|
+
model.cost = { input: 0, output: 0 };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Dynamically populate provider models from Cursor API if available.
|
|
164
|
+
try {
|
|
165
|
+
const cursorClient = new CursorClient(accessToken);
|
|
166
|
+
const models = await listCursorModels(cursorClient);
|
|
167
|
+
if (models.length > 0) {
|
|
168
|
+
for (const m of models) {
|
|
169
|
+
// Determine if this is a "thinking" (reasoning) model
|
|
170
|
+
const isThinking = m.modelId?.includes("thinking") ||
|
|
171
|
+
m.displayModelId?.includes("thinking") ||
|
|
172
|
+
m.displayName?.toLowerCase().includes("thinking");
|
|
173
|
+
// Use displayModelId as the primary ID (user-facing), fall back to modelId
|
|
174
|
+
const modelID = m.displayModelId || m.modelId;
|
|
175
|
+
if (!modelID)
|
|
176
|
+
continue;
|
|
177
|
+
const existingModel = provider.models[modelID];
|
|
178
|
+
const limits = getModelLimits(modelID);
|
|
179
|
+
const parsedModel = {
|
|
180
|
+
id: modelID,
|
|
181
|
+
api: {
|
|
182
|
+
id: modelID,
|
|
183
|
+
npm: "@ai-sdk/openai-compatible",
|
|
184
|
+
url: undefined,
|
|
185
|
+
},
|
|
186
|
+
status: "active",
|
|
187
|
+
name: m.displayName || m.displayNameShort || modelID,
|
|
188
|
+
providerID: CURSOR_PROVIDER_ID,
|
|
189
|
+
capabilities: {
|
|
190
|
+
temperature: true,
|
|
191
|
+
reasoning: isThinking,
|
|
192
|
+
attachment: true,
|
|
193
|
+
toolcall: true,
|
|
194
|
+
input: {
|
|
195
|
+
text: true,
|
|
196
|
+
audio: false,
|
|
197
|
+
image: true,
|
|
198
|
+
video: false,
|
|
199
|
+
pdf: false,
|
|
200
|
+
},
|
|
201
|
+
output: {
|
|
202
|
+
text: true,
|
|
203
|
+
audio: false,
|
|
204
|
+
image: false,
|
|
205
|
+
video: false,
|
|
206
|
+
pdf: false,
|
|
207
|
+
},
|
|
208
|
+
interleaved: false,
|
|
209
|
+
},
|
|
210
|
+
cost: {
|
|
211
|
+
input: 0,
|
|
212
|
+
output: 0,
|
|
213
|
+
cache: {
|
|
214
|
+
read: 0,
|
|
215
|
+
write: 0,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
options: {},
|
|
219
|
+
limit: limits,
|
|
220
|
+
headers: {},
|
|
221
|
+
...existingModel,
|
|
222
|
+
};
|
|
223
|
+
provider.models[modelID] = parsedModel;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
// Silently continue with defaults if model listing fails
|
|
229
|
+
}
|
|
230
|
+
// Create custom fetch function instead of starting proxy server
|
|
231
|
+
const customFetch = createPluginFetch({
|
|
232
|
+
accessToken,
|
|
233
|
+
// Disable logging to avoid polluting the UI
|
|
234
|
+
log: () => { },
|
|
235
|
+
});
|
|
236
|
+
// We need to provide baseURL even when using custom fetch
|
|
237
|
+
// OpenCode uses baseURL to identify the provider/API for the model
|
|
238
|
+
// The actual URL doesn't matter since our fetch intercepts everything
|
|
239
|
+
return {
|
|
240
|
+
apiKey: "cursor-via-opencode", // Dummy key, not used
|
|
241
|
+
baseURL: "https://cursor.opencode.local/v1", // Virtual URL, intercepted by fetch
|
|
242
|
+
fetch: customFetch,
|
|
243
|
+
};
|
|
244
|
+
},
|
|
245
|
+
methods: [
|
|
246
|
+
{
|
|
247
|
+
label: "OAuth with Cursor",
|
|
248
|
+
type: "oauth",
|
|
249
|
+
authorize: async (_inputs) => {
|
|
250
|
+
console.log("\n=== Cursor OAuth Setup ===");
|
|
251
|
+
console.log("1. You'll be asked to sign in to your Cursor account.");
|
|
252
|
+
console.log("2. After signing in, the authentication will complete automatically.");
|
|
253
|
+
console.log("3. Return to this terminal when you see confirmation.\n");
|
|
254
|
+
const loginManager = new LoginManager();
|
|
255
|
+
const { metadata, loginUrl } = loginManager.startLogin();
|
|
256
|
+
return {
|
|
257
|
+
url: loginUrl,
|
|
258
|
+
instructions: "Complete the sign-in flow in your browser. We'll automatically detect when you're done.",
|
|
259
|
+
method: "auto",
|
|
260
|
+
callback: async () => {
|
|
261
|
+
try {
|
|
262
|
+
// Open browser
|
|
263
|
+
try {
|
|
264
|
+
await openBrowser(loginUrl);
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
console.log("Could not open browser automatically. Please visit the URL above.");
|
|
268
|
+
}
|
|
269
|
+
// Wait for authentication
|
|
270
|
+
const result = await loginManager.waitForResult(metadata, {
|
|
271
|
+
onProgress: () => process.stdout.write("."),
|
|
272
|
+
});
|
|
273
|
+
if (!result) {
|
|
274
|
+
return {
|
|
275
|
+
type: "failed",
|
|
276
|
+
error: "Authentication timed out or was cancelled",
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
// Get token expiration
|
|
280
|
+
let expires = Date.now() + 3600 * 1000; // 1 hour default
|
|
281
|
+
const payload = decodeJwtPayload(result.accessToken);
|
|
282
|
+
if (payload?.exp && typeof payload.exp === "number") {
|
|
283
|
+
expires = payload.exp * 1000;
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
type: "success",
|
|
287
|
+
refresh: result.refreshToken,
|
|
288
|
+
access: result.accessToken,
|
|
289
|
+
expires,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
return {
|
|
294
|
+
type: "failed",
|
|
295
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
label: "Manually enter API Key",
|
|
304
|
+
type: "api",
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/plugin/plugin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Plugin Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the OpenCode plugin system.
|
|
5
|
+
* These types mirror the @opencode-ai/plugin interface.
|
|
6
|
+
*/
|
|
7
|
+
export interface OAuthAuthDetails {
|
|
8
|
+
type: "oauth";
|
|
9
|
+
refresh: string;
|
|
10
|
+
access?: string;
|
|
11
|
+
expires?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ApiKeyAuthDetails {
|
|
14
|
+
type: "api";
|
|
15
|
+
apiKey: string;
|
|
16
|
+
}
|
|
17
|
+
export interface TokenAuthDetails {
|
|
18
|
+
type: "token";
|
|
19
|
+
token: string;
|
|
20
|
+
}
|
|
21
|
+
export type AuthDetails = OAuthAuthDetails | ApiKeyAuthDetails | TokenAuthDetails | {
|
|
22
|
+
type: string;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
};
|
|
25
|
+
export type GetAuth = () => Promise<AuthDetails>;
|
|
26
|
+
export interface ProviderModel {
|
|
27
|
+
cost?: {
|
|
28
|
+
input: number;
|
|
29
|
+
output: number;
|
|
30
|
+
};
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
export interface Provider {
|
|
34
|
+
id?: string;
|
|
35
|
+
name?: string;
|
|
36
|
+
models?: Record<string, ProviderModel | undefined>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* RequestInfo type for fetch-like functions
|
|
40
|
+
*/
|
|
41
|
+
export type FetchInput = Request | string | URL;
|
|
42
|
+
export interface LoaderResult {
|
|
43
|
+
apiKey?: string;
|
|
44
|
+
baseURL?: string;
|
|
45
|
+
fetch?(input: FetchInput, init?: RequestInit): Promise<Response>;
|
|
46
|
+
}
|
|
47
|
+
export interface TokenExchangeSuccess {
|
|
48
|
+
type: "success";
|
|
49
|
+
refresh: string;
|
|
50
|
+
access: string;
|
|
51
|
+
expires: number;
|
|
52
|
+
}
|
|
53
|
+
export interface TokenExchangeFailure {
|
|
54
|
+
type: "failed";
|
|
55
|
+
error?: string;
|
|
56
|
+
}
|
|
57
|
+
export type TokenExchangeResult = TokenExchangeSuccess | TokenExchangeFailure;
|
|
58
|
+
export interface OAuthAuthMethod {
|
|
59
|
+
label: string;
|
|
60
|
+
type: "oauth";
|
|
61
|
+
prompts?: AuthPrompt[];
|
|
62
|
+
authorize: (inputs?: Record<string, string>) => Promise<{
|
|
63
|
+
url: string;
|
|
64
|
+
instructions: string;
|
|
65
|
+
method: "auto" | "code";
|
|
66
|
+
callback: (callbackUrl?: string) => Promise<TokenExchangeResult>;
|
|
67
|
+
}>;
|
|
68
|
+
}
|
|
69
|
+
export interface AuthPrompt {
|
|
70
|
+
type: "text" | "select";
|
|
71
|
+
key: string;
|
|
72
|
+
message: string;
|
|
73
|
+
placeholder?: string;
|
|
74
|
+
validate?: (value: string) => string | undefined;
|
|
75
|
+
condition?: (inputs: Record<string, string>) => boolean;
|
|
76
|
+
options?: Array<{
|
|
77
|
+
label: string;
|
|
78
|
+
value: string;
|
|
79
|
+
hint?: string;
|
|
80
|
+
}>;
|
|
81
|
+
}
|
|
82
|
+
export interface ApiKeyAuthMethod {
|
|
83
|
+
label: string;
|
|
84
|
+
type: "api";
|
|
85
|
+
prompts?: AuthPrompt[];
|
|
86
|
+
authorize?: (inputs?: Record<string, string>) => Promise<{
|
|
87
|
+
type: "success";
|
|
88
|
+
key: string;
|
|
89
|
+
provider?: string;
|
|
90
|
+
} | {
|
|
91
|
+
type: "failed";
|
|
92
|
+
}>;
|
|
93
|
+
}
|
|
94
|
+
export type AuthMethod = OAuthAuthMethod | ApiKeyAuthMethod;
|
|
95
|
+
export interface PluginClient {
|
|
96
|
+
auth: {
|
|
97
|
+
set(input: {
|
|
98
|
+
path: {
|
|
99
|
+
id: string;
|
|
100
|
+
};
|
|
101
|
+
body: AuthDetails;
|
|
102
|
+
}): Promise<void>;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export interface PluginContext {
|
|
106
|
+
client: PluginClient;
|
|
107
|
+
}
|
|
108
|
+
export interface PluginResult {
|
|
109
|
+
auth: {
|
|
110
|
+
provider: string;
|
|
111
|
+
loader: (getAuth: GetAuth, provider: Provider) => Promise<LoaderResult | null>;
|
|
112
|
+
methods: AuthMethod[];
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export type Plugin = (context: PluginContext) => Promise<PluginResult>;
|
|
116
|
+
export interface CursorAuthRecord {
|
|
117
|
+
accessToken: string;
|
|
118
|
+
refreshToken: string;
|
|
119
|
+
expires?: number;
|
|
120
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/plugin/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-Compatible API Server
|
|
3
|
+
*
|
|
4
|
+
* Routes OpenAI API requests through Cursor's Agent API backend.
|
|
5
|
+
* Supports:
|
|
6
|
+
* - POST /v1/chat/completions (streaming and non-streaming)
|
|
7
|
+
* - GET /v1/models
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* CURSOR_ACCESS_TOKEN=<token> bun run src/server.ts
|
|
11
|
+
*
|
|
12
|
+
* Or with auto-loaded credentials:
|
|
13
|
+
* bun run src/server.ts
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-Compatible API Server
|
|
3
|
+
*
|
|
4
|
+
* Routes OpenAI API requests through Cursor's Agent API backend.
|
|
5
|
+
* Supports:
|
|
6
|
+
* - POST /v1/chat/completions (streaming and non-streaming)
|
|
7
|
+
* - GET /v1/models
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* CURSOR_ACCESS_TOKEN=<token> bun run src/server.ts
|
|
11
|
+
*
|
|
12
|
+
* Or with auto-loaded credentials:
|
|
13
|
+
* bun run src/server.ts
|
|
14
|
+
*/
|
|
15
|
+
import { FileCredentialManager } from "./lib/storage";
|
|
16
|
+
import { createRequestHandler } from "./lib/openai-compat";
|
|
17
|
+
// Debug logging - set CURSOR_DEBUG=1 to enable
|
|
18
|
+
const DEBUG = process.env.CURSOR_DEBUG === "1";
|
|
19
|
+
const debugLog = DEBUG ? console.log.bind(console) : () => { };
|
|
20
|
+
// --- Server Configuration ---
|
|
21
|
+
const PORT = Number.parseInt(process.env.PORT ?? "18741", 10);
|
|
22
|
+
// --- Authentication ---
|
|
23
|
+
async function getAccessToken() {
|
|
24
|
+
// First check environment variable
|
|
25
|
+
const envToken = process.env.CURSOR_ACCESS_TOKEN;
|
|
26
|
+
if (envToken) {
|
|
27
|
+
return envToken;
|
|
28
|
+
}
|
|
29
|
+
// Fall back to credential manager
|
|
30
|
+
const cm = new FileCredentialManager("cursor");
|
|
31
|
+
const token = await cm.getAccessToken();
|
|
32
|
+
if (!token) {
|
|
33
|
+
throw new Error("No access token found. Set CURSOR_ACCESS_TOKEN or authenticate first.");
|
|
34
|
+
}
|
|
35
|
+
return token;
|
|
36
|
+
}
|
|
37
|
+
// --- Main ---
|
|
38
|
+
debugLog("Starting OpenAI-compatible API server...");
|
|
39
|
+
let accessToken;
|
|
40
|
+
try {
|
|
41
|
+
accessToken = await getAccessToken();
|
|
42
|
+
debugLog("Access token loaded successfully");
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
console.error("Failed to get access token:", err instanceof Error ? err.message : String(err));
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
// Create the request handler from shared module
|
|
49
|
+
const handleRequest = createRequestHandler({
|
|
50
|
+
accessToken,
|
|
51
|
+
log: debugLog,
|
|
52
|
+
});
|
|
53
|
+
Bun.serve({
|
|
54
|
+
port: PORT,
|
|
55
|
+
idleTimeout: 120, // 2 minutes to allow for long tool executions
|
|
56
|
+
async fetch(req) {
|
|
57
|
+
const url = new URL(req.url);
|
|
58
|
+
// Enhanced health check with version info (server-specific)
|
|
59
|
+
if (url.pathname === "/health" || url.pathname === "/") {
|
|
60
|
+
return new Response(JSON.stringify({ status: "ok", version: "1.0.0" }), {
|
|
61
|
+
headers: {
|
|
62
|
+
"Content-Type": "application/json",
|
|
63
|
+
"Access-Control-Allow-Origin": "*",
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Delegate to shared request handler
|
|
68
|
+
return handleRequest(req);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
debugLog(`
|
|
72
|
+
╔════════════════════════════════════════════════════════════╗
|
|
73
|
+
║ OpenAI-Compatible API Server ║
|
|
74
|
+
╠════════════════════════════════════════════════════════════╣
|
|
75
|
+
║ Server running on http://localhost:${PORT.toString().padEnd(24)}║
|
|
76
|
+
║ ║
|
|
77
|
+
║ Endpoints: ║
|
|
78
|
+
║ POST /v1/chat/completions - Chat completions ║
|
|
79
|
+
║ GET /v1/models - List available models ║
|
|
80
|
+
║ GET /health - Health check ║
|
|
81
|
+
║ ║
|
|
82
|
+
║ Usage with curl: ║
|
|
83
|
+
║ curl http://localhost:${PORT}/v1/chat/completions ${" ".repeat(Math.max(0, 6 - PORT.toString().length))}║
|
|
84
|
+
║ -H "Content-Type: application/json" \ ║
|
|
85
|
+
║ -d '{"model":"gpt-4o","messages":[...]}' ║
|
|
86
|
+
║ ║
|
|
87
|
+
║ Usage with OpenAI SDK: ║
|
|
88
|
+
║ const openai = new OpenAI({ ║
|
|
89
|
+
║ baseURL: "http://localhost:${PORT}/v1",${" ".repeat(Math.max(0, 20 - PORT.toString().length))}║
|
|
90
|
+
║ apiKey: "not-needed" ║
|
|
91
|
+
║ }); ║
|
|
92
|
+
|
|
93
|
+
╚════════════════════════════════════════════════════════════╝
|
|
94
|
+
`);
|
|
95
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,+CAA+C;AAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,GAAG,CAAC;AAC/C,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;AAE9D,+BAA+B;AAE/B,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,EAAE,EAAE,CAAC,CAAC;AAE9D,yBAAyB;AAEzB,KAAK,UAAU,cAAc;IAC3B,mCAAmC;IACnC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;IACjD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,kCAAkC;IAClC,MAAM,EAAE,GAAG,IAAI,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,cAAc,EAAE,CAAC;IACxC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,eAAe;AAEf,QAAQ,CAAC,0CAA0C,CAAC,CAAC;AAErD,IAAI,WAAmB,CAAC;AACxB,IAAI,CAAC;IACH,WAAW,GAAG,MAAM,cAAc,EAAE,CAAC;IACrC,QAAQ,CAAC,kCAAkC,CAAC,CAAC;AAC/C,CAAC;AAAC,OAAO,GAAG,EAAE,CAAC;IACb,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,gDAAgD;AAChD,MAAM,aAAa,GAAG,oBAAoB,CAAC;IACzC,WAAW;IACX,GAAG,EAAE,QAAQ;CACd,CAAC,CAAC;AAEH,GAAG,CAAC,KAAK,CAAC;IACR,IAAI,EAAE,IAAI;IACV,WAAW,EAAE,GAAG,EAAE,8CAA8C;IAEhE,KAAK,CAAC,KAAK,CAAC,GAAG;QACb,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAE7B,4DAA4D;QAC5D,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,GAAG,CAAC,QAAQ,KAAK,GAAG,EAAE,CAAC;YACvD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;gBACtE,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,6BAA6B,EAAE,GAAG;iBACnC;aACF,CAAC,CAAC;QACL,CAAC;QAED,qCAAqC;QACrC,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;CACF,CAAC,CAAC;AAEH,QAAQ,CAAC;;;;wCAI+B,IAAI,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;;;;;;;;6BAQrC,IAAI,wBAAwB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAC;;;;;;oCAMxE,IAAI,QAAQ,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAC;;;;;CAKnG,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-cursor-proxy",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "OpenCode plugin for Cursor's AI backend",
|
|
5
|
+
"author": "Morse Wayne",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/MorseWayne/opencode-cursor-proxy"
|
|
10
|
+
},
|
|
11
|
+
"module": "dist/index.js",
|
|
12
|
+
"main": "dist/index.js",
|
|
13
|
+
"types": "dist/index.d.ts",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"README.zh-CN.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts"
|
|
25
|
+
},
|
|
26
|
+
"./plugin": {
|
|
27
|
+
"import": "./dist/plugin/index.js",
|
|
28
|
+
"types": "./dist/plugin/index.d.ts"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "rm -rf dist && bun x tsc -p tsconfig.build.json",
|
|
33
|
+
"start": "bun run src/server.ts",
|
|
34
|
+
"server": "bun run src/server.ts",
|
|
35
|
+
"proxy": "bun run src/server.ts",
|
|
36
|
+
"auth": "bun run scripts/auth.ts",
|
|
37
|
+
"auth:status": "bun run scripts/auth.ts status",
|
|
38
|
+
"auth:login": "bun run scripts/auth.ts login",
|
|
39
|
+
"auth:logout": "bun run scripts/auth.ts logout",
|
|
40
|
+
"models": "bun run scripts/fetch-models.ts",
|
|
41
|
+
"test": "bun test tests/unit",
|
|
42
|
+
"test:integration": "bun test tests/integration",
|
|
43
|
+
"test:all": "bun test tests"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"cursor",
|
|
47
|
+
"opencode",
|
|
48
|
+
"openai",
|
|
49
|
+
"api",
|
|
50
|
+
"proxy",
|
|
51
|
+
"llm",
|
|
52
|
+
"ai",
|
|
53
|
+
"tool-calling",
|
|
54
|
+
"streaming",
|
|
55
|
+
"claude",
|
|
56
|
+
"gpt"
|
|
57
|
+
],
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@opencode-ai/plugin": "^0.13.7",
|
|
60
|
+
"@opencode-ai/sdk": "^0.13.9",
|
|
61
|
+
"@types/babel__generator": "^7.27.0",
|
|
62
|
+
"@types/bun": "latest",
|
|
63
|
+
"@types/node": "^25.0.9",
|
|
64
|
+
"typescript": "^5"
|
|
65
|
+
},
|
|
66
|
+
"peerDependencies": {
|
|
67
|
+
"@opencode-ai/plugin": "^0.13.7",
|
|
68
|
+
"typescript": "^5"
|
|
69
|
+
},
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"@anthropic-ai/tokenizer": "^0.0.4",
|
|
72
|
+
"@babel/generator": "^7.28.5",
|
|
73
|
+
"@babel/parser": "^7.28.5",
|
|
74
|
+
"@babel/types": "^7.28.5",
|
|
75
|
+
"@bufbuild/protobuf": "1.10.0",
|
|
76
|
+
"gpt-tokenizer": "^3.4.0",
|
|
77
|
+
"llm-info": "^1.0.69"
|
|
78
|
+
}
|
|
79
|
+
}
|