@playwo/opencode-cursor-oauth 0.0.0-dev.17eadae36ea6 → 0.0.0-dev.b8e6dd72a8b6
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/dist/models.js +5 -2
- package/dist/proxy.d.ts +1 -0
- package/dist/proxy.js +116 -8
- package/package.json +1 -1
package/dist/models.js
CHANGED
|
@@ -73,10 +73,13 @@ export function clearModelCache() {
|
|
|
73
73
|
}
|
|
74
74
|
function buildDiscoveryHttpError(exitCode, body) {
|
|
75
75
|
const detail = extractDiscoveryErrorDetail(body);
|
|
76
|
+
const protocolHint = exitCode === 464
|
|
77
|
+
? " Likely protocol mismatch: Cursor appears to expect an HTTP/2 Connect unary request."
|
|
78
|
+
: "";
|
|
76
79
|
if (!detail) {
|
|
77
|
-
return `Cursor model discovery failed with HTTP ${exitCode}
|
|
80
|
+
return `Cursor model discovery failed with HTTP ${exitCode}.${protocolHint}`;
|
|
78
81
|
}
|
|
79
|
-
return `Cursor model discovery failed with HTTP ${exitCode}: ${detail}`;
|
|
82
|
+
return `Cursor model discovery failed with HTTP ${exitCode}: ${detail}.${protocolHint}`;
|
|
80
83
|
}
|
|
81
84
|
function extractDiscoveryErrorDetail(body) {
|
|
82
85
|
if (body.length === 0)
|
package/dist/proxy.d.ts
CHANGED
package/dist/proxy.js
CHANGED
|
@@ -16,8 +16,10 @@ import { create, fromBinary, fromJson, toBinary, toJson } from "@bufbuild/protob
|
|
|
16
16
|
import { ValueSchema } from "@bufbuild/protobuf/wkt";
|
|
17
17
|
import { AgentClientMessageSchema, AgentRunRequestSchema, AgentServerMessageSchema, BidiRequestIdSchema, ClientHeartbeatSchema, ConversationActionSchema, ConversationStateStructureSchema, ConversationStepSchema, AgentConversationTurnStructureSchema, ConversationTurnStructureSchema, AssistantMessageSchema, BackgroundShellSpawnResultSchema, DeleteResultSchema, DeleteRejectedSchema, DiagnosticsResultSchema, ExecClientMessageSchema, FetchErrorSchema, FetchResultSchema, GetBlobResultSchema, GrepErrorSchema, GrepResultSchema, KvClientMessageSchema, LsRejectedSchema, LsResultSchema, McpErrorSchema, McpResultSchema, McpSuccessSchema, McpTextContentSchema, McpToolDefinitionSchema, McpToolResultContentItemSchema, ModelDetailsSchema, ReadRejectedSchema, ReadResultSchema, RequestContextResultSchema, RequestContextSchema, RequestContextSuccessSchema, SetBlobResultSchema, ShellRejectedSchema, ShellResultSchema, UserMessageActionSchema, UserMessageSchema, WriteRejectedSchema, WriteResultSchema, WriteShellStdinErrorSchema, WriteShellStdinResultSchema, } from "./proto/agent_pb";
|
|
18
18
|
import { createHash } from "node:crypto";
|
|
19
|
+
import { connect as connectHttp2 } from "node:http2";
|
|
19
20
|
const CURSOR_API_URL = process.env.CURSOR_API_URL ?? "https://api2.cursor.sh";
|
|
20
21
|
const CURSOR_CLIENT_VERSION = "cli-2026.01.09-231024f";
|
|
22
|
+
const CURSOR_CONNECT_PROTOCOL_VERSION = "1";
|
|
21
23
|
const CONNECT_END_STREAM_FLAG = 0b00000010;
|
|
22
24
|
const SSE_HEADERS = {
|
|
23
25
|
"Content-Type": "text/event-stream",
|
|
@@ -47,18 +49,19 @@ function frameConnectMessage(data, flags = 0) {
|
|
|
47
49
|
return frame;
|
|
48
50
|
}
|
|
49
51
|
function buildCursorHeaders(options, contentType, extra = {}) {
|
|
50
|
-
const headers = new Headers(
|
|
52
|
+
const headers = new Headers(buildCursorHeaderValues(options, contentType, extra));
|
|
53
|
+
return headers;
|
|
54
|
+
}
|
|
55
|
+
function buildCursorHeaderValues(options, contentType, extra = {}) {
|
|
56
|
+
return {
|
|
51
57
|
authorization: `Bearer ${options.accessToken}`,
|
|
52
58
|
"content-type": contentType,
|
|
53
59
|
"x-ghost-mode": "true",
|
|
54
60
|
"x-cursor-client-version": CURSOR_CLIENT_VERSION,
|
|
55
61
|
"x-cursor-client-type": "cli",
|
|
56
62
|
"x-request-id": crypto.randomUUID(),
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
headers.set(key, value);
|
|
60
|
-
}
|
|
61
|
-
return headers;
|
|
63
|
+
...extra,
|
|
64
|
+
};
|
|
62
65
|
}
|
|
63
66
|
function encodeVarint(value) {
|
|
64
67
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
@@ -236,6 +239,17 @@ async function createCursorSession(options) {
|
|
|
236
239
|
};
|
|
237
240
|
}
|
|
238
241
|
export async function callCursorUnaryRpc(options) {
|
|
242
|
+
const target = new URL(options.rpcPath, options.url ?? CURSOR_API_URL);
|
|
243
|
+
const transport = options.transport ?? "auto";
|
|
244
|
+
if (transport === "http2" || (transport === "auto" && target.protocol === "https:")) {
|
|
245
|
+
const http2Result = await callCursorUnaryRpcOverHttp2(options, target);
|
|
246
|
+
if (transport === "http2" || http2Result.timedOut || http2Result.exitCode !== 1) {
|
|
247
|
+
return http2Result;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return callCursorUnaryRpcOverFetch(options, target);
|
|
251
|
+
}
|
|
252
|
+
async function callCursorUnaryRpcOverFetch(options, target) {
|
|
239
253
|
let timedOut = false;
|
|
240
254
|
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
241
255
|
const controller = new AbortController();
|
|
@@ -246,9 +260,13 @@ export async function callCursorUnaryRpc(options) {
|
|
|
246
260
|
}, timeoutMs)
|
|
247
261
|
: undefined;
|
|
248
262
|
try {
|
|
249
|
-
const response = await fetch(
|
|
263
|
+
const response = await fetch(target, {
|
|
250
264
|
method: "POST",
|
|
251
|
-
headers: buildCursorHeaders(options, "application/proto"
|
|
265
|
+
headers: buildCursorHeaders(options, "application/proto", {
|
|
266
|
+
accept: "application/proto, application/json",
|
|
267
|
+
"connect-protocol-version": CURSOR_CONNECT_PROTOCOL_VERSION,
|
|
268
|
+
"connect-timeout-ms": String(timeoutMs),
|
|
269
|
+
}),
|
|
252
270
|
body: toFetchBody(options.requestBody),
|
|
253
271
|
signal: controller.signal,
|
|
254
272
|
});
|
|
@@ -271,6 +289,96 @@ export async function callCursorUnaryRpc(options) {
|
|
|
271
289
|
clearTimeout(timeout);
|
|
272
290
|
}
|
|
273
291
|
}
|
|
292
|
+
async function callCursorUnaryRpcOverHttp2(options, target) {
|
|
293
|
+
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
294
|
+
const authority = `${target.protocol}//${target.host}`;
|
|
295
|
+
return new Promise((resolve) => {
|
|
296
|
+
let settled = false;
|
|
297
|
+
let timedOut = false;
|
|
298
|
+
let session;
|
|
299
|
+
let stream;
|
|
300
|
+
const finish = (result) => {
|
|
301
|
+
if (settled)
|
|
302
|
+
return;
|
|
303
|
+
settled = true;
|
|
304
|
+
if (timeout)
|
|
305
|
+
clearTimeout(timeout);
|
|
306
|
+
try {
|
|
307
|
+
stream?.close();
|
|
308
|
+
}
|
|
309
|
+
catch { }
|
|
310
|
+
try {
|
|
311
|
+
session?.close();
|
|
312
|
+
}
|
|
313
|
+
catch { }
|
|
314
|
+
resolve(result);
|
|
315
|
+
};
|
|
316
|
+
const timeout = timeoutMs > 0
|
|
317
|
+
? setTimeout(() => {
|
|
318
|
+
timedOut = true;
|
|
319
|
+
finish({
|
|
320
|
+
body: new Uint8Array(),
|
|
321
|
+
exitCode: 124,
|
|
322
|
+
timedOut: true,
|
|
323
|
+
});
|
|
324
|
+
}, timeoutMs)
|
|
325
|
+
: undefined;
|
|
326
|
+
try {
|
|
327
|
+
session = connectHttp2(authority);
|
|
328
|
+
session.once("error", () => {
|
|
329
|
+
finish({
|
|
330
|
+
body: new Uint8Array(),
|
|
331
|
+
exitCode: timedOut ? 124 : 1,
|
|
332
|
+
timedOut,
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
const headers = {
|
|
336
|
+
":method": "POST",
|
|
337
|
+
":path": `${target.pathname}${target.search}`,
|
|
338
|
+
...buildCursorHeaderValues(options, "application/proto", {
|
|
339
|
+
accept: "application/proto, application/json",
|
|
340
|
+
"connect-protocol-version": CURSOR_CONNECT_PROTOCOL_VERSION,
|
|
341
|
+
"connect-timeout-ms": String(timeoutMs),
|
|
342
|
+
}),
|
|
343
|
+
};
|
|
344
|
+
stream = session.request(headers);
|
|
345
|
+
let statusCode = 0;
|
|
346
|
+
const chunks = [];
|
|
347
|
+
stream.once("response", (responseHeaders) => {
|
|
348
|
+
const statusHeader = responseHeaders[":status"];
|
|
349
|
+
statusCode = typeof statusHeader === "number"
|
|
350
|
+
? statusHeader
|
|
351
|
+
: Number(statusHeader ?? 0);
|
|
352
|
+
});
|
|
353
|
+
stream.on("data", (chunk) => {
|
|
354
|
+
chunks.push(Buffer.from(chunk));
|
|
355
|
+
});
|
|
356
|
+
stream.once("end", () => {
|
|
357
|
+
const body = new Uint8Array(Buffer.concat(chunks));
|
|
358
|
+
finish({
|
|
359
|
+
body,
|
|
360
|
+
exitCode: statusCode >= 200 && statusCode < 300 ? 0 : (statusCode || 1),
|
|
361
|
+
timedOut,
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
stream.once("error", () => {
|
|
365
|
+
finish({
|
|
366
|
+
body: new Uint8Array(),
|
|
367
|
+
exitCode: timedOut ? 124 : 1,
|
|
368
|
+
timedOut,
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
stream.end(Buffer.from(options.requestBody));
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
finish({
|
|
375
|
+
body: new Uint8Array(),
|
|
376
|
+
exitCode: timedOut ? 124 : 1,
|
|
377
|
+
timedOut,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
274
382
|
let proxyServer;
|
|
275
383
|
let proxyPort;
|
|
276
384
|
let proxyAccessTokenProvider;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playwo/opencode-cursor-oauth",
|
|
3
|
-
"version": "0.0.0-dev.
|
|
3
|
+
"version": "0.0.0-dev.b8e6dd72a8b6",
|
|
4
4
|
"description": "OpenCode plugin that connects Cursor's API to OpenCode via OAuth, model discovery, and a local OpenAI-compatible proxy.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|