@magpie-community/opencode-cursor-auth 0.2.15 → 0.2.16

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 CHANGED
@@ -100,6 +100,10 @@ reach their bound (a long history asks for its blobs at once, and a slow link
100
100
  drains them slowly), the Run waits for the link to drain, and fails only if it
101
101
  doesn't for 30 seconds.
102
102
 
103
+ A step's calls must all arrive within 30 seconds of each other: Cursor sends
104
+ each one once the model has written it, so a step of many long calls can take
105
+ longer than that in all (#155).
106
+
103
107
  Cursor's count of a step's tool calls includes calls the plugin answers itself:
104
108
  a tool the request doesn't have is refused to the model, and Cursor's own
105
109
  `get_mcp_tools` is answered with the request's tools. The step's calls are the
@@ -115,7 +119,11 @@ request can rebuild from its supplied history without submitting the result to
115
119
  the old native exec again.
116
120
 
117
121
  A native tool handoff reports zero usage with `magpie_native.accounting=pending`;
118
- the final native answer reports the turn's totals. An identical completed retry
122
+ the final native answer bills the turn's totals. Its own `prompt_tokens` and
123
+ `completion_tokens` are its last model call's share (the larger of that
124
+ call's prompt estimate and the turn's mean per call), since a client reads
125
+ them as how full its context is; the earlier calls' share is
126
+ `usage.magpie_deferred` (#156). An identical completed retry
119
127
  replays the saved response with zero usage. A native final also collects earlier
120
128
  stateless debt as `usage.magpie_deferred`, separately from its own prompt total.
121
129
  If a started native Run is abandoned after a pending handoff, its dashboard
package/index.mjs CHANGED
@@ -2592,7 +2592,8 @@ async function answerOf(auth, chat, signal, session = "") {
2592
2592
  const convID = conversationID(chat, session)
2593
2593
  const prepared = nativeSessions.prepare(tok, chat, convID, tools)
2594
2594
  let r
2595
- if (prepared.owner) r = prepared.owner.resume(prepared, signal)
2595
+ // the continuation's own prompt, which its final step reports (#156)
2596
+ if (prepared.owner) r = prepared.owner.resume({ ...prepared, estimate: conversation(chat, tools).estimate }, signal)
2596
2597
  const model = String(chat.model ?? "")
2597
2598
  if (!r) {
2598
2599
  let raw = []
@@ -454,6 +454,10 @@ class NativeSession {
454
454
  this.retainedBytes = 0
455
455
  this.sequence = 0
456
456
  this.segments = 0
457
+ // the model calls of the turn so far, and what was said before the
458
+ // latest one began: the last step's own share of the turn (#156)
459
+ this.steps = 1
460
+ this.stepSaid = 0
457
461
  this.said = 0
458
462
  this.wrote = 0
459
463
  this.terminal = false
@@ -515,6 +519,7 @@ class NativeSession {
515
519
  }
516
520
  this.transport = transport
517
521
  this.estimate = estimate
522
+ this.stepEstimate = estimate
518
523
  this.blobs = blobs
519
524
  this.knownHashes = new Set(blobs.keys())
520
525
  this.blobSize = [...blobs.values()].reduce((n, b) => n + b.length, 0)
@@ -571,6 +576,8 @@ class NativeSession {
571
576
  this.state = "submitting"
572
577
  clearTimeout(this.resultTimer)
573
578
  clearTimeout(this.batchTimer)
579
+ this.batchTimer = null
580
+ this.nextStep(prepared.estimate)
574
581
  try {
575
582
  for (let i = 0; i < prepared.results.length; i++) {
576
583
  const call = batch.calls.find((c) => c.id === prepared.results[i].tool_call_id)
@@ -620,8 +627,20 @@ class NativeSession {
620
627
  this.waitForBatch()
621
628
  this.sealBatch()
622
629
  }
630
+ // nextStep is Cursor's next model call in the same turn: after the
631
+ // caller's results, or after a step whose calls were all answered here.
632
+ nextStep(estimate) {
633
+ this.steps++
634
+ this.stepSaid = this.said
635
+ if (estimate != null) this.stepEstimate = estimate
636
+ }
637
+ // waitForBatch gives the step batchWait from its latest call, answered
638
+ // call or count, not from its first. Cursor sends each call once the model
639
+ // has written its arguments, so a step of many long calls arrives over
640
+ // longer than batchWait while never pausing that long (#155).
623
641
  waitForBatch() {
624
- this.batchTimer ??= later(() => {
642
+ clearTimeout(this.batchTimer)
643
+ this.batchTimer = later(() => {
625
644
  if (this.segment.expected == null && this.segment.calls.length && !this.resultSubmitted) this.missingCount()
626
645
  else this.fail({ status: 502, message: "Cursor did not complete its declared native tool batch" })
627
646
  }, this.limits.batchWait)
@@ -636,6 +655,7 @@ class NativeSession {
636
655
  answeredCall() {
637
656
  if (this.terminal || this.state === "awaiting-results") return
638
657
  this.segment.answered++
658
+ if (this.batchTimer) this.waitForBatch()
639
659
  this.sealBatch()
640
660
  }
641
661
  listed(count) {
@@ -659,6 +679,7 @@ class NativeSession {
659
679
  segment.answered = 0
660
680
  clearTimeout(this.batchTimer)
661
681
  this.batchTimer = null
682
+ this.nextStep()
662
683
  return
663
684
  }
664
685
  clearTimeout(this.batchTimer)
@@ -762,6 +783,24 @@ class NativeSession {
762
783
  const u = usageOf(this.usageFields ?? [])
763
784
  if (!measuredInput) u.input = this.estimate
764
785
  if (!measuredOutput) u.output = Math.floor((this.said + 3) / 4)
786
+ // A turn of several model calls: TurnEndedUpdate counts them all, but a
787
+ // client reads the last response's prompt as how full its context is
788
+ // (Codex compacted a 61K context at 1.1M, #156). The last call keeps its
789
+ // own share, the larger of its prompt estimate and the turn's mean per
790
+ // call; the rest is billed as magpie_deferred.
791
+ let earlier = null
792
+ if (this.steps > 1) {
793
+ const whole = u.input + u.cacheRead + u.cacheWrite
794
+ const share = (total, own) => total > 0 ? Math.min(1, Math.max(own, total / this.steps) / total) : 0
795
+ const fi = share(whole, measuredInput ? this.stepEstimate ?? 0 : whole)
796
+ const fo = share(u.output, Math.floor((this.said - this.stepSaid + 3) / 4))
797
+ const own = {
798
+ input: Math.round(u.input * fi), cacheRead: Math.round(u.cacheRead * fi), cacheWrite: Math.round(u.cacheWrite * fi),
799
+ output: Math.round(u.output * fo), reasoning: Math.round(u.reasoning * fo),
800
+ }
801
+ earlier = { input: u.input - own.input, cacheRead: u.cacheRead - own.cacheRead, cacheWrite: u.cacheWrite - own.cacheWrite, output: u.output - own.output }
802
+ Object.assign(u, own)
803
+ }
765
804
  const prompt = u.input + u.cacheRead + u.cacheWrite
766
805
  this.finalUsage = {
767
806
  prompt_tokens: prompt, completion_tokens: u.output, total_tokens: prompt + u.output,
@@ -771,8 +810,9 @@ class NativeSession {
771
810
  }
772
811
  this.state = "finalizing"
773
812
  this.closeTransport()
774
- const finish = (more) => {
813
+ const finish = (settled) => {
775
814
  if (this.error) return
815
+ const more = earlier && settled ? Object.fromEntries(Object.keys(earlier).map((k) => [k, earlier[k] + settled[k]])) : earlier ?? settled
776
816
  const deferred = more && more.input + more.output + more.cacheRead + more.cacheWrite > 0
777
817
  this.segment.finish({ stop: "stop", usage: {
778
818
  ...this.finalUsage,
@@ -816,6 +856,7 @@ class NativeSession {
816
856
  this.said += text.length
817
857
  if (u.num === 1) this.wrote += text.length
818
858
  this.segment.emit(u.num === 1 ? { text } : { reasoning: text })
859
+ if (this.batchTimer) this.waitForBatch()
819
860
  }
820
861
  } else if (u.num === 14) {
821
862
  if (!this.segment.content && !this.segment.calls.length) invalid("an empty reply (Cursor ended the turn without text or a tool call)", 502)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-cursor-auth",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
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",