@magpie-community/opencode-cursor-auth 0.1.0 → 0.1.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/index.mjs +86 -1
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
// OpenCode speaks chat completions to it (@ai-sdk/openai-compatible); the
|
|
26
26
|
// fetch here answers them.
|
|
27
27
|
import http2 from "node:http2"
|
|
28
|
+
import { STATUS_CODES } from "node:http"
|
|
28
29
|
import { gunzipSync } from "node:zlib"
|
|
29
30
|
import { createHash, randomBytes, randomUUID } from "node:crypto"
|
|
30
31
|
import { execFile } from "node:child_process"
|
|
@@ -318,6 +319,79 @@ async function cliSignIn() {
|
|
|
318
319
|
|
|
319
320
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
320
321
|
|
|
322
|
+
// ---- usage ------------------------------------------------------------------------
|
|
323
|
+
//
|
|
324
|
+
// How much of the plan's included usage is gone, as magpie's built-in Cursor
|
|
325
|
+
// account shows it (internal/provider/cursor_usage.go): the dashboard's
|
|
326
|
+
// current period, split into the Cursor Models and Other Models pools.
|
|
327
|
+
|
|
328
|
+
// statusText is a refused request's status as magpie says it (Go's
|
|
329
|
+
// http.StatusText).
|
|
330
|
+
const statusText = (s) =>
|
|
331
|
+
({ 413: "Request Entity Too Large", 414: "Request URI Too Long", 416: "Requested Range Not Satisfiable", 418: "I'm a teapot", 509: "" })[s] ?? STATUS_CODES[s] ?? ""
|
|
332
|
+
|
|
333
|
+
const POOL_VARIANT = /-(fast|none|low|medium|high|xhigh|extra-high|max|thinking)$/
|
|
334
|
+
|
|
335
|
+
// poolBase names a model's family: lower case, without cursor- and the
|
|
336
|
+
// effort and speed the CLI adds to it.
|
|
337
|
+
function poolBase(model) {
|
|
338
|
+
model = String(model).trim().toLowerCase()
|
|
339
|
+
if (model.startsWith("cursor-")) model = model.slice("cursor-".length)
|
|
340
|
+
for (;;) {
|
|
341
|
+
const b = model.replace(POOL_VARIANT, "")
|
|
342
|
+
if (b === model) return model
|
|
343
|
+
model = b
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Cursor's autoBucketModels can lag model releases: it still omitted Grok
|
|
348
|
+
// 4.6/4.7 when the published Cursor Models pool already included them.
|
|
349
|
+
// Keep those documented families alongside the server's exact model list.
|
|
350
|
+
// See https://cursor.com/docs/models-and-pricing.
|
|
351
|
+
function firstParty(model) {
|
|
352
|
+
if (model.startsWith("cursor-")) model = model.slice("cursor-".length)
|
|
353
|
+
if (model === "default" || model === "composer" || model.startsWith("composer-")) return true
|
|
354
|
+
return ["grok-4.5", "grok-4.6", "grok-4.7"].some((b) => model === b || model.startsWith(b + "-"))
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// usage is the account's windows this billing period; an enterprise plan
|
|
358
|
+
// reports spend instead, and gets none. ids are the models it can be
|
|
359
|
+
// asked for, each counted by the pool Cursor bills it to.
|
|
360
|
+
async function usage(tok, ids) {
|
|
361
|
+
const res = await fetch(API + "/aiserver.v1.DashboardService/GetCurrentPeriodUsage", {
|
|
362
|
+
method: "POST",
|
|
363
|
+
headers: { Authorization: `Bearer ${tok}`, "Content-Type": "application/json", "Connect-Protocol-Version": "1" },
|
|
364
|
+
body: "{}",
|
|
365
|
+
signal: AbortSignal.timeout(15_000),
|
|
366
|
+
})
|
|
367
|
+
if (!res.ok) return { error: statusText(res.status), windows: [] }
|
|
368
|
+
const data = await res.json()
|
|
369
|
+
const u = data?.planUsage
|
|
370
|
+
if (!u) return { windows: [] }
|
|
371
|
+
const ms = /^[+-]?\d+$/.test(data.billingCycleEnd ?? "") ? Number(data.billingCycleEnd) : 0
|
|
372
|
+
const resets = ms > 0 ? { resetsAt: new Date(ms).toISOString() } : {}
|
|
373
|
+
const bucket = new Set((data.autoBucketModels ?? []).map(poolBase))
|
|
374
|
+
const inPool = (model) => {
|
|
375
|
+
model = poolBase(model)
|
|
376
|
+
if (model === "auto") model = "default" // the CLI's Auto is default in Cursor's API
|
|
377
|
+
// the server names a family (grok-4.8); the CLI asks for one at an
|
|
378
|
+
// effort or speed (grok-4.8-high-fast)
|
|
379
|
+
return bucket.has(model) || firstParty(model)
|
|
380
|
+
}
|
|
381
|
+
// Auto is always one, so the pool's list is never empty (which would
|
|
382
|
+
// count every model)
|
|
383
|
+
const pool = [...new Set(["auto", ...ids, ...(data.autoBucketModels ?? [])])].filter(inPool)
|
|
384
|
+
const num = (v) => (typeof v === "number" ? v : 0)
|
|
385
|
+
// the two pools fit the line; the total goes in its tooltip
|
|
386
|
+
return {
|
|
387
|
+
windows: [
|
|
388
|
+
{ name: "Cursor Models", used: num(u.autoPercentUsed), ...resets, models: pool },
|
|
389
|
+
{ name: "Other Models", used: num(u.apiPercentUsed), ...resets, notModels: pool },
|
|
390
|
+
{ name: "Total", used: num(u.totalPercentUsed), ...resets, aside: true },
|
|
391
|
+
],
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
321
395
|
// ---- models ----------------------------------------------------------------------
|
|
322
396
|
//
|
|
323
397
|
// Cursor lists a model once for each effort and speed it serves it at
|
|
@@ -1381,6 +1455,17 @@ export async function CursorAuthPlugin() {
|
|
|
1381
1455
|
{ type: "oauth", label: "cursor-agent's sign-in", authorize: cliSignIn },
|
|
1382
1456
|
{ type: "api", label: "Cursor API key (cursor.com/dashboard → Integrations)" },
|
|
1383
1457
|
],
|
|
1458
|
+
// magpie's: how much of the plan's included usage is gone (the plan
|
|
1459
|
+
// is the one the sign-in read)
|
|
1460
|
+
async usage(getAuth, provider) {
|
|
1461
|
+
let tok
|
|
1462
|
+
try {
|
|
1463
|
+
tok = await tokenOf(await getAuth())
|
|
1464
|
+
} catch (e) {
|
|
1465
|
+
return { error: e.message, windows: [] }
|
|
1466
|
+
}
|
|
1467
|
+
return usage(tok, Object.keys(provider?.models ?? {}))
|
|
1468
|
+
},
|
|
1384
1469
|
},
|
|
1385
1470
|
async config(config) {
|
|
1386
1471
|
config.provider ??= {}
|
|
@@ -1414,4 +1499,4 @@ export async function CursorAuthPlugin() {
|
|
|
1414
1499
|
}
|
|
1415
1500
|
|
|
1416
1501
|
// for tests
|
|
1417
|
-
export const _internal = { splitID, families, byEffort, offered, modelID, conversation, buildRun, decode, exec, fields, pb, pbValue, pbAny, frame, frames, failure, toolsOf, expiry }
|
|
1502
|
+
export const _internal = { poolBase, splitID, families, byEffort, offered, modelID, conversation, buildRun, decode, exec, fields, pb, pbValue, pbAny, frame, frames, failure, toolsOf, expiry }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-cursor-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Cursor subscriptions (Pro, Pro+, Ultra, Teams) in OpenCode and magpie, through the API cursor-agent talks to",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|