@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 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
@@ -4,6 +4,7 @@ interface CursorUnaryRpcOptions {
4
4
  requestBody: Uint8Array;
5
5
  url?: string;
6
6
  timeoutMs?: number;
7
+ transport?: "auto" | "fetch" | "http2";
7
8
  }
8
9
  export declare function callCursorUnaryRpc(options: CursorUnaryRpcOptions): Promise<{
9
10
  body: Uint8Array;
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
- for (const [key, value] of Object.entries(extra)) {
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(new URL(options.rpcPath, options.url ?? CURSOR_API_URL), {
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.17eadae36ea6",
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",