@magpie-community/opencode-cursor-auth 0.2.10 → 0.2.12

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.
Files changed (3) hide show
  1. package/README.md +7 -3
  2. package/index.mjs +65 -22
  3. 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 count it
80
- with their own, and a step that ends the turn waits for what is still
81
- owed, so the conversation adds up to what Cursor counted. An account
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
package/index.mjs CHANGED
@@ -1408,7 +1408,8 @@ function owe(conv, { since = Date.now(), n = 0, ready = Date.now(), carry = null
1408
1408
 
1409
1409
  // usageEvents are the dashboard's events of conversation conv from since
1410
1410
  // on, the `want` oldest not counted yet, summed: usage (null when none),
1411
- // how many were found (got), and refused when the account can't read them.
1411
+ // each with its time (events), how many were found (got), and refused
1412
+ // when the account can't read them.
1412
1413
  // Polling, they are looked for as STEP_WAIT says, from `first` on, until
1413
1414
  // all show; else asked for once.
1414
1415
  async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_WAIT.first, signal } = {}) {
@@ -1421,6 +1422,7 @@ async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_
1421
1422
  const num = (v) => (Number.isFinite(Number(v)) ? Number(v) : 0)
1422
1423
  let usage = null
1423
1424
  let got = 0
1425
+ const events = []
1424
1426
  if (poll) await wait(first)
1425
1427
  while (!signal?.aborted) {
1426
1428
  try {
@@ -1433,7 +1435,7 @@ async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_
1433
1435
  if (res.status >= 400 && res.status < 500 && res.status !== 429) {
1434
1436
  // not this account's to read
1435
1437
  unreadable.set(tokKey(tok), Date.now())
1436
- return { usage, got, refused: true }
1438
+ return { usage, events, got, refused: true }
1437
1439
  }
1438
1440
  const evs = res.ok ? ((await res.json())?.usageEventsDisplay ?? []) : []
1439
1441
  const found = evs
@@ -1443,7 +1445,9 @@ async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_
1443
1445
  for (const ev of found) {
1444
1446
  claimed.set(conv + "\0" + ev.timestamp, Date.now())
1445
1447
  const t = ev.tokenUsage
1446
- usage = addUsage(usage ?? ZERO(), { input: num(t.inputTokens), output: num(t.outputTokens), cacheRead: num(t.cacheReadTokens), cacheWrite: num(t.cacheWriteTokens) })
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 })
1447
1451
  got++
1448
1452
  }
1449
1453
  if (got >= want) break
@@ -1451,7 +1455,7 @@ async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_
1451
1455
  if (!poll || Date.now() + STEP_WAIT.every > end) break
1452
1456
  await wait(STEP_WAIT.every)
1453
1457
  }
1454
- return { usage, got, refused: false }
1458
+ return { usage, events, got, refused: false }
1455
1459
  }
1456
1460
 
1457
1461
  // stepUsage is the usage of the Run of conversation conv started at since,
@@ -1461,22 +1465,24 @@ async function stepUsage(tok, conv, since, signal) {
1461
1465
  }
1462
1466
 
1463
1467
  // collect takes conversation conv's debt and asks for its events (polling
