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

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 CHANGED
@@ -21,13 +21,15 @@
21
21
  // a conversation, the caller's system prompt at its head; the Run only
22
22
  // resumes it. The caller's tools are MCP tools, which the model calls
23
23
  // through Cursor's CallDynamicTool: the server hands each call to the
24
- // client to run, and once it has said how many a step made, the stream is
25
- // closed and the calls go back to OpenCode as a chat completion's tool
26
- // calls. Nothing else the server asks the client to run (a shell, a file
27
- // read) is run.
24
+ // client to run, and once it has said how many a step made, the calls
25
+ // go back to OpenCode. When the owner is available their results continue
26
+ // that Run; otherwise the caller's history starts a stateless Run. Nothing
27
+ // else the server asks the client to run (a shell, a file read) is run.
28
28
  //
29
29
  // OpenCode speaks chat completions to it (@ai-sdk/openai-compatible); the
30
30
  // fetch here answers them.
31
+ import { NATIVE_LIMITS, nativeSessions, NativeSessionError, nativeFields, nativeMcpArgs } from "./native-session.mjs"
32
+ import { isDeepStrictEqual } from "node:util"
31
33
  import http2 from "node:http2"
32
34
  import { STATUS_CODES } from "node:http"
33
35
  import { gunzipSync } from "node:zlib"
