@playwo/opencode-cursor-oauth 0.0.0-dev.a9d6c62f0dd9 → 0.0.0-dev.c80ebcb27754

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 CHANGED
@@ -44,7 +44,7 @@ OpenAI-compatible proxy on demand and routes requests through Cursor's gRPC API.
44
44
  ## How it works
45
45
 
46
46
  1. OAuth — browser-based login to Cursor via PKCE.
47
- 2. Model discovery — queries Cursor's gRPC API for all available models and fails plugin loading visibly if discovery does not succeed.
47
+ 2. Model discovery — queries Cursor's gRPC API for all available models.
48
48
  3. Local proxy — translates `POST /v1/chat/completions` into Cursor's
49
49
  protobuf/Connect protocol.
50
50
  4. Native tool routing — rejects Cursor's built-in filesystem/shell tools and
package/dist/models.d.ts CHANGED
@@ -5,9 +5,6 @@ export interface CursorModel {
5
5
  contextWindow: number;
6
6
  maxTokens: number;
7
7
  }
8
- export declare class CursorModelDiscoveryError extends Error {
9
- constructor(message: string);
10
- }
11
8
  export declare function getCursorModels(apiKey: string): Promise<CursorModel[]>;
12
9
  /** @internal Test-only. */
13
10
  export declare function clearModelCache(): void;
package/dist/models.js CHANGED
@@ -1,9 +1,13 @@
1
+ /**
2
+ * Cursor model discovery via GetUsableModels.
3
+ * Uses the H2 bridge for transport. Falls back to a hardcoded list
4
+ * when discovery fails.
5
+ */
1
6
  import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
2
7
  import { z } from "zod";
3
8
  import { callCursorUnaryRpc } from "./proxy";
4
9
  import { GetUsableModelsRequestSchema, GetUsableModelsResponseSchema, } from "./proto/agent_pb";
5
10
  const GET_USABLE_MODELS_PATH = "/agent.v1.AgentService/GetUsableModels";
6
- const MODEL_DISCOVERY_TIMEOUT_MS = 5_000;
7
11
  const DEFAULT_CONTEXT_WINDOW = 200_000;
8
12
  const DEFAULT_MAX_TOKENS = 64_000;