1464
- // when poll): what was carried and what was found (usage), and whether
1465
- // any is still owed (short), which is owed on. What comes back once the
1466
- // Run that asked is over (over() true) is carried to the next instead.
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.
1467
1472
  function collect(tok, conv, { poll = false, first, signal, over = () => false } = {}) {
1468
1473
  const d = owed.get(conv)
1469
- if (!d) return Promise.resolve({ usage: null, short: false })
1474
+ if (!d) return Promise.resolve({ usage: null, carry: null, events: [], short: false })
1470
1475
  owed.delete(conv)
1471
1476
  const p = (async () => {
1472
- let r = { usage: null, got: 0 }
1477
+ let r = { usage: null, events: [], got: 0 }
1473
1478
  if (d.n > 0 && canRead(tok)) r = await usageEvents(tok, conv, d.since, d.n, { poll, first, signal }).catch(() => r)
1474
1479
  const short = d.n > r.got && canRead(tok)
1475
1480
  if (short) owe(conv, { since: d.since, n: d.n - r.got, ready: d.ready })
1481
+ const carry = d.carry && { ...d.carry }
1476
1482
  const usage = addUsage(d.carry, r.usage)
1477
- if (!over()) return { usage, short }
1483
+ if (!over()) return { usage, carry, events: r.events ?? [], short }
1478
1484
  owe(conv, { carry: usage })
1479
- return { usage: null, short }
1485
+ return { usage: null, carry: null, events: [], short }
1480
1486
  })()
1481
1487
  const set = flights.get(conv) ?? new Set()
1482
1488
  flights.set(conv, set.add(p))
@@ -1498,22 +1504,41 @@ function collect(tok, conv, { poll = false, first, signal, over = () => false }
1498
1504
  // read its events). owed(final) is what the Run collected otherwise,
1499
1505
  // all that can be had when final. done() ends the Run: what was collected
1500
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.
1501
1516
  function ledger(tok, conv, since, signal, kept) {
1502
1517
  const key = tokKey(tok)
1503
- let pending = null // collected, not given out yet
1518
+ let mine = null // this Run's own, collected, not given out yet
1519
+ let theirs = null // earlier Runs', collected, not given out yet
1504
1520
  let over = false
1505
1521
  const gather = (opts) =>
1506
1522
  collect(tok, conv, { ...opts, over: () => over }).then((r) => {
1507
- pending = addUsage(pending, r.usage)
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
+ }
1508
1528
  return r
1509
1529
  })
1510
1530
  let collecting = null
1511
1531
  const debt = kept ? owed.get(conv) : undefined
1512
1532
  const timer = debt ? setTimeout(() => (collecting = gather()), Math.max(0, debt.ready - Date.now())) : null
1513
1533
  timer?.unref?.()
1534
+ const takeMine = () => {
1535
+ const u = mine
1536
+ mine = null
1537
+ return u
1538
+ }
1514
1539
  const take = () => {
1515
- const u = pending
1516
- pending = null
1540
+ const u = addUsage(theirs, mine)
1541
+ mine = theirs = null
1517
1542
  return u
1518
1543
  }
1519
1544
  return {
@@ -1534,7 +1559,7 @@ function ledger(tok, conv, since, signal, kept) {
1534
1559
  if (r.short) misses.set(key, m + 1)
1535
1560
  else misses.delete(key)
1536
1561
  } else misses.set(key, m + 1)
1537
- return take() ?? false
1562
+ return takeMine() ?? false
1538
1563
  },
1539
1564
  async owed(final) {
1540
1565
  if (!kept) return null
@@ -1548,7 +1573,8 @@ function ledger(tok, conv, since, signal, kept) {
1548
1573
  done() {
1549
1574
  over = true
1550
1575
  clearTimeout(timer)
1551
- if (pending) owe(conv, { carry: take() })
1576
+ const u = take()
1577
+ if (u) owe(conv, { carry: u })
1552
1578
  },
1553
1579
  }
1554
1580
  }
@@ -1918,16 +1944,19 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
1918
1944
  // with it or by a later step (ledger), never guessed
1919
1945
  let counted = false
1920
1946
  // finish is the step's end, with what earlier steps owed (more), which
1921
- // 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.
1922
1952
  const finish = (more) => {
1923
1953
  if (!counted && !usage.output) usage.output = Math.floor((said + 3) / 4)
1924
1954
  // a guess only when Cursor counted nothing: a prompt read whole from
1925
1955
  // the cache leaves no uncached rest, and is no reason to guess; nor is
1926
1956
  // a prompt counted from the events
1927
1957
  if (!counted && !usage.input && !usage.cacheRead && !usage.cacheWrite) usage.input = estimate
1928
- // the events' output is their steps', counted here as their prompt is
1929
- if (more) for (const k of ["input", "output", "cacheRead", "cacheWrite"]) usage[k] += more[k]
1930
1958
  const prompt = usage.input + usage.cacheRead + usage.cacheWrite
1959
+ const deferred = more && more.input + more.output + more.cacheRead + more.cacheWrite > 0
1931
1960
  return {
1932
1961
  stop: calls > 0 ? "tool_calls" : "stop",
1933
1962
  usage: {
@@ -1936,6 +1965,16 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
1936
1965
  total_tokens: prompt + usage.output,
1937
1966
  prompt_tokens_details: { cached_tokens: usage.cacheRead, cache_write_tokens: usage.cacheWrite },
1938
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
+ : {}),
1939
1978
  },
1940
1979
  }
1941
1980
  }
@@ -2310,8 +2349,12 @@ export async function CursorAuthPlugin() {
2310
2349
  return { error: e.message, windows: [], signIn: "kept" }
2311
2350
  }
2312
2351
  const [u, plan, bot] = await Promise.all([usage(tok, Object.keys(provider?.models ?? {})), planOf(tok), botUsage(tok)])
2313
- // Put Bot before Total for magpie's three menu-bar rings.
2314
- const windows = [...u.windows.filter((w) => w.name !== "Total"), ...bot, ...u.windows.filter((w) => w.name === "Total")]
2352
+ // Bot goes among magpie's three menu-bar rings: before Total, and
2353
+ // before an On-demand with no limit, which is only a dollar figure on
2354
+ // a ring that never fills (magpie #2213, Morganwan90). An On-demand
2355
+ // with a limit stays ahead of it: that is what can stop the account.
2356
+ const last = (w) => w.name === "Total" || (w.name === "On-demand" && w.aside)
2357
+ const windows = [...u.windows.filter((w) => !last(w)), ...bot, ...u.windows.filter(last)]
2315
2358
  return { ...u, windows, ...(plan ? { plan } : {}), signIn: "kept" }
2316
2359
  },
2317
2360
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-cursor-auth",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
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",