@magpie-community/opencode-cursor-auth 0.2.35 → 0.2.36

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
@@ -2853,7 +2853,7 @@ async function runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, pa
2853
2853
 
2854
2854
  // decode follows the server's messages into the answer's pieces until the
2855
2855
  // turn ends, or the model has made its tool calls.
2856
- async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage, owedUsage, note }) {
2856
+ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage, owedUsage, note, countWait = NATIVE_LIMITS.countWait }) {
2857
2857
  let calls = 0
2858
2858
  // answered: the step's calls answered here (a refusal, the MCP tool
2859
2859
  // list), which Cursor's count of the step's calls takes in too (#142)
@@ -2937,8 +2937,48 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
2937
2937
  return finish(owedUsage ? await owedUsage(calls === 0).catch(() => null) : null)
2938
2938
  }
2939
2939
  const closeExec = (id) => send(pb().bytes(5, pb().bytes(1, pb().varint(1, id))).done())
2940
+ // A step whose calls came and whose count never does: Cursor sent the
2941
+ // calls and then only heartbeats (plugins#550, the fifth trace: two
2942
+ // calls, no count, 240 s of heartbeats until quiet). Once countWait has
2943
+ // passed since Cursor's last frame that wasn't a heartbeat, with no call
2944
+ // still being written, the calls that came are the step's. Heartbeats
2945
+ // never move the deadline: they are all such a Run sends.
2946
+ const it = (stream[Symbol.asyncIterator] ?? stream[Symbol.iterator]).call(stream)
2947
+ let heardAt = Date.now()
2948
+ const read = async () => {
2949
+ const next = Promise.resolve(it.next())
2950
+ if (!calls || listed || step.writing) return next
2951
+ next.catch(() => {}) // a read left behind by the cut fails unheard
2952
+ let timer
2953
+ const cut = new Promise((r) => {
2954
+ timer = setTimeout(() => r({ cut: true }), Math.max(0, heardAt + countWait - Date.now()))
2955
+ timer.unref?.()
2956
+ })
2957
+ try {
2958
+ return await Promise.race([next, cut])
2959
+ } finally {
2960
+ clearTimeout(timer)
2961
+ }
2962
+ }
2963
+ const beat = (data) => {
2964
+ try {
2965
+ const ms = fields(data)
2966
+ return ms.length > 0 && ms.every((m) => m.num === 1 && fields(m.data).every((u) => u.num === 13))
2967
+ } catch {
2968
+ return false
2969
+ }
2970
+ }
2940
2971
  try {
2941
- for await (const f of stream) {
2972
+ for (;;) {
2973
+ const r = await read()
2974
+ if (r.cut) {
2975
+ note?.("no count")
2976
+ yield await settled()
2977
+ return
2978
+ }
2979
+ if (r.done) break
2980
+ const f = r.value
2981
+ if (f.end || !beat(f.data)) heardAt = Date.now()
2942
2982
  if (f.end) {
2943
2983
  const e = failure(200, f.data)
2944
2984
  if (e.status < 200 || e.status > 299) yield { error: e }
@@ -3072,6 +3112,9 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
3072
3112
  return
3073
3113
  }
3074
3114
  yield { error: { status: 502, message: "the reply broke off: " + (e?.message ?? e) } }
3115
+ } finally {
3116
+ // not waited for: after a cut, the read left behind ends with the Run
3117
+ Promise.resolve(it.return?.()).catch(() => {})
3075
3118
  }
3076
3119
  }
3077
3120
 
@@ -9,6 +9,12 @@ export const NATIVE_LIMITS = {
9
9
  resultWait: 120_000,
10
10
  lifetime: 15 * 60_000,
11
11
  batchWait: 30_000,
12
+ // How long a stateless step whose calls came waits for Cursor's count of
13
+ // them (update 27) with nothing but heartbeats before it hands the calls
14
+ // that came to the caller (plugins#550). A count comes within ms of the
15
+ // step's last call; a Run that sent calls and then only heartbeats has
16
+ // stopped, and the caller's next request goes on from the calls' results.
17
+ countWait: 60_000,
12
18
  drain: 6000,
13
19
  replayWait: 120_000,
14
20
  blobBytes: 64 << 20,
@@ -511,6 +517,14 @@ export class StepCalls {
511
517
  get completed() {
512
518
  return this.byCursor.size
513
519
  }
520
+ // writing is how many calls the step wrote (7) that are neither started
521
+ // on the client, sent as an exec, nor completed by Cursor: a call of
522
+ // Cursor's own still running, or one still to be sent
523
+ get writing() {
524
+ let n = 0
525
+ for (const id of this.partial) if (!this.started.has(id) && !this.execs.has(id) && !this.byCursor.has(id)) n++
526
+ return n
527
+ }
514
528
  // nextStep starts the next step's count
515
529
  nextStep() {
516
530
  this.partial.clear()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-cursor-auth",
3
- "version": "0.2.35",
3
+ "version": "0.2.36",
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",