9
13
  const CursorModelDetailsSchema = z.object({
@@ -18,12 +22,24 @@ const CursorModelDetailsSchema = z.object({
18
22
  .transform((aliases) => (aliases ?? []).filter((alias) => typeof alias === "string")),
19
23
  thinkingDetails: z.unknown().optional(),
20
24
  });
21
- export class CursorModelDiscoveryError extends Error {
22
- constructor(message) {
23
- super(message);
24
- this.name = "CursorModelDiscoveryError";
25
- }
26
- }
25
+ const FALLBACK_MODELS = [
26
+ // Composer models
27
+ { id: "composer-1", name: "Composer 1", reasoning: true, contextWindow: 200_000, maxTokens: 64_000 },
28
+ { id: "composer-1.5", name: "Composer 1.5", reasoning: true, contextWindow: 200_000, maxTokens: 64_000 },
29
+ // Claude models
30
+ { id: "claude-4.6-opus-high", name: "Claude 4.6 Opus", reasoning: true, contextWindow: 200_000, maxTokens: 128_000 },
31
+ { id: "claude-4.6-sonnet-medium", name: "Claude 4.6 Sonnet", reasoning: true, contextWindow: 200_000, maxTokens: 64_000 },
32
+ { id: "claude-4.5-sonnet", name: "Claude 4.5 Sonnet", reasoning: true, contextWindow: 200_000, maxTokens: 64_000 },
33
+ // GPT models
34
+ { id: "gpt-5.4-medium", name: "GPT-5.4", reasoning: true, contextWindow: 272_000, maxTokens: 128_000 },
35
+ { id: "gpt-5.2", name: "GPT-5.2", reasoning: true, contextWindow: 400_000, maxTokens: 128_000 },
36
+ { id: "gpt-5.2-codex", name: "GPT-5.2 Codex", reasoning: true, contextWindow: 400_000, maxTokens: 128_000 },
37
+ { id: "gpt-5.3-codex", name: "GPT-5.3 Codex", reasoning: true, contextWindow: 400_000, maxTokens: 128_000 },
38
+ { id: "gpt-5.3-codex-spark-preview", name: "GPT-5.3 Codex Spark", reasoning: true, contextWindow: 128_000, maxTokens: 128_000 },
39
+ // Other models
40
+ { id: "gemini-3.1-pro", name: "Gemini 3.1 Pro", reasoning: true, contextWindow: 1_000_000, maxTokens: 64_000 },
41
+ { id: "grok-code-fast-1", name: "Grok Code Fast 1", reasoning: false, contextWindow: 128_000, maxTokens: 64_000 },
42
+ ];
27
43
  async function fetchCursorUsableModels(apiKey) {
28
44
  try {
29
45
  const requestPayload = create(GetUsableModelsRequestSchema, {});
@@ -32,31 +48,18 @@ async function fetchCursorUsableModels(apiKey) {
32
48
  accessToken: apiKey,
33
49
  rpcPath: GET_USABLE_MODELS_PATH,
34
50
  requestBody,
35
- timeoutMs: MODEL_DISCOVERY_TIMEOUT_MS,
36
51
  });
37
- if (response.timedOut) {
38
- throw new CursorModelDiscoveryError(`Cursor model discovery timed out after ${MODEL_DISCOVERY_TIMEOUT_MS}ms.`);
39
- }
40
- if (response.exitCode !== 0) {
41
- throw new CursorModelDiscoveryError(buildDiscoveryHttpError(response.exitCode, response.body));
42
- }
43
- if (response.body.length === 0) {
44
- throw new CursorModelDiscoveryError("Cursor model discovery returned an empty response.");
52
+ if (response.timedOut || response.exitCode !== 0 || response.body.length === 0) {
53
+ return null;
45
54
  }
46
55
  const decoded = decodeGetUsableModelsResponse(response.body);
47
- if (!decoded) {
48
- throw new CursorModelDiscoveryError("Cursor model discovery returned an unreadable response.");
49
- }
56
+ if (!decoded)
57
+ return null;
50
58
  const models = normalizeCursorModels(decoded.models);
51
- if (models.length === 0) {
52
- throw new CursorModelDiscoveryError("Cursor model discovery returned no usable models.");
53
- }
54
- return models;
59
+ return models.length > 0 ? models : null;
55
60
  }
56
- catch (error) {
57
- if (error instanceof CursorModelDiscoveryError)
58
- throw error;
59
- throw new CursorModelDiscoveryError("Cursor model discovery failed.");
61
+ catch {
62
+ return null;
60
63
  }
61
64
  }
62
65
  let cachedModels = null;
@@ -64,40 +67,13 @@ export async function getCursorModels(apiKey) {
64
67
  if (cachedModels)
65
68
  return cachedModels;
66
69
  const discovered = await fetchCursorUsableModels(apiKey);
67
- cachedModels = discovered;
70
+ cachedModels = discovered && discovered.length > 0 ? discovered : FALLBACK_MODELS;
68
71
  return cachedModels;
69
72
  }
70
73
  /** @internal Test-only. */
71
74
  export function clearModelCache() {
72
75
  cachedModels = null;
73
76
  }
74
- function buildDiscoveryHttpError(exitCode, body) {
75
- const detail = extractDiscoveryErrorDetail(body);
76
- if (!detail) {
77
- return `Cursor model discovery failed with HTTP ${exitCode}.`;
78
- }
79
- return `Cursor model discovery failed with HTTP ${exitCode}: ${detail}`;
80
- }
81
- function extractDiscoveryErrorDetail(body) {
82
- if (body.length === 0)
83
- return null;
84
- const text = new TextDecoder().decode(body).trim();
85
- if (!text)
86
- return null;
87
- try {
88
- const parsed = JSON.parse(text);
89
- const code = typeof parsed.code === "string" ? parsed.code : undefined;
90
- const message = typeof parsed.message === "string" ? parsed.message : undefined;
91
- if (message && code)
92
- return `${message} (${code})`;
93
- if (message)
94
- return message;
95
- if (code)
96
- return code;
97
- }
98
- catch { }
99
- return text.length > 200 ? `${text.slice(0, 197)}...` : text;
100
- }
101
77
  function decodeGetUsableModelsResponse(payload) {
102
78
  try {
103
79
  return fromBinary(GetUsableModelsResponseSchema, payload);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playwo/opencode-cursor-oauth",
3
- "version": "0.0.0-dev.a9d6c62f0dd9",
3
+ "version": "0.0.0-dev.c80ebcb27754",
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",