@@ -1358,17 +1360,18 @@ function usageOf(uf) {
1358
1360
  // event: its step looks for it as before, and is guessed without it.
1359
1361
  const STEP_WAIT = { first: 1500, every: 500, until: 6000, ask: 5000, skipAfter: 3, retryEvery: 10, owedAt: 5000, owedFor: 600_000 }
1360
1362
 
1361
- // claimed are the usage events already counted, by conversation and time,
1362
- // so that two steps of one conversation never count the same one.
1363
+ // claimed are the usage events already counted, by account, conversation
1364
+ // and time, so that two steps of one conversation never count the same one.
1363
1365
  const claimed = new Map()
1364
1366
 
1365
- // owed are what each conversation still owes, by conversation_id: n Runs
1366
- // whose events are to be counted, the earliest started at since, looked
1367
- // for from ready on; and carry, a count collected that no step gave out.
1367
+ // owed are what each account's conversation still owes, by credential
1368
+ // fingerprint and conversation_id: n Runs whose events are to be counted,
1369
+ // the earliest started at since, looked for from ready on; and carry, a
1370
+ // count collected that no step gave out.
1368
1371
  const owed = new Map()
1369
1372
 
1370
- // flights are each conversation's collections still going, which a step
1371
- // that ends the turn waits for.
1373
+ // flights are each account's conversation collections still going, which
1374
+ // a step that ends the turn waits for.
1372
1375
  const flights = new Map()
1373
1376
 
1374
1377
  // unreadable are the accounts (by token) whose usage events Cursor won't
@@ -1378,6 +1381,7 @@ const flights = new Map()
1378
1381
  const unreadable = new Map()
1379
1382
  const misses = new Map()
1380
1383
  const tokKey = (tok) => createHash("sha256").update(tok).digest("hex")
1384
+ const usageKey = (tok, conv) => tokKey(tok) + "\0" + conv
1381
1385
  function canRead(tok) {
1382
1386
  const at = unreadable.get(tokKey(tok))
1383
1387
  if (at === undefined) return true
@@ -1394,14 +1398,43 @@ function addUsage(a, b) {
1394
1398
  return a
1395
1399
  }
1396
1400
 
1397
- // owe adds to conversation conv's debt.
1398
- function owe(conv, { since = Date.now(), n = 0, ready = Date.now(), carry = null }) {
1399
- for (const [k, d] of owed) if (Date.now() - d.since > STEP_WAIT.owedFor) owed.delete(k)
1401
+ // owe adds to the account/conversation's debt. A native Run bounds older
1402
+ // event timestamps before its start: its authoritative final must never be
1403
+ // collected as an earlier Run. Keep that bound when a later stateless Run
1404
+ // adds unbounded debt. Ordinary stateless debt retains its original shape.
1405
+ const DEBT_GROUP_LIMIT = 64
1406
+ function owe(key, { since = Date.now(), n = 0, ready = Date.now(), carry = null, before }) {
1407
+ for (const [k, d] of owed) {
1408
+ if (d.groups) {
1409
+ d.groups = d.groups.filter((g) => Date.now() - g.since <= STEP_WAIT.owedFor)
1410
+ d.n = d.groups.reduce((sum, g) => sum + g.n, 0)
1411
+ if (Date.now() - d.since > STEP_WAIT.owedFor) d.carry = null
1412
+ if (!d.n && !d.carry) owed.delete(k)
1413
+ else if (d.groups.length) d.since = Math.min(...d.groups.map((g) => g.since))
1414
+ } else if (Date.now() - d.since > STEP_WAIT.owedFor) owed.delete(k)
1415
+ }
1400
1416
  if (n <= 0 && !carry) return
1401
- const d = owed.get(conv)
1402
- if (!d) return void owed.set(conv, { since, n, ready, carry: carry && { ...carry } })
1403
- d.since = Math.min(d.since, since)
1404
- d.n += n
1417
+ const d = owed.get(key)
1418
+ if (!d) return void owed.set(key, { since, n, ready, carry: carry && { ...carry }, ...(before !== undefined ? { before } : {}) })
1419
+ if (n > 0 && (d.groups || d.before !== undefined || before !== undefined)) {
1420
+ const groups = d.groups ?? (d.n > 0 ? [{ since: d.since, n: d.n, ready: d.ready, before: d.before }] : [])
1421
+ const same = groups.find((g) => g.before === before)
1422
+ if (same) {
1423
+ same.since = Math.min(same.since, since)
1424
+ same.n += n
1425
+ same.ready = Math.min(same.ready, ready)
1426
+ } else groups.push({ since, n, ready, before })
1427
+ // As with expired dashboard debt, overflow stays unknown, never estimated
1428
+ // or merged across bounds into an authoritative native Run's event.
1429
+ groups.sort((a, b) => a.since - b.since)
1430
+ d.groups = groups.slice(-DEBT_GROUP_LIMIT)
1431
+ delete d.before
1432
+ d.n = d.groups.reduce((sum, g) => sum + g.n, 0)
1433
+ d.since = Math.min(...d.groups.map((g) => g.since))
1434
+ } else {
1435
+ d.since = Math.min(d.since, since)
1436
+ d.n += n
1437
+ }
1405
1438
  d.ready = Math.min(d.ready, ready)
1406
1439
  d.carry = addUsage(d.carry, carry)
1407
1440
  }
@@ -1412,7 +1445,8 @@ function owe(conv, { since = Date.now(), n = 0, ready = Date.now(), carry = null
1412
1445
  // when the account can't read them.
1413
1446
  // Polling, they are looked for as STEP_WAIT says, from `first` on, until
1414
1447
  // all show; else asked for once.
1415
- async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_WAIT.first, signal } = {}) {
1448
+ async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_WAIT.first, signal, before = () => Infinity, groups, abortFetch = false } = {}) {
1449
+ const key = usageKey(tok, conv)
1416
1450
  const end = Date.now() + STEP_WAIT.until
1417
1451
  const wait = (ms) => new Promise((r) => {
1418
1452
  const t = setTimeout(r, ms)
@@ -1423,6 +1457,7 @@ async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_
1423
1457
  let usage = null
1424
1458
  let got = 0
1425
1459
  const events = []
1460
+ const counts = groups?.map(() => 0)
1426
1461
  if (poll) await wait(first)
1427
1462
  while (!signal?.aborted) {
1428
1463
  try {
@@ -1430,20 +1465,27 @@ async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_
1430
1465
  method: "POST",
1431
1466
  headers: await headersFor(tok),
1432
1467
  body: JSON.stringify({ teamId: 0, startDate: String(since - 60_000), endDate: String(Date.now() + 60_000), page: 1, pageSize: 50 }),
1433
- signal: AbortSignal.timeout(STEP_WAIT.ask),
1468
+ signal: abortFetch && signal ? AbortSignal.any([signal, AbortSignal.timeout(STEP_WAIT.ask)]) : AbortSignal.timeout(STEP_WAIT.ask),
1434
1469
  })
1435
1470
  if (res.status >= 400 && res.status < 500 && res.status !== 429) {
1436
1471
  // not this account's to read
1437
1472
  unreadable.set(tokKey(tok), Date.now())
1438
- return { usage, events, got, refused: true }
1473
+ return { usage, events, got, counts, refused: true }
1439
1474
  }
1440
1475
  const evs = res.ok ? ((await res.json())?.usageEventsDisplay ?? []) : []
1441
1476
  const found = evs
1442
- .filter((e) => e?.conversationId === conv && e.tokenUsage && num(e.timestamp) >= since - 1000 && !claimed.has(conv + "\0" + e.timestamp))
1477
+ .filter((e) => e?.conversationId === conv && e.tokenUsage && num(e.timestamp) >= since - 1000 && !claimed.has(key + "\0" + e.timestamp))
1443
1478
  .sort((a, b) => num(a.timestamp) - num(b.timestamp))
1444
- .slice(0, want - got)
1445
1479
  for (const ev of found) {
1446
- claimed.set(conv + "\0" + ev.timestamp, Date.now())
1480
+ if (got >= want) break
1481
+ const at = num(ev.timestamp)
1482
+ // found already excludes timestamps claimed by earlier fetches. Two
1483
+ // distinct events in this response may share a millisecond; count both.
1484
+ if (at >= before()) continue
1485
+ const group = groups?.findIndex((g, i) => counts[i] < g.n && at >= g.since - 1000 && at < (g.before ?? Infinity))
1486
+ if (groups && group === -1) continue
1487
+ if (groups) counts[group]++
1488
+ claimed.set(key + "\0" + ev.timestamp, Date.now())
1447
1489
  const t = ev.tokenUsage
1448
1490
  const u = { input: num(t.inputTokens), output: num(t.outputTokens), cacheRead: num(t.cacheReadTokens), cacheWrite: num(t.cacheWriteTokens), reasoning: 0 }
1449
1491
  usage = addUsage(usage ?? ZERO(), u)
@@ -1455,7 +1497,7 @@ async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_
1455
1497
  if (!poll || Date.now() + STEP_WAIT.every > end) break
1456
1498
  await wait(STEP_WAIT.every)
1457
1499
  }
1458
- return { usage, events, got, refused: false }
1500
+ return { usage, events, got, counts, refused: false }
1459
1501
  }
1460
1502
 
1461
1503
  // stepUsage is the usage of the Run of conversation conv started at since,
@@ -1464,31 +1506,45 @@ async function stepUsage(tok, conv, since, signal) {
1464
1506
  return (await usageEvents(tok, conv, since, 1, { poll: true, signal })).usage
1465
1507
  }
1466
1508
 
1467
- // collect takes conversation conv's debt and asks for its events (polling
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
1509
+ // collect takes the account's debt for conversation conv and asks for its
1510
+ // events (polling when poll): what was carried and what was found (usage),
1511
+ // what was carried alone (carry) and the events found (events), and whether any is
1470
1512
  // still owed (short), which is owed on. What comes back once the Run that
1471
1513
  // asked is over (over() true) is carried to the next instead.
1472
- function collect(tok, conv, { poll = false, first, signal, over = () => false } = {}) {
1473
- const d = owed.get(conv)
1514
+ function collect(tok, conv, { poll = false, first, signal, over = () => false, abortFetch = false } = {}) {
1515
+ const key = usageKey(tok, conv)
1516
+ const d = owed.get(key)
1474
1517
  if (!d) return Promise.resolve({ usage: null, carry: null, events: [], short: false })
1475
- owed.delete(conv)
1518
+ owed.delete(key)
1476
1519
  const p = (async () => {
1520
+ const groups = (d.groups ?? [d]).filter((g) => (g.before === undefined && !d.groups) || Date.now() - g.since <= STEP_WAIT.owedFor)
1521
+ const want = groups.reduce((sum, g) => sum + g.n, 0)
1477
1522
  let r = { usage: null, events: [], got: 0 }
1478
- if (d.n > 0 && canRead(tok)) r = await usageEvents(tok, conv, d.since, d.n, { poll, first, signal }).catch(() => r)
1479
- const short = d.n > r.got && canRead(tok)
1480
- if (short) owe(conv, { since: d.since, n: d.n - r.got, ready: d.ready })
1523
+ if (want > 0 && canRead(tok)) r = await usageEvents(tok, conv, Math.min(...groups.map((g) => g.since)), want, {
1524
+ poll, first, signal, abortFetch, before: () => d.before ?? Infinity, ...(d.groups ? { groups } : {}),
1525
+ }).catch(() => r)
1526
+ let short = false
1527
+ for (const [i, g] of groups.entries()) {
1528
+ const remaining = g.n - (d.groups ? r.counts?.[i] ?? 0 : r.got)
1529
+ if (remaining > 0 && canRead(tok)) {
1530
+ short = true
1531
+ owe(key, { since: g.since, n: remaining, ready: g.ready, before: g.before })
1532
+ }
1533
+ }
1481
1534
  const carry = d.carry && { ...d.carry }
1482
1535
  const usage = addUsage(d.carry, r.usage)
1483
1536
  if (!over()) return { usage, carry, events: r.events ?? [], short }
1484
- owe(conv, { carry: usage })
1537
+ owe(key, { carry: usage })
1485
1538
  return { usage: null, carry: null, events: [], short }
1486
1539
  })()
1487
- const set = flights.get(conv) ?? new Set()
1488
- flights.set(conv, set.add(p))
1540
+ // Native admission may happen while an older collector is still in flight.
1541
+ // Its dynamic event bound must apply before that response is claimed too.
1542
+ p.debt = d
1543
+ const set = flights.get(key) ?? new Set()
1544
+ flights.set(key, set.add(p))
1489
1545
  p.finally(() => {
1490
1546
  set.delete(p)
1491
- if (!set.size && flights.get(conv) === set) flights.delete(conv)
1547
+ if (!set.size && flights.get(key) === set) flights.delete(key)
1492
1548
  })
1493
1549
  return p
1494
1550
  }
@@ -1513,13 +1569,18 @@ function collect(tok, conv, { poll = false, first, signal, over = () => false }
1513
1569
  // owed(), apart. A Run of its conversation begins only once the one
1514
1570
  // before has ended, so the two never meet; were the time off, the step's
1515
1571
  // count would be told in the wrong field, never twice nor lost.
1516
- function ledger(tok, conv, since, signal, kept) {
1572
+ function ledger(tok, conv, since, signal, kept, { before } = {}) {
1517
1573
  const key = tokKey(tok)
1574
+ const debtKey = usageKey(tok, conv)
1575
+ if (kept && before !== undefined) {
1576
+ const debts = [owed.get(debtKey), ...[...(flights.get(debtKey) ?? [])].map((p) => p.debt)]
1577
+ for (const d of debts) for (const g of d?.groups ?? (d ? [d] : [])) g.before = Math.min(g.before ?? Infinity, before)
1578
+ }
1518
1579
  let mine = null // this Run's own, collected, not given out yet
1519
1580
  let theirs = null // earlier Runs', collected, not given out yet
1520
1581
  let over = false
1521
1582
  const gather = (opts) =>
1522
- collect(tok, conv, { ...opts, over: () => over }).then((r) => {
1583
+ collect(tok, conv, { ...opts, over: () => over, abortFetch: before !== undefined }).then((r) => {
1523
1584
  theirs = addUsage(theirs, r.carry)
1524
1585
  for (const e of r.events ?? []) {
1525
1586
  if (e.at >= since - 1000) mine = addUsage(mine, e.usage)
@@ -1528,7 +1589,7 @@ function ledger(tok, conv, since, signal, kept) {
1528
1589
  return r
1529
1590
  })
1530
1591
  let collecting = null
1531
- const debt = kept ? owed.get(conv) : undefined
1592
+ const debt = kept ? owed.get(debtKey) : undefined
1532
1593
  const timer = debt ? setTimeout(() => (collecting = gather()), Math.max(0, debt.ready - Date.now())) : null
1533
1594
  timer?.unref?.()
1534
1595
  const takeMine = () => {
@@ -1552,7 +1613,7 @@ function ledger(tok, conv, since, signal, kept) {
1552
1613
  else if (!r.refused) misses.set(key, m + 1)
1553
1614
  return r.usage ?? null
1554
1615
  }
1555
- owe(conv, { since, n: 1, ready: Date.now() + STEP_WAIT.owedAt })
1616
+ owe(debtKey, { since, n: 1, ready: Date.now() + STEP_WAIT.owedAt })
1556
1617
  if (waits) {
1557
1618
  const r = await gather({ poll: true, signal })
1558
1619
  if (!canRead(tok)) return null
@@ -1565,8 +1626,8 @@ function ledger(tok, conv, since, signal, kept) {
1565
1626
  if (!kept) return null
1566
1627
  if (final) {
1567
1628
  await collecting
1568
- await Promise.all([...(flights.get(conv) ?? [])])
1569
- if (owed.has(conv)) await gather({ poll: owed.get(conv).n > 0, first: 0, signal })
1629
+ await Promise.all([...(flights.get(debtKey) ?? [])])
1630
+ if (owed.has(debtKey)) await gather({ poll: owed.get(debtKey).n > 0, first: 0, signal })
1570
1631
  }
1571
1632
  return take()
1572
1633
  },
@@ -1574,7 +1635,7 @@ function ledger(tok, conv, since, signal, kept) {
1574
1635
  over = true
1575
1636
  clearTimeout(timer)
1576
1637
  const u = take()
1577
- if (u) owe(conv, { carry: u })
1638
+ if (u) owe(debtKey, { carry: u })
1578
1639
  },
1579
1640
  }
1580
1641
  }
@@ -1666,12 +1727,21 @@ async function agentURL(tok, fresh) {
1666
1727
  // connection that failed (refused, no network) or a stream the server
1667
1728
  // refused leaves the next Run to try HTTP/2 again.
1668
1729
  let h1Until = 0
1730
+ const cancelledOpen = () => ({ error: { status: 410, message: "the request was cancelled" }, h2Failed: false })
1669
1731
  async function open(base, headers, signal) {
1732
+ if (signal?.aborted) return cancelledOpen()
1670
1733
  if (Date.now() >= h1Until) {
1671
1734
  const o = await openH2(base, headers, signal)
1735
+ // The head can arrive just before abort, before this continuation owns it.
1736
+ if (signal?.aborted) {
1737
+ o.req?.close(http2.constants.NGHTTP2_CANCEL)
1738
+ o.session?.destroy()
1739
+ return cancelledOpen()
1740
+ }
1672
1741
  if (!o.h2Failed) return o
1673
1742
  if (o.blocked) h1Until = Date.now() + H2.retryAfter
1674
1743
  }
1744
+ if (signal?.aborted) return cancelledOpen()
1675
1745
  return openH1(headers, signal)
1676
1746
  }
1677
1747
 
@@ -1702,6 +1772,7 @@ function h2Blocked(e) {
1702
1772
  // than by being cancelled, says h2Failed, and blocked when h2Blocked says
1703
1773
  // so of the failure.
1704
1774
  function openH2(base, headers, signal) {
1775
+ if (signal?.aborted) return Promise.resolve(cancelledOpen())
1705
1776
  return new Promise((resolve) => {
1706
1777
  const u = new URL(base)
1707
1778
  const session = http2.connect(u.origin)
@@ -1710,9 +1781,11 @@ function openH2(base, headers, signal) {
1710
1781
  if (done) return
1711
1782
  done = true
1712
1783
  clearTimeout(timer)
1784
+ signal?.removeEventListener("abort", abort)
1713
1785
  session.destroy()
1714
- resolve({ error: { status: 502, message: e?.message ?? String(e) }, h2Failed, blocked: h2Failed && h2Blocked(e) })
1786
+ resolve({ error: { status: h2Failed ? 502 : 410, message: e?.message ?? String(e) }, h2Failed, blocked: h2Failed && h2Blocked(e) })
1715
1787
  }
1788
+ const abort = () => fail(new Error("the request was cancelled"), false)
1716
1789
  const timer = setTimeout(() => fail(Object.assign(new Error(`no answer over HTTP/2 in ${H2.wait / 1000}s`), { h2Timeout: true })), H2.wait)
1717
1790
  session.on("error", fail)
1718
1791
  const req = session.request({
@@ -1728,9 +1801,11 @@ function openH2(base, headers, signal) {
1728
1801
  if (done) return
1729
1802
  done = true
1730
1803
  clearTimeout(timer)
1804
+ signal?.removeEventListener("abort", abort)
1731
1805
  resolve({ session, req, status: Number(h[":status"]) })
1732
1806
  })
1733
- signal?.addEventListener("abort", () => fail(new Error("the request was cancelled"), false), { once: true })
1807
+ signal?.addEventListener("abort", abort, { once: true })
1808
+ if (signal?.aborted) abort()
1734
1809
  })
1735
1810
  }
1736
1811
 
@@ -1750,23 +1825,29 @@ function connectCode(status) {
1750
1825
  // ends the stream with Connect's end frame, which runOnce reads as the
1751
1826
  // Run's own.
1752
1827
  function openH1(headers, signal) {
1828
+ if (signal?.aborted) return Promise.resolve(cancelledOpen())
1753
1829
  const ac = new AbortController()
1754
- signal?.addEventListener("abort", () => ac.abort(), { once: true })
1755
1830
  const id = headers["x-request-id"]
1756
1831
  const h = { ...headers, "accept-encoding": "identity", "x-cursor-streaming": "true" }
1757
1832
  let ended = false
1758
1833
  let seq = 0
1759
- let chain = Promise.resolve()
1834
+ let reader
1760
1835
  const req = new Duplex({
1761
1836
  read() {},
1762
1837
  write(chunk, _enc, cb) {
1763
1838
  // runOnce writes one whole frame each time
1764
1839
  const msg = Buffer.from(chunk).subarray(5)
1765
1840
  const n = seq++
1766
- chain = chain.then(() => (ended ? undefined : append(msg, n)))
1767
- cb()
1841
+ // Keep the write buffered until its append finishes, so callbacks and
1842
+ // drain reflect the pending upload rather than an unbounded promise chain.
1843
+ append(msg, n).then(() => cb(), (e) => cb(e))
1768
1844
  },
1769
1845
  })
1846
+ const cleanup = () => {
1847
+ signal?.removeEventListener("abort", close)
1848
+ ac.abort()
1849
+ reader?.cancel().catch(() => {})
1850
+ }
1770
1851
  const end = (body) => {
1771
1852
  if (ended || req.destroyed) return
1772
1853
  ended = true
@@ -1774,7 +1855,7 @@ function openH1(headers, signal) {
1774
1855
  f[0] = 2
1775
1856
  req.push(f)
1776
1857
  req.push(null)
1777
- ac.abort()
1858
+ cleanup()
1778
1859
  }
1779
1860
  const failed = async (res) => {
1780
1861
  const text = await res.text().catch(() => "")
@@ -1788,6 +1869,7 @@ function openH1(headers, signal) {
1788
1869
  // (more time for a large one) ends the Run, as cursor-agent's
1789
1870
  // bidi_append_deadline_exceeded does: the messages after it wait on it.
1790
1871
  const append = async (msg, n) => {
1872
+ if (ended || ac.signal.aborted) return
1791
1873
  const body = pb().str(1, msg.toString("hex")).bytes(2, pb().str(1, id).done()).varint(3, n).done()
1792
1874
  const ms = H1.appendWait + Math.ceil(body.length / 65536) * 1000
1793
1875
  const deadline = AbortSignal.timeout(ms)
@@ -1798,6 +1880,7 @@ function openH1(headers, signal) {
1798
1880
  body,
1799
1881
  signal: AbortSignal.any([ac.signal, deadline]),
1800
1882
  })
1883
+ if (ended || ac.signal.aborted) { await res.body?.cancel().catch(() => {}); return }
1801
1884
  if (!res.ok) return failed(res)
1802
1885
  await res.arrayBuffer()
1803
1886
  } catch (e) {
@@ -1806,6 +1889,16 @@ function openH1(headers, signal) {
1806
1889
  end(JSON.stringify({ error: { code: "unavailable", message: `BidiAppend: ${e?.message ?? e}` } }))
1807
1890
  }
1808
1891
  }
1892
+ const close = () => {
1893
+ ended = true
1894
+ cleanup()
1895
+ req.destroy()
1896
+ }
1897
+ signal?.addEventListener("abort", close, { once: true })
1898
+ if (signal?.aborted) {
1899
+ close()
1900
+ return Promise.resolve(cancelledOpen())
1901
+ }
1809
1902
  ;(async () => {
1810
1903
  try {
1811
1904
  const res = await fetch(API + "/agent.v1.AgentService/RunSSE", {
@@ -1814,25 +1907,229 @@ function openH1(headers, signal) {
1814
1907
  body: frame(pb().str(1, id).done()),
1815
1908
  signal: ac.signal,
1816
1909
  })
1910
+ if (ended || ac.signal.aborted) { await res.body?.cancel().catch(() => {}); return }
1817
1911
  if (!res.ok) return failed(res)
1818
- for await (const c of res.body) {
1819
- if (ended || req.destroyed) return
1820
- req.push(Buffer.from(c))
1912
+ reader = res.body.getReader()
1913
+ try {
1914
+ while (!ended && !req.destroyed) {
1915
+ const { value, done } = await reader.read()
1916
+ if (done) break
1917
+ if (!ended && !req.destroyed) req.push(Buffer.from(value))
1918
+ }
1919
+ } finally {
1920
+ reader.releaseLock()
1921
+ reader = undefined
1821
1922
  }
1822
1923
  if (!ended && !req.destroyed) {
1823
1924
  ended = true
1824
1925
  req.push(null)
1926
+ cleanup()
1825
1927
  }
1826
1928
  } catch (e) {
1827
1929
  if (!ac.signal.aborted) end(JSON.stringify({ error: { code: "unavailable", message: `RunSSE: ${e?.message ?? e}` } }))
1828
1930
  }
1829
1931
  })()
1932
+ return Promise.resolve(signal?.aborted ? cancelledOpen() : { session: { close, destroy: close }, req: Object.assign(req, { close }), status: 200, h1: true })
1933
+ }
1934
+
1935
+ // Every outbound frame, including KV replies and heartbeats, uses the same
1936
+ // bounded FIFO. Admission is synchronous; a result is reserved only once even
1937
+ // when a later write fails. Count bytes until both callback and drain complete.
1938
+ function nativeTransport(req, close, fail, limits = NATIVE_LIMITS) {
1939
+ const outbound = { bytes: 0, messages: 0 }
1940
+ const queue = []
1941
+ let closed = false, flushing = false, rejectWrite
1942
+ const flush = async () => {
1943
+ if (closed || flushing) return
1944
+ flushing = true
1945
+ try {
1946
+ while (!closed && queue.length) {
1947
+ const packet = queue.shift()
1948
+ await new Promise((resolve, reject) => {
1949
+ let written = false, drained = false, started = false, settled = false
1950
+ const finish = (error) => {
1951
+ if (settled) return
1952
+ settled = true
1953
+ rejectWrite = undefined
1954
+ req.removeListener("drain", drain)
1955
+ req.removeListener("error", failed)
1956
+ req.removeListener("close", lost)
1957
+ error ? reject(error) : resolve()
1958
+ }
1959
+ const check = () => { if (started && written && drained) finish() }
1960
+ const drain = () => { drained = true; check() }
1961
+ const failed = (e) => finish(e)
1962
+ const lost = () => finish(new NativeSessionError(502, "native transport closed during a write; its outcome is uncertain"))
1963
+ rejectWrite = finish
1964
+ req.once("drain", drain)
1965
+ req.once("error", failed)
1966
+ req.once("close", lost)
1967
+ try {
1968
+ drained = req.write(packet, (e) => {
1969
+ if (e) return finish(e)
1970
+ written = true
1971
+ check()
1972
+ }) || drained
1973
+ started = true
1974
+ if (drained) req.removeListener("drain", drain)
1975
+ check()
1976
+ } catch (e) { finish(e) }
1977
+ })
1978
+ if (!closed) { outbound.bytes -= packet.length; outbound.messages-- }
1979
+ }
1980
+ } catch (e) {
1981
+ if (!closed) fail(e)
1982
+ } finally { flushing = false }
1983
+ }
1984
+ return {
1985
+ outbound,
1986
+ send(message) {
1987
+ if (closed || req.destroyed || !req.writable) throw new NativeSessionError(502, "native transport is no longer writable")
1988
+ const size = message.length + 5
1989
+ if (outbound.bytes + size > limits.outboundBytes) throw new NativeSessionError(503, "native outbound bytes limit reached")
1990
+ if (outbound.messages + 1 > limits.outboundMessages) throw new NativeSessionError(503, "native outbound messages limit reached")
1991
+ outbound.bytes += size
1992
+ outbound.messages++
1993
+ queue.push(frame(message))
1994
+ void flush()
1995
+ },
1996
+ close() {
1997
+ if (closed) return
1998
+ closed = true
1999
+ queue.length = 0
2000
+ outbound.bytes = 0
2001
+ outbound.messages = 0
2002
+ rejectWrite?.(new NativeSessionError(410, "native transport was cancelled"))
2003
+ close()
2004
+ },
2005
+ }
2006
+ }
2007
+
2008
+ // runNative opens a session-owned Run. Its background decoder survives the end
2009
+ // of each Chat segment; only terminal completion, failure, or cancellation
2010
+ // releases the transport.
2011
+ async function runNative({ tok, base, id, tools, conv, convID, signal, maxMode, params, native }) {
2012
+ const cid = convID || randomUUID()
2013
+ const since = Date.now()
2014
+ // Transport shutdown precedes final debt collection. Keep its cancellation
2015
+ // separate, and return any undelivered collection through the ledger's carry.
2016
+ const accounting = new AbortController()
2017
+ const book = ledger(tok, cid, since, accounting.signal, !!convID, { before: since })
2018
+ const debtKey = usageKey(tok, cid)
2019
+ let started = false, settled = false, deferred = null
2020
+ native.accounting = {
2021
+ started() { started = true },
2022
+ async settle() {
2023
+ const more = await book.owed(true)
2024
+ if (settled) {
2025
+ if (more) owe(debtKey, { carry: more })
2026
+ return null
2027
+ }
2028
+ deferred = more
2029
+ return more
2030
+ },
2031
+ delivered() {
2032
+ if (settled) return
2033
+ settled = true
2034
+ deferred = null
2035
+ book.done()
2036
+ accounting.abort()
2037
+ },
2038
+ abandon() {
2039
+ if (settled) return
2040
+ settled = true
2041
+ book.done()
2042
+ accounting.abort()
2043
+ if (deferred) owe(debtKey, { carry: deferred })
2044
+ deferred = null
2045
+ // Every handoff of this owner belongs to one Run. Only an abandoned
2046
+ // started/pending Run is owed; a final snapshot (including zero) is not.
2047
+ if (started && native.segments > 0 && !native.finalUsage && convID && canRead(tok)) {
2048
+ owe(debtKey, { since, n: 1, ready: Date.now() + STEP_WAIT.owedAt })
2049
+ }
2050
+ },
2051
+ }
2052
+ const { first, blobs } = buildRun(conv.msgs, conv.last, tools, id, cid, { maxMode, params })
2053
+ native.chatTools = tools
2054
+ native.attach(signal)
2055
+ if (native.error) {
2056
+ nativeSessions.forget(native)
2057
+ return { error: native.error }
2058
+ }
2059
+ const o = await open(
2060
+ base,
2061
+ {
2062
+ "content-type": "application/connect+proto",
2063
+ "connect-protocol-version": "1",
2064
+ authorization: `Bearer ${tok}`,
2065
+ "x-cursor-client-version": await clientVersion(),
2066
+ "x-cursor-client-type": "cli", // else Cursor adds a prompt of its own
2067
+ "x-ghost-mode": "true", // privacy mode: nothing kept for training
2068
+ "x-request-id": randomUUID(),
2069
+ // none of Cursor's own tools, only the caller's
2070
+ "x-cursor-agent-allowed-tools": "mcp_tool_call,get_mcp_tools_tool_call",
2071
+ },
2072
+ native.ac.signal,
2073
+ )
2074
+ if (o.error) {
2075
+ native.fail(o.error)
2076
+ nativeSessions.forget(native)
2077
+ return o
2078
+ }
2079
+ const { session, req } = o
1830
2080
  const close = () => {
1831
- ended = true
1832
- ac.abort()
1833
- req.destroy()
2081
+ try { req.close(http2.constants.NGHTTP2_CANCEL) } catch {}
2082
+ session.close()
2083
+ session.destroy()
2084
+ }
2085
+ const transport = nativeTransport(req, close, (e) => native.fail({ status: e.status ?? 502, message: e.message }), native.limits)
2086
+ try {
2087
+ native.bind(transport, blobs, conv.estimate)
2088
+ if (native.error) {
2089
+ nativeSessions.forget(native)
2090
+ return { error: native.error }
2091
+ }
2092
+ native.send(first)
2093
+ native.accounting.started()
2094
+ if (o.status < 200 || o.status > 299) {
2095
+ const chunks = []
2096
+ for await (const c of req) chunks.push(c)
2097
+ native.fail(failure(o.status, Buffer.concat(chunks).toString("utf8").slice(0, 1 << 20)))
2098
+ nativeSessions.forget(native)
2099
+ return { error: native.error, h1: !!o.h1 }
2100
+ }
2101
+ native.pumpTask = (async () => {
2102
+ await native.pump(frames(req))
2103
+ })()
2104
+ const response = native.response()
2105
+ const it = response.events
2106
+ const head = await it.next()
2107
+ if (head.done || head.value.error) {
2108
+ const error = head.value?.error ?? { status: 502, message: "an empty reply" }
2109
+ await native.cancel(error.message)
2110
+ nativeSessions.forget(native)
2111
+ return { error, h1: !!o.h1 }
2112
+ }
2113
+ let firstEvent = head
2114
+ response.events = {
2115
+ next() {
2116
+ if (firstEvent) {
2117
+ const r = firstEvent
2118
+ firstEvent = null
2119
+ return Promise.resolve(r)
2120
+ }
2121
+ return it.next()
2122
+ },
2123
+ return: () => it.return(),
2124
+ [Symbol.asyncIterator]() { return this },
2125
+ }
2126
+ return response
2127
+ } catch (e) {
2128
+ native.fail({ status: e.status ?? 502, message: e.message })
2129
+ await native.pumpTask
2130
+ nativeSessions.forget(native)
2131
+ return { error: native.error, h1: !!o.h1 }
1834
2132
  }
1835
- return Promise.resolve({ session: { close, destroy: close }, req: Object.assign(req, { close }), status: 200, h1: true })
1836
2133
  }
1837
2134
 
1838
2135
  // runOnce is one Run of the chat on the agent API at base: an async
@@ -1865,14 +2162,15 @@ async function runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, pa
1865
2162
  if (o.error) return book.done(), o
1866
2163
  const { session, req } = o
1867
2164
  let closed = false
2165
+ let heartbeat
1868
2166
  const send = (msg) => {
1869
2167
  if (!closed && !req.destroyed && req.writable) req.write(frame(msg))
1870
2168
  }
1871
- const heartbeat = setInterval(() => send(pb().bytes(7, EMPTY).done()), 5000) // the client is still there, as the CLI says
1872
2169
  const close = () => {
1873
2170
  if (closed) return
1874
2171
  closed = true
1875
2172
  clearInterval(heartbeat)
2173
+ signal?.removeEventListener("abort", close)
1876
2174
  try {
1877
2175
  req.close(http2.constants.NGHTTP2_CANCEL)
1878
2176
  } catch {}
@@ -1880,6 +2178,13 @@ async function runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, pa
1880
2178
  setTimeout(() => session.destroy(), 1000).unref?.()
1881
2179
  }
1882
2180
  signal?.addEventListener("abort", close, { once: true })
2181
+ // Abort can win after open's head check but before this Run takes ownership.
2182
+ if (signal?.aborted) {
2183
+ close()
2184
+ book.done()
2185
+ return cancelledOpen()
2186
+ }
2187
+ heartbeat = setInterval(() => send(pb().bytes(7, EMPTY).done()), 5000) // the client is still there, as the CLI says
1883
2188
  send(first)
1884
2189
  if (o.status < 200 || o.status > 299) {
1885
2190
  const chunks = []
@@ -2125,18 +2430,50 @@ function emptyWhy(how, seen, usage) {
2125
2430
  // exec answers what the server asks the client to run: a call of the
2126
2431
  // caller's tools is returned, the list of them is given, and anything else
2127
2432
  // is refused.
2128
- function exec(msg, tools, send, closeExec, refused) {
2129
- const es = fields(msg)
2433
+ function exec(msg, tools, send, closeExec, refused, native = false) {
2434
+ const es = native ? nativeFields(msg) : fields(msg)
2435
+ // ExecServerMessage.id is a proto3 uint32: an omitted field means zero.
2436
+ // Explicit malformed fields must not acquire that valid default.
2437
+ if (native && es.some((e) => e.num === 1 && (e.wire !== 0 || !Number.isInteger(e.n) || e.n < 0 || e.n > 0xffffffff)))
2438
+ throw new NativeSessionError(502, "invalid native exec request ID")
2130
2439
  const id = pbNum(es, 1)
2131
2440
  const execID = pbStr(es, 15)
2441
+ // Validate every MCP envelope before any exec can publish a call or send a
2442
+ // response. Differing duplicates have no supported selection/merge contract.
2443
+ let nativeCall, nativeUnavailable = false
2444
+ if (native) for (const e of es) if (e.num === 11) {
2445
+ if (e.wire !== 2) throw new NativeSessionError(502, "invalid native MCP argument envelope")
2446
+ const { fields: a, args } = nativeMcpArgs(e.data)
2447
+ const name = pbStr(a, 5) || pbStr(a, 1).replace(/^magpie-/, "")
2448
+ const provider = pbStr(a, 4)
2449
+ const server = pbStr(a, 9)
2450
+ if (provider && provider !== "magpie" || server && server !== "magpie") nativeUnavailable = true
2451
+ if (pbNum(a, 7) || field(a, 6)) nativeUnavailable = true
2452
+ if (!tools.some((t) => t.name === name)) nativeUnavailable = true
2453
+ if (!pbStr(a, 5) && pbStr(a, 1) !== "magpie-" + name) nativeUnavailable = true
2454
+ const call = { requestID: id, execID, nativeID: pbStr(a, 3), name, args }
2455
+ if (nativeCall && !isDeepStrictEqual(nativeCall, call)) throw new NativeSessionError(502, "ambiguous repeated native MCP argument envelopes")
2456
+ nativeCall ??= call
2457
+ }
2132
2458
  const answer = (num, res) => {
2133
2459
  send(pb().bytes(2, pb().varint(1, id).str(15, execID).bytes(num, res)).done())
2134
2460
  closeExec(id)
2135
2461
  }
2462
+ const unavailable = () => {
2463
+ const kind = es.find((e) => ![1, 15, 19, 55].includes(e.num))?.num ?? 0
2464
+ refused?.set(kind, (refused.get(kind) ?? 0) + 1)
2465
+ send(pb().bytes(5, pb().bytes(2, pb().varint(1, id).str(2, "not available"))).done())
2466
+ closeExec(id)
2467
+ return null
2468
+ }
2469
+ // A foreign/unregistered MCP or approval flow is unavailable to this bridge.
2470
+ // Refuse it like a native shell request, then let the model continue.
2471
+ if (nativeUnavailable) return unavailable()
2136
2472
  for (const e of es) {
2137
2473
  switch (e.num) {
2138
2474
  case 11: {
2139
2475
  // mcp_args: a call of the caller's tools
2476
+ if (native) return { ...nativeCall, args: JSON.stringify(nativeCall.args) }
2140
2477
  const a = fields(e.data)
2141
2478
  const args = {}
2142
2479
  for (const kv of a) {
@@ -2164,11 +2501,7 @@ function exec(msg, tools, send, closeExec, refused) {
2164
2501
  }
2165
2502
  }
2166
2503
  // a shell, a file read or an edit of Cursor's own: never run
2167
- const kind = es.find((e) => e.num !== 1 && e.num !== 15)?.num ?? 0
2168
- refused?.set(kind, (refused.get(kind) ?? 0) + 1)
2169
- send(pb().bytes(5, pb().bytes(2, pb().varint(1, id).str(2, "not available"))).done())
2170
- closeExec(id)
2171
- return null
2504
+ return unavailable()
2172
2505
  }
2173
2506
 
2174
2507
  // ---- chat completions ------------------------------------------------------------------
@@ -2209,67 +2542,117 @@ async function answerOf(auth, chat, signal, session = "") {
2209
2542
  } catch (e) {
2210
2543
  return errorResponse({ status: e.status ?? 401, message: e.message })
2211
2544
  }
2212
- const model = String(chat.model ?? "")
2213
- let raw = []
2214
- try {
2215
- raw = await usable(tok)
2216
- } catch {}
2217
- // fast only when asked: a service_tier of priority or fast is on; any
2218
- // other tier, or none, is off, unless the id is a fast one of Cursor's own
2219
- const tier = chat.service_tier
2220
- const fast = tier === "priority" || tier === "fast" ? true : tier ? false : undefined
2221
- const req = request(raw, model, chat.reasoning_effort ?? "", fast)
2222
- const id = req.id
2223
- const params = req.params
2224
- let maxMode = req.maxMode || maxOnly.has(id)
2545
+ if (signal?.aborted) return errorResponse({ status: 410, message: "the request was cancelled" })
2225
2546
  const tools = toolsOf(chat)
2226
- const conv = conversation(chat, tools)
2227
2547
  const convID = conversationID(chat, session)
2228
- let base = await agentURL(tok, false)
2229
- const once = () => runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, params })
2230
- let r = await once()
2231
- if (r.error && regional(r.error.message)) {
2232
- // the team moved, or the config was kept from before: once more with
2233
- // what the config says now. A Run that went over HTTP/1.1 went to api2,
2234
- // not the region's agent host: once more over HTTP/2 there.
2235
- const viaH1 = !!r.h1
2236
- h1Until = 0
2237
- const fresh = await agentURL(tok, true)
2238
- if (fresh !== base || viaH1) {
2239
- base = fresh
2240
- r = await once()
2548
+ const prepared = nativeSessions.prepare(tok, chat, convID, tools)
2549
+ let r
2550
+ if (prepared.owner) r = prepared.owner.resume(prepared, signal)
2551
+ const model = String(chat.model ?? "")
2552
+ if (!r) {
2553
+ let raw = []
2554
+ try {
2555
+ raw = await usable(tok)
2556
+ } catch {}
2557
+ // fast only when asked: a service_tier of priority or fast is on; any
2558
+ // other tier, or none, is off, unless the id is a fast one of Cursor's own
2559
+ const tier = chat.service_tier
2560
+ const fast = tier === "priority" || tier === "fast" ? true : tier ? false : undefined
2561
+ const req = request(raw, model, chat.reasoning_effort ?? "", fast)
2562
+ const id = req.id
2563
+ const params = req.params
2564
+ let maxMode = req.maxMode || maxOnly.has(id)
2565
+ const conv = conversation(chat, tools)
2566
+ let base = await agentURL(tok, false)
2567
+ const once = async () => {
2568
+ // Retention is an optimization. Admission pressure or unavailable ownership
2569
+ // uses the original history builder/decoder without cancelling a live parent.
2570
+ const native = prepared.fallback ? null : nativeSessions.create(prepared, { id, maxMode, params }, { pb, exec, usageOf, failure })
2571
+ const args = { tok, base, id, tools, conv, convID, signal, maxMode, params }
2572
+ let response = native ? await runNative({ ...args, native }) : await runOnce(args)
2573
+ const rebuild = async () => {
2574
+ const fallback = await runOnce(args)
2575
+ fallback.diagnostic = "stateless; bootstrap=imported-history; reason=missing-batch-count"
2576
+ return fallback
2577
+ }
2578
+ // A missing native batch count may be retried once, only before Chat
2579
+ // output is exposed or any native result was submitted. Never repeat an
2580
+ // uncertain tool result write or duplicate a progressive stream's output.
2581
+ if (response.error?.retentionIneligible && !signal?.aborted) response = await rebuild()
2582
+ else if (native) response.rebuild = rebuild
2583
+ response.diagnostic ??= "stateless; bootstrap=imported-history; reason=" + (prepared.fallback || "retention capacity or reservation unavailable")
2584
+ return response
2241
2585
  }
2242
- }
2243
- if (r.error && !maxMode && maxRequired(r.error.message)) {
2244
- // a model Cursor serves only in Max Mode that neither list said so of:
2245
- // in Max Mode, as the CLI turns it on for such a model, and so from
2246
- // now on. An account Max Mode isn't open to is answered as Cursor
2247
- // answers that.
2248
- maxMode = true
2249
- maxOnly.add(id)
2250
2586
  r = await once()
2587
+ if (r.error && regional(r.error.message)) {
2588
+ // the team moved, or the config was kept from before: once more with
2589
+ // what the config says now. A Run that went over HTTP/1.1 went to api2,
2590
+ // not the region's agent host: once more over HTTP/2 there.
2591
+ const viaH1 = !!r.h1
2592
+ h1Until = 0
2593
+ const fresh = await agentURL(tok, true)
2594
+ if (fresh !== base || viaH1) {
2595
+ base = fresh
2596
+ r = await once()
2597
+ }
2598
+ }
2599
+ if (r.error && !maxMode && maxRequired(r.error.message)) {
2600
+ // a model Cursor serves only in Max Mode that neither list said so of:
2601
+ // in Max Mode, as the CLI turns it on for such a model, and so from
2602
+ // now on. An account Max Mode isn't open to is answered as Cursor
2603
+ // answers that.
2604
+ maxMode = true
2605
+ maxOnly.add(id)
2606
+ r = await once()
2607
+ }
2251
2608
  }
2252
2609
  if (r.error) return errorResponse(r.error)
2610
+ // A resumed owner may fail while submitting results. Surface that uncertain
2611
+ // write once; later history rebuilds never submit it again on that owner.
2253
2612
  const it = r.events
2613
+ let head = await it.next()
2614
+ if (head.value?.error) return errorResponse(head.value.error)
2615
+ let events = {
2616
+ next() {
2617
+ if (head) { const value = head; head = null; return Promise.resolve(value) }
2618
+ return it.next()
2619
+ },
2620
+ return: () => it.return?.(),
2621
+ [Symbol.asyncIterator]() { return this },
2622
+ }
2623
+ const responseHeaders = { "X-Magpie-Cursor-Continuity": r.diagnostic }
2254
2624
  const cid = "chatcmpl-" + randomBytes(12).toString("hex")
2255
2625
  const created = Math.floor(Date.now() / 1000)
2256
2626
 
2257
2627
  if (!chat.stream) {
2258
- const msg = { role: "assistant", content: "" }
2259
- let reasoning = ""
2260
- const calls = []
2261
- let stop = "stop"
2262
- let usage
2263
- for await (const e of it) {
2264
- if (e.error) return errorResponse(e.error)
2265
- if (e.text) msg.content += e.text
2266
- if (e.reasoning) reasoning += e.reasoning
2267
- if (e.tool) calls.push({ id: e.tool.id, type: "function", function: { name: e.tool.name, arguments: e.tool.args } })
2268
- if (e.stop) [stop, usage] = [e.stop, e.usage]
2628
+ while (true) {
2629
+ let retry = false
2630
+ const msg = { role: "assistant", content: "" }
2631
+ let reasoning = ""
2632
+ const calls = []
2633
+ let stop = "stop"
2634
+ let usage
2635
+ for await (const e of events) {
2636
+ if (e.error) {
2637
+ if (!e.error.retentionIneligible || !r.rebuild || signal?.aborted) return errorResponse(e.error)
2638
+ r = await r.rebuild()
2639
+ if (r.error) return errorResponse(r.error)
2640
+ events = r.events
2641
+ responseHeaders["X-Magpie-Cursor-Continuity"] = r.diagnostic
2642
+ retry = true
2643
+ break
2644
+ }
2645
+ if (e.text) msg.content += e.text
2646
+ if (e.reasoning) reasoning += e.reasoning
2647
+ if (e.tool) calls.push({ id: e.tool.id, type: "function", function: { name: e.tool.name, arguments: e.tool.args } })
2648
+ if (e.stop) [stop, usage] = [e.stop, e.usage]
2649
+ }
2650
+ if (retry) continue
2651
+ if (reasoning) msg.reasoning_content = reasoning
2652
+ if (calls.length) msg.tool_calls = calls
2653
+ r.delivered?.()
2654
+ return Response.json({ id: cid, object: "chat.completion", created, model, choices: [{ index: 0, message: msg, finish_reason: stop }], ...(usage ? { usage } : {}) }, { headers: responseHeaders })
2269
2655
  }
2270
- if (reasoning) msg.reasoning_content = reasoning
2271
- if (calls.length) msg.tool_calls = calls
2272
- return Response.json({ id: cid, object: "chat.completion", created, model, choices: [{ index: 0, message: msg, finish_reason: stop }], ...(usage ? { usage } : {}) })
2273
2656
  }
2274
2657
 
2275
2658
  const chunk = (delta, finish_reason = null, extra = {}) =>
@@ -2277,16 +2660,17 @@ async function answerOf(auth, chat, signal, session = "") {
2277
2660
  let began = false
2278
2661
  const stream = new ReadableStream({
2279
2662
  async pull(ctl) {
2280
- const r = await it.next()
2663
+ const next = await events.next()
2281
2664
  if (!began) {
2282
2665
  began = true
2283
2666
  ctl.enqueue(chunk({ role: "assistant", content: "" }))
2284
2667
  }
2285
- if (r.done) {
2668
+ if (next.done) {
2669
+ r.delivered?.()
2286
2670
  ctl.enqueue(enc.encode("data: [DONE]\n\n"))
2287
2671
  return ctl.close()
2288
2672
  }
2289
- const e = r.value
2673
+ const e = next.value
2290
2674
  if (e.text) ctl.enqueue(chunk({ content: e.text }))
2291
2675
  else if (e.reasoning) ctl.enqueue(chunk({ reasoning_content: e.reasoning }))
2292
2676
  else if (e.tool) ctl.enqueue(chunk({ tool_calls: [{ index: e.tool.index, id: e.tool.id, type: "function", function: { name: e.tool.name, arguments: e.tool.args } }] }))
@@ -2296,11 +2680,12 @@ async function answerOf(auth, chat, signal, session = "") {
2296
2680
  ctl.close()
2297
2681
  }
2298
2682
  },
2299
- cancel() {
2300
- it.return?.()
2683
+ async cancel() {
2684
+ await r.cancel?.()
2685
+ await events.return()
2301
2686
  },
2302
2687
  })
2303
- return new Response(stream, { status: 200, headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" } })
2688
+ return new Response(stream, { status: 200, headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", ...responseHeaders } })
2304
2689
  }
2305
2690
 
2306
2691
  // ---- the plugin ---------------------------------------------------------------------------
@@ -2397,4 +2782,4 @@ export async function CursorAuthPlugin() {
2397
2782
  }
2398
2783
 
2399
2784
  // for tests
2400
- export const _internal = { onDemandOf,resetH1: () => (h1Until = 0), onH1: () => Date.now() < h1Until, H2, H1, h2Blocked, open, openH1, STEP_WAIT, stepUsage, ledger, owed, flights, misses, unreadable, catalog, usable, request, modelsOf, tokensOf, maxRequired, conversationID, usageOf, sessionOf, SESSION, errorResponse, kept, poolBase, conversation, buildRun, decode, exec, fields, pb, pbValue, pbAny, frame, frames, failure, toolsOf, expiry }
2785
+ export const _internal = { nativeTransport, onDemandOf,resetH1: () => (h1Until = 0), onH1: () => Date.now() < h1Until, H2, H1, h2Blocked, open, openH1, openH2, STEP_WAIT, stepUsage, usageKey, ledger, owed, flights, misses, unreadable, catalog, usable, request, modelsOf, tokensOf, maxRequired, conversationID, usageOf, sessionOf, SESSION, errorResponse, kept, poolBase, conversation, buildRun, decode, exec, fields, pb, pbValue, pbAny, frame, frames, failure, toolsOf, expiry }