@magpie-community/opencode-cursor-auth 0.2.9 → 0.2.11
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 +11 -4
- package/index.mjs +73 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,9 +76,13 @@ plugin's `fetch` answers them on Cursor's agent API:
|
|
|
76
76
|
usage events, by the Run's conversation id: it shows in about 2.5 s,
|
|
77
77
|
and is looked for up to 6 s. Far from Cursor it shows later than that
|
|
78
78
|
(yetone/magpie#1053), so the step says 0 used and the conversation owes
|
|
79
|
-
it: its next steps collect the event in the background and
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
it: its next steps collect the event in the background, and a step
|
|
80
|
+
that ends the turn waits for what is still owed, so the conversation
|
|
81
|
+
adds up to what Cursor counted. What a step collected of earlier steps
|
|
82
|
+
is not added to its own count, which a client reads as how full its
|
|
83
|
+
context is: it is `usage.magpie_deferred` (`prompt_tokens`,
|
|
84
|
+
`completion_tokens`, `cached_tokens`, `cache_write_tokens`), which
|
|
85
|
+
magpie bills with the step and clients ignore. An account
|
|
82
86
|
whose events miss the wait 3 steps in a row stops waiting for them.
|
|
83
87
|
With nothing naming the session (so no later step to collect it), or
|
|
84
88
|
an account that can't read its usage events, the step is estimated as
|
|
@@ -140,7 +144,10 @@ team or personal spend limit, as `cursor-agent`'s usage view shows it) also
|
|
|
140
144
|
shows On-demand: its spend, and its limit when it has one. Cursor keeps
|
|
141
145
|
serving such an account past the included usage, so magpie doesn't count
|
|
142
146
|
it used up when Cursor Models or Other Models reach 100%; it does when the
|
|
143
|
-
on-demand spend reaches its limit.
|
|
147
|
+
on-demand spend reaches its limit. A team member with no limit of their
|
|
148
|
+
own spends the team's pool: On-demand shows the pool's spend and limit,
|
|
149
|
+
with the member's own spend beside it, and the account is used up when
|
|
150
|
+
the pool is.
|
|
144
151
|
|
|
145
152
|
Sign in to the Cursor account linked in Grok Bot. A linked SuperGrok
|
|
146
153
|
subscription grants Bot access without a paid Cursor plan; see
|
package/index.mjs
CHANGED
|
@@ -385,7 +385,10 @@ function firstParty(model) {
|
|
|
385
385
|
// period's spendLimitUsage (cents) and, where that sets no limit of the
|
|
386
386
|
// user's, GetHardLimit's reply (dollars; undefined when it couldn't be
|
|
387
387
|
// read). kind is "fixed" (up to limitDollars), "unlimited", "disabled" or
|
|
388
|
-
// "unavailable" (not known).
|
|
388
|
+
// "unavailable" (not known). A team member with no limit of their own
|
|
389
|
+
// spends the team's pool, which cursor-agent's view calls unlimited but
|
|
390
|
+
// which runs out at pooledLimit for the whole team (magpie #1868): that is
|
|
391
|
+
// a fixed limit on the pool's spend, with the member's own in mineDollars.
|
|
389
392
|
function onDemandOf(data, hard) {
|
|
390
393
|
const s = data?.spendLimitUsage
|
|
391
394
|
const usedDollars = Number(s?.individualUsed ?? 0) / 100
|
|
@@ -397,8 +400,11 @@ function onDemandOf(data, hard) {
|
|
|
397
400
|
: { kind: "disabled", usedDollars }
|
|
398
401
|
if (s?.limitType === "team") {
|
|
399
402
|
if (own) return own
|
|
400
|
-
if (hard
|
|
401
|
-
|
|
403
|
+
if (hard && (hard.noUsageBasedAllowed || !(Number(hard.hardLimit) > 0))) return { kind: "disabled", usedDollars }
|
|
404
|
+
// pooledLimit and pooledRemaining come as int64 strings, pooledUsed as a number
|
|
405
|
+
const pool = Number(s.pooledLimit ?? 0)
|
|
406
|
+
if (pool > 0) return { kind: "fixed", usedDollars: Number(s.pooledUsed ?? 0) / 100, limitDollars: pool / 100, mineDollars: usedDollars }
|
|
407
|
+
return hard ? { kind: "unlimited", usedDollars } : { kind: "unavailable", usedDollars }
|
|
402
408
|
}
|
|
403
409
|
if (!hard) return own ?? { kind: "unavailable", usedDollars }
|
|
404
410
|
if (hard.noUsageBasedAllowed) return { kind: "disabled", usedDollars }
|
|
@@ -422,7 +428,8 @@ async function hardLimit(tok) {
|
|
|
422
428
|
}
|
|
423
429
|
}
|
|
424
430
|
|
|
425
|
-
|
|
431
|
+
// dollars, thousands grouped: a team's pool runs to six figures ($451,200.00)
|
|
432
|
+
const dollars = (n) => "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
426
433
|
|
|
427
434
|
// usage is the account's windows this billing period; an enterprise plan
|
|
428
435
|
// reports spend instead, and gets none. ids are the models it can be
|
|
@@ -430,7 +437,8 @@ const dollars = (n) => "$" + n.toFixed(2)
|
|
|
430
437
|
// may spend on-demand once its included usage is gone (fottencity on
|
|
431
438
|
// Discord: a team that pays for more) is still served when the pools are
|
|
432
439
|
// spent, so they are shown aside, and its On-demand spend is what can run
|
|
433
|
-
// out: at its limit
|
|
440
|
+
// out: at its limit (a team member with none of their own: the team's
|
|
441
|
+
// pool), or never when it has none.
|
|
434
442
|
async function usage(tok, ids) {
|
|
435
443
|
const [res, hard] = await Promise.all([
|
|
436
444
|
fetch(API + "/aiserver.v1.DashboardService/GetCurrentPeriodUsage", {
|
|
@@ -467,7 +475,7 @@ async function usage(tok, ids) {
|
|
|
467
475
|
const pooled = od.kind === "fixed" || od.kind === "unlimited" ? { aside: true } : {}
|
|
468
476
|
const onDemand =
|
|
469
477
|
od.kind === "fixed"
|
|
470
|
-
? [{ name: "On-demand", used: Math.max(0, Math.min(100, (100 * od.usedDollars) / od.limitDollars)), display: `${dollars(od.usedDollars)} / ${dollars(od.limitDollars)}
|
|
478
|
+
? [{ name: "On-demand", used: Math.max(0, Math.min(100, (100 * od.usedDollars) / od.limitDollars)), display: `${dollars(od.usedDollars)} / ${dollars(od.limitDollars)}` + (od.mineDollars === undefined ? "" : ` (you: ${dollars(od.mineDollars)})`), ...resets }]
|
|
471
479
|
: od.kind === "unlimited"
|
|
472
480
|
? [{ name: "On-demand", used: 0, display: dollars(od.usedDollars), ...resets, aside: true }]
|
|
473
481
|
: []
|
|
@@ -1400,7 +1408,8 @@ function owe(conv, { since = Date.now(), n = 0, ready = Date.now(), carry = null
|
|
|
1400
1408
|
|
|
1401
1409
|
// usageEvents are the dashboard's events of conversation conv from since
|
|
1402
1410
|
// on, the `want` oldest not counted yet, summed: usage (null when none),
|
|
1403
|
-
// how many were found (got), and refused
|
|
1411
|
+
// each with its time (events), how many were found (got), and refused
|
|
1412
|
+
// when the account can't read them.
|
|
1404
1413
|
// Polling, they are looked for as STEP_WAIT says, from `first` on, until
|
|
1405
1414
|
// all show; else asked for once.
|
|
1406
1415
|
async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_WAIT.first, signal } = {}) {
|
|
@@ -1413,6 +1422,7 @@ async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_
|
|
|
1413
1422
|
const num = (v) => (Number.isFinite(Number(v)) ? Number(v) : 0)
|
|
1414
1423
|
let usage = null
|
|
1415
1424
|
let got = 0
|
|
1425
|
+
const events = []
|
|
1416
1426
|
if (poll) await wait(first)
|
|
1417
1427
|
while (!signal?.aborted) {
|
|
1418
1428
|
try {
|
|
@@ -1425,7 +1435,7 @@ async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_
|
|
|
1425
1435
|
if (res.status >= 400 && res.status < 500 && res.status !== 429) {
|
|
1426
1436
|
// not this account's to read
|
|
1427
1437
|
unreadable.set(tokKey(tok), Date.now())
|
|
1428
|
-
return { usage, got, refused: true }
|
|
1438
|
+
return { usage, events, got, refused: true }
|
|
1429
1439
|
}
|
|
1430
1440
|
const evs = res.ok ? ((await res.json())?.usageEventsDisplay ?? []) : []
|
|
1431
1441
|
const found = evs
|
|
@@ -1435,7 +1445,9 @@ async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_
|
|
|
1435
1445
|
for (const ev of found) {
|
|
1436
1446
|
claimed.set(conv + "\0" + ev.timestamp, Date.now())
|
|
1437
1447
|
const t = ev.tokenUsage
|
|
1438
|
-
|
|
1448
|
+
const u = { input: num(t.inputTokens), output: num(t.outputTokens), cacheRead: num(t.cacheReadTokens), cacheWrite: num(t.cacheWriteTokens), reasoning: 0 }
|
|
1449
|
+
usage = addUsage(usage ?? ZERO(), u)
|
|
1450
|
+
events.push({ at: num(ev.timestamp), usage: u })
|
|
1439
1451
|
got++
|
|
1440
1452
|
}
|
|
1441
1453
|
if (got >= want) break
|
|
@@ -1443,7 +1455,7 @@ async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_
|
|
|
1443
1455
|
if (!poll || Date.now() + STEP_WAIT.every > end) break
|
|
1444
1456
|
await wait(STEP_WAIT.every)
|
|
1445
1457
|
}
|
|
1446
|
-
return { usage, got, refused: false }
|
|
1458
|
+
return { usage, events, got, refused: false }
|
|
1447
1459
|
}
|
|
1448
1460
|
|
|
1449
1461
|
// stepUsage is the usage of the Run of conversation conv started at since,
|
|
@@ -1453,22 +1465,24 @@ async function stepUsage(tok, conv, since, signal) {
|
|
|
1453
1465
|
}
|
|
1454
1466
|
|
|
1455
1467
|
// collect takes conversation conv's debt and asks for its events (polling
|
|
1456
|
-
// when poll): what was carried and what was found (usage),
|
|
1457
|
-
//
|
|
1458
|
-
//
|
|
1468
|
+
// when poll): what was carried and what was found (usage), what was
|
|
1469
|
+
// carried alone (carry) and the events found (events), and whether any is
|
|
1470
|
+
// still owed (short), which is owed on. What comes back once the Run that
|
|
1471
|
+
// asked is over (over() true) is carried to the next instead.
|
|
1459
1472
|
function collect(tok, conv, { poll = false, first, signal, over = () => false } = {}) {
|
|
1460
1473
|
const d = owed.get(conv)
|
|
1461
|
-
if (!d) return Promise.resolve({ usage: null, short: false })
|
|
1474
|
+
if (!d) return Promise.resolve({ usage: null, carry: null, events: [], short: false })
|
|
1462
1475
|
owed.delete(conv)
|
|
1463
1476
|
const p = (async () => {
|
|
1464
|
-
let r = { usage: null, got: 0 }
|
|
1477
|
+
let r = { usage: null, events: [], got: 0 }
|
|
1465
1478
|
if (d.n > 0 && canRead(tok)) r = await usageEvents(tok, conv, d.since, d.n, { poll, first, signal }).catch(() => r)
|
|
1466
1479
|
const short = d.n > r.got && canRead(tok)
|
|
1467
1480
|
if (short) owe(conv, { since: d.since, n: d.n - r.got, ready: d.ready })
|
|
1481
|
+
const carry = d.carry && { ...d.carry }
|
|
1468
1482
|
const usage = addUsage(d.carry, r.usage)
|
|
1469
|
-
if (!over()) return { usage, short }
|
|
1483
|
+
if (!over()) return { usage, carry, events: r.events ?? [], short }
|
|
1470
1484
|
owe(conv, { carry: usage })
|
|
1471
|
-
return { usage: null, short }
|
|
1485
|
+
return { usage: null, carry: null, events: [], short }
|
|
1472
1486
|
})()
|
|
1473
1487
|
const set = flights.get(conv) ?? new Set()
|
|
1474
1488
|
flights.set(conv, set.add(p))
|
|
@@ -1490,22 +1504,41 @@ function collect(tok, conv, { poll = false, first, signal, over = () => false }
|
|
|
1490
1504
|
// read its events). owed(final) is what the Run collected otherwise,
|
|
1491
1505
|
// all that can be had when final. done() ends the Run: what was collected
|
|
1492
1506
|
// and not given out goes back.
|
|
1507
|
+
//
|
|
1508
|
+
// What a Run collects is told apart by its event's time: Cursor stamps a
|
|
1509
|
+
// Run's event as the Run begins (1.4–3.4 s after since), so one from since
|
|
1510
|
+
// on (less the second usageEvents allows) is this Run's own (mine), and one
|
|
1511
|
+
// before it, or a count carried, an earlier Run's (theirs). own() gives out only mine, as the step's usage,
|
|
1512
|
+
// which a client reads as how full its context is; theirs goes out by
|
|
1513
|
+
// owed(), apart. A Run of its conversation begins only once the one
|
|
1514
|
+
// before has ended, so the two never meet; were the time off, the step's
|
|
1515
|
+
// count would be told in the wrong field, never twice nor lost.
|
|
1493
1516
|
function ledger(tok, conv, since, signal, kept) {
|
|
1494
1517
|
const key = tokKey(tok)
|
|
1495
|
-
let
|
|
1518
|
+
let mine = null // this Run's own, collected, not given out yet
|
|
1519
|
+
let theirs = null // earlier Runs', collected, not given out yet
|
|
1496
1520
|
let over = false
|
|
1497
1521
|
const gather = (opts) =>
|
|
1498
1522
|
collect(tok, conv, { ...opts, over: () => over }).then((r) => {
|
|
1499
|
-
|
|
1523
|
+
theirs = addUsage(theirs, r.carry)
|
|
1524
|
+
for (const e of r.events ?? []) {
|
|
1525
|
+
if (e.at >= since - 1000) mine = addUsage(mine, e.usage)
|
|
1526
|
+
else theirs = addUsage(theirs, e.usage)
|
|
1527
|
+
}
|
|
1500
1528
|
return r
|
|
1501
1529
|
})
|
|
1502
1530
|
let collecting = null
|
|
1503
1531
|
const debt = kept ? owed.get(conv) : undefined
|
|
1504
1532
|
const timer = debt ? setTimeout(() => (collecting = gather()), Math.max(0, debt.ready - Date.now())) : null
|
|
1505
1533
|
timer?.unref?.()
|
|
1534
|
+
const takeMine = () => {
|
|
1535
|
+
const u = mine
|
|
1536
|
+
mine = null
|
|
1537
|
+
return u
|
|
1538
|
+
}
|
|
1506
1539
|
const take = () => {
|
|
1507
|
-
const u =
|
|
1508
|
-
|
|
1540
|
+
const u = addUsage(theirs, mine)
|
|
1541
|
+
mine = theirs = null
|
|
1509
1542
|
return u
|
|
1510
1543
|
}
|
|
1511
1544
|
return {
|
|
@@ -1526,7 +1559,7 @@ function ledger(tok, conv, since, signal, kept) {
|
|
|
1526
1559
|
if (r.short) misses.set(key, m + 1)
|
|
1527
1560
|
else misses.delete(key)
|
|
1528
1561
|
} else misses.set(key, m + 1)
|
|
1529
|
-
return
|
|
1562
|
+
return takeMine() ?? false
|
|
1530
1563
|
},
|
|
1531
1564
|
async owed(final) {
|
|
1532
1565
|
if (!kept) return null
|
|
@@ -1540,7 +1573,8 @@ function ledger(tok, conv, since, signal, kept) {
|
|
|
1540
1573
|
done() {
|
|
1541
1574
|
over = true
|
|
1542
1575
|
clearTimeout(timer)
|
|
1543
|
-
|
|
1576
|
+
const u = take()
|
|
1577
|
+
if (u) owe(conv, { carry: u })
|
|
1544
1578
|
},
|
|
1545
1579
|
}
|
|
1546
1580
|
}
|
|
@@ -1910,16 +1944,19 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
|
|
|
1910
1944
|
// with it or by a later step (ledger), never guessed
|
|
1911
1945
|
let counted = false
|
|
1912
1946
|
// finish is the step's end, with what earlier steps owed (more), which
|
|
1913
|
-
// is no reason not to guess the step's own
|
|
1947
|
+
// is no reason not to guess the step's own. The step's usage is its own
|
|
1948
|
+
// Run's alone: a client reads its prompt_tokens as how full the context
|
|
1949
|
+
// is (pi, OpenCode), and an earlier step's prompt added to it read twice
|
|
1950
|
+
// the context. What earlier steps owed goes apart, as magpie_deferred,
|
|
1951
|
+
// which magpie counts with the step and no client reads.
|
|
1914
1952
|
const finish = (more) => {
|
|
1915
1953
|
if (!counted && !usage.output) usage.output = Math.floor((said + 3) / 4)
|
|
1916
1954
|
// a guess only when Cursor counted nothing: a prompt read whole from
|
|
1917
1955
|
// the cache leaves no uncached rest, and is no reason to guess; nor is
|
|
1918
1956
|
// a prompt counted from the events
|
|
1919
1957
|
if (!counted && !usage.input && !usage.cacheRead && !usage.cacheWrite) usage.input = estimate
|
|
1920
|
-
// the events' output is their steps', counted here as their prompt is
|
|
1921
|
-
if (more) for (const k of ["input", "output", "cacheRead", "cacheWrite"]) usage[k] += more[k]
|
|
1922
1958
|
const prompt = usage.input + usage.cacheRead + usage.cacheWrite
|
|
1959
|
+
const deferred = more && more.input + more.output + more.cacheRead + more.cacheWrite > 0
|
|
1923
1960
|
return {
|
|
1924
1961
|
stop: calls > 0 ? "tool_calls" : "stop",
|
|
1925
1962
|
usage: {
|
|
@@ -1928,6 +1965,16 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
|
|
|
1928
1965
|
total_tokens: prompt + usage.output,
|
|
1929
1966
|
prompt_tokens_details: { cached_tokens: usage.cacheRead, cache_write_tokens: usage.cacheWrite },
|
|
1930
1967
|
...(usage.reasoning ? { completion_tokens_details: { reasoning_tokens: usage.reasoning } } : {}),
|
|
1968
|
+
...(deferred
|
|
1969
|
+
? {
|
|
1970
|
+
magpie_deferred: {
|
|
1971
|
+
prompt_tokens: more.input + more.cacheRead + more.cacheWrite,
|
|
1972
|
+
completion_tokens: more.output,
|
|
1973
|
+
cached_tokens: more.cacheRead,
|
|
1974
|
+
cache_write_tokens: more.cacheWrite,
|
|
1975
|
+
},
|
|
1976
|
+
}
|
|
1977
|
+
: {}),
|
|
1931
1978
|
},
|
|
1932
1979
|
}
|
|
1933
1980
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-cursor-auth",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
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",
|