@magpie-community/opencode-cursor-auth 0.2.35 → 0.2.37
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 +6 -3
- package/index.mjs +45 -2
- package/native-session.mjs +30 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,9 +101,12 @@ Tool results can continue a retained Run in the same plugin process. The owner
|
|
|
101
101
|
uses the same session/cache key and first user message as `conversation_id`, so
|
|
102
102
|
Task subagents sharing their parent's session can run independently. Retention
|
|
103
103
|
waits at most 120 seconds for a result batch and 15 minutes for a Run's total
|
|
104
|
-
lifetime. A continuation arriving
|
|
105
|
-
from supplied history
|
|
106
|
-
|
|
104
|
+
lifetime. A continuation arriving with less than 10 minutes of that lifetime
|
|
105
|
+
left rebuilds from supplied history on a fresh Run before submitting results
|
|
106
|
+
to the old one, since one step can be a long answer. The absolute deadline
|
|
107
|
+
still applies to an active segment; it is never extended by activity. A Run
|
|
108
|
+
cut by it mid-answer ends with `native Run lifetime expired: the Run timed
|
|
109
|
+
out; retry your request`, words agents ask again on.
|
|
107
110
|
The pool holds at most 8 owners per account and 32 per process, with
|
|
108
111
|
bounded history, blobs, arguments, and pending writes. When pending writes
|
|
109
112
|
reach their bound (a long history asks for its blobs at once, and a slow link
|
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
|
|
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
|
|
package/native-session.mjs
CHANGED
|
@@ -8,7 +8,20 @@ import { join } from "node:path"
|
|
|
8
8
|
export const NATIVE_LIMITS = {
|
|
9
9
|
resultWait: 120_000,
|
|
10
10
|
lifetime: 15 * 60_000,
|
|
11
|
+
// How much of the lifetime a retained Run must have left to take a tool
|
|
12
|
+
// batch's results and go on; with less, the step starts on a fresh Run
|
|
13
|
+
// from the supplied history. One step can be a long answer: a Pi turn on
|
|
14
|
+
// grok-4.7 went on with 494 s left and was still answering at the
|
|
15
|
+
// deadline (plugins#616, yetone/magpie#4419). At most two thirds of the
|
|
16
|
+
// lifetime, so a short one set in a test still has room to continue.
|
|
17
|
+
continueReserve: 10 * 60_000,
|
|
11
18
|
batchWait: 30_000,
|
|
19
|
+
// How long a stateless step whose calls came waits for Cursor's count of
|
|
20
|
+
// them (update 27) with nothing but heartbeats before it hands the calls
|
|
21
|
+
// that came to the caller (plugins#550). A count comes within ms of the
|
|
22
|
+
// step's last call; a Run that sent calls and then only heartbeats has
|
|
23
|
+
// stopped, and the caller's next request goes on from the calls' results.
|
|
24
|
+
countWait: 60_000,
|
|
12
25
|
drain: 6000,
|
|
13
26
|
replayWait: 120_000,
|
|
14
27
|
blobBytes: 64 << 20,
|
|
@@ -511,6 +524,14 @@ export class StepCalls {
|
|
|
511
524
|
get completed() {
|
|
512
525
|
return this.byCursor.size
|
|
513
526
|
}
|
|
527
|
+
// writing is how many calls the step wrote (7) that are neither started
|
|
528
|
+
// on the client, sent as an exec, nor completed by Cursor: a call of
|
|
529
|
+
// Cursor's own still running, or one still to be sent
|
|
530
|
+
get writing() {
|
|
531
|
+
let n = 0
|
|
532
|
+
for (const id of this.partial) if (!this.started.has(id) && !this.execs.has(id) && !this.byCursor.has(id)) n++
|
|
533
|
+
return n
|
|
534
|
+
}
|
|
514
535
|
// nextStep starts the next step's count
|
|
515
536
|
nextStep() {
|
|
516
537
|
this.partial.clear()
|
|
@@ -714,6 +735,13 @@ class Segment {
|
|
|
714
735
|
}
|
|
715
736
|
}
|
|
716
737
|
|
|
738
|
+
// What a Run cut at its absolute deadline says. It most often lands
|
|
739
|
+
// mid-answer, the 200 long sent, so the agent reads only these words; the
|
|
740
|
+
// conversation is not lost (asked again, the history goes on a fresh Run),
|
|
741
|
+
// and they say so in words agents retry on: "timed out" (Pi, magpie's
|
|
742
|
+
// passingSaid) and "retry your request" (plugins#616, yetone/magpie#4419).
|
|
743
|
+
export const LIFETIME_EXPIRED = "native Run lifetime expired: the Run timed out; retry your request"
|
|
744
|
+
|
|
717
745
|
export class NativeSessionPool {
|
|
718
746
|
constructor(limits = NATIVE_LIMITS) {
|
|
719
747
|
this.limits = limits
|
|
@@ -755,7 +783,7 @@ export class NativeSessionPool {
|
|
|
755
783
|
// A retained Run's absolute timer spans all tool roundtrips. Rebuild
|
|
756
784
|
// near its deadline before writing this batch: otherwise a new Chat
|
|
757
785
|
// segment can lose its transport after already exposing output.
|
|
758
|
-
const reserve = Math.min(
|
|
786
|
+
const reserve = Math.min(owner.limits.continueReserve, owner.limits.lifetime * 2 / 3)
|
|
759
787
|
if (owner.expiresAt - Date.now() <= reserve) {
|
|
760
788
|
owner.fail({ status: 410, message: "native Run retired before lifetime deadline" }, "expired")
|
|
761
789
|
return fallback("owner near lifetime deadline")
|
|
@@ -853,7 +881,7 @@ class NativeSession {
|
|
|
853
881
|
expireIn(ms) {
|
|
854
882
|
clearTimeout(this.lifetimeTimer)
|
|
855
883
|
this.expiresAt = Date.now() + ms
|
|
856
|
-
this.lifetimeTimer = later(() => this.fail({ status: 410, message:
|
|
884
|
+
this.lifetimeTimer = later(() => this.fail({ status: 410, message: LIFETIME_EXPIRED }, "expired"), ms)
|
|
857
885
|
}
|
|
858
886
|
retain(n) {
|
|
859
887
|
if (this.retainedBytes + n > this.limits.outputBytes) invalid("native output/history retention limit reached", 503)
|
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.37",
|
|
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",
|