@magpie-community/opencode-cursor-auth 0.2.14 → 0.2.15
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 +9 -1
- package/index.mjs +50 -5
- package/native-session.mjs +39 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,7 +95,15 @@ uses the same session/cache key and first user message as `conversation_id`, so
|
|
|
95
95
|
Task subagents sharing their parent's session can run independently. Retention
|
|
96
96
|
waits at most 120 seconds for a result batch and 15 minutes for a Run's total
|
|
97
97
|
lifetime. The pool holds at most 8 owners per account and 32 per process, with
|
|
98
|
-
bounded history, blobs, arguments, and pending writes.
|
|
98
|
+
bounded history, blobs, arguments, and pending writes. When pending writes
|
|
99
|
+
reach their bound (a long history asks for its blobs at once, and a slow link
|
|
100
|
+
drains them slowly), the Run waits for the link to drain, and fails only if it
|
|
101
|
+
doesn't for 30 seconds.
|
|
102
|
+
|
|
103
|
+
Cursor's count of a step's tool calls includes calls the plugin answers itself:
|
|
104
|
+
a tool the request doesn't have is refused to the model, and Cursor's own
|
|
105
|
+
`get_mcp_tools` is answered with the request's tools. The step's calls are the
|
|
106
|
+
rest.
|
|
99
107
|
|
|
100
108
|
These are retention limits. A missing, expired, restarted, busy, incompatible,
|
|
101
109
|
or full owner uses the original history reconstruction and stateless decoder
|
package/index.mjs
CHANGED
|
@@ -1939,6 +1939,15 @@ function nativeTransport(req, close, fail, limits = NATIVE_LIMITS) {
|
|
|
1939
1939
|
const outbound = { bytes: 0, messages: 0 }
|
|
1940
1940
|
const queue = []
|
|
1941
1941
|
let closed = false, flushing = false, rejectWrite
|
|
1942
|
+
// waiters for room (KV replies), woken as writes complete. An eighth of
|
|
1943
|
+
// each bound stays free for heartbeats and exec answers, which never wait.
|
|
1944
|
+
const waiters = new Set()
|
|
1945
|
+
const spare = (n) => Math.floor(n / 8)
|
|
1946
|
+
const fits = (size) => outbound.messages === 0 || (outbound.messages + 1 + spare(limits.outboundMessages) <= limits.outboundMessages
|
|
1947
|
+
&& outbound.bytes + size + spare(limits.outboundBytes) <= limits.outboundBytes)
|
|
1948
|
+
const wake = () => {
|
|
1949
|
+
for (const w of waiters) if (closed || fits(w.size)) w.done()
|
|
1950
|
+
}
|
|
1942
1951
|
const flush = async () => {
|
|
1943
1952
|
if (closed || flushing) return
|
|
1944
1953
|
flushing = true
|
|
@@ -1975,7 +1984,7 @@ function nativeTransport(req, close, fail, limits = NATIVE_LIMITS) {
|
|
|
1975
1984
|
check()
|
|
1976
1985
|
} catch (e) { finish(e) }
|
|
1977
1986
|
})
|
|
1978
|
-
if (!closed) { outbound.bytes -= packet.length; outbound.messages
|
|
1987
|
+
if (!closed) { outbound.bytes -= packet.length; outbound.messages--; wake() }
|
|
1979
1988
|
}
|
|
1980
1989
|
} catch (e) {
|
|
1981
1990
|
if (!closed) fail(e)
|
|
@@ -1993,12 +2002,35 @@ function nativeTransport(req, close, fail, limits = NATIVE_LIMITS) {
|
|
|
1993
2002
|
queue.push(frame(message))
|
|
1994
2003
|
void flush()
|
|
1995
2004
|
},
|
|
2005
|
+
// room resolves once a message of size fits the queue with the spare
|
|
2006
|
+
// kept, and fails the Run when the queue doesn't drain for writeWait.
|
|
2007
|
+
room(size) {
|
|
2008
|
+
if (closed || fits(size)) return Promise.resolve()
|
|
2009
|
+
return new Promise((resolve, reject) => {
|
|
2010
|
+
const w = {
|
|
2011
|
+
size,
|
|
2012
|
+
done() {
|
|
2013
|
+
waiters.delete(w)
|
|
2014
|
+
clearTimeout(w.timer)
|
|
2015
|
+
resolve()
|
|
2016
|
+
},
|
|
2017
|
+
}
|
|
2018
|
+
w.timer = setTimeout(() => {
|
|
2019
|
+
waiters.delete(w)
|
|
2020
|
+
const which = outbound.bytes + size + spare(limits.outboundBytes) > limits.outboundBytes ? "bytes" : "messages"
|
|
2021
|
+
reject(new NativeSessionError(503, `native outbound ${which} limit reached: the queue did not drain for ${Math.round(limits.writeWait / 1000)}s`))
|
|
2022
|
+
}, limits.writeWait)
|
|
2023
|
+
w.timer.unref?.()
|
|
2024
|
+
waiters.add(w)
|
|
2025
|
+
})
|
|
2026
|
+
},
|
|
1996
2027
|
close() {
|
|
1997
2028
|
if (closed) return
|
|
1998
2029
|
closed = true
|
|
1999
2030
|
queue.length = 0
|
|
2000
2031
|
outbound.bytes = 0
|
|
2001
2032
|
outbound.messages = 0
|
|
2033
|
+
wake()
|
|
2002
2034
|
rejectWrite?.(new NativeSessionError(410, "native transport was cancelled"))
|
|
2003
2035
|
close()
|
|
2004
2036
|
},
|
|
@@ -2232,9 +2264,22 @@ async function runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, pa
|
|
|
2232
2264
|
// turn ends, or the model has made its tool calls.
|
|
2233
2265
|
async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage, owedUsage }) {
|
|
2234
2266
|
let calls = 0
|
|
2267
|
+
// answered: the step's calls answered here (a refusal, the MCP tool
|
|
2268
|
+
// list), which Cursor's count of the step's calls takes in too (#142)
|
|
2269
|
+
let answered = 0
|
|
2235
2270
|
// ended: Cursor said what the turn used
|
|
2236
2271
|
let ended = false
|
|
2237
2272
|
let listed = 0
|
|
2273
|
+
// stepDone is whether every call the step listed has come. A step whose
|
|
2274
|
+
// calls were all answered here goes on on Cursor's side, under a count of
|
|
2275
|
+
// its own.
|
|
2276
|
+
const stepDone = () => {
|
|
2277
|
+
if (!listed || calls + answered < listed) return false
|
|
2278
|
+
if (calls > 0) return true
|
|
2279
|
+
listed = 0
|
|
2280
|
+
answered = 0
|
|
2281
|
+
return false
|
|
2282
|
+
}
|
|
2238
2283
|
let said = 0
|
|
2239
2284
|
// wrote: the reply's text alone; a turn of thinking only says nothing
|
|
2240
2285
|
let wrote = 0
|
|
@@ -2342,9 +2387,9 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
|
|
|
2342
2387
|
return
|
|
2343
2388
|
}
|
|
2344
2389
|
break
|
|
2345
|
-
case 27: // how many tool calls the step makes, sent before them
|
|
2390
|
+
case 27: // how many tool calls the step makes, sent before or after them
|
|
2346
2391
|
listed = pbNum(uf, 1)
|
|
2347
|
-
if (
|
|
2392
|
+
if (stepDone()) {
|
|
2348
2393
|
yield await settled()
|
|
2349
2394
|
return
|
|
2350
2395
|
}
|
|
@@ -2360,8 +2405,8 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
|
|
|
2360
2405
|
if (call) {
|
|
2361
2406
|
said += call.args.length
|
|
2362
2407
|
yield { tool: { index: calls++, ...call } }
|
|
2363
|
-
}
|
|
2364
|
-
if (
|
|
2408
|
+
} else if (!fields(m.data).some((e) => e.num === 10)) answered++
|
|
2409
|
+
if (stepDone()) {
|
|
2365
2410
|
// every call made: the caller runs them
|
|
2366
2411
|
yield await settled()
|
|
2367
2412
|
return
|
package/native-session.mjs
CHANGED
|
@@ -16,6 +16,9 @@ export const NATIVE_LIMITS = {
|
|
|
16
16
|
argumentDepth: 64,
|
|
17
17
|
outboundBytes: 16 << 20,
|
|
18
18
|
outboundMessages: 256,
|
|
19
|
+
// How long a KV reply waits for room in the outbound queue (Cursor's own
|
|
20
|
+
// flow control taking the writes before it) before the Run fails.
|
|
21
|
+
writeWait: 30_000,
|
|
19
22
|
perAccount: 8,
|
|
20
23
|
perProcess: 32,
|
|
21
24
|
}
|
|
@@ -270,6 +273,9 @@ class Segment {
|
|
|
270
273
|
this.prefixDigest = historyDigest(chat.messages)
|
|
271
274
|
this.events = []
|
|
272
275
|
this.calls = []
|
|
276
|
+
// answered: the step's execs the bridge answered itself (a refusal, or
|
|
277
|
+
// the MCP tool list). Cursor's tool_requests_listed counts them too.
|
|
278
|
+
this.answered = 0
|
|
273
279
|
this.content = ""
|
|
274
280
|
this.reasoning = ""
|
|
275
281
|
this.expected = null
|
|
@@ -530,6 +536,14 @@ class NativeSession {
|
|
|
530
536
|
if (this.error || this.transportClosed) invalid("native transport is no longer writable", 410)
|
|
531
537
|
this.transport.send(message)
|
|
532
538
|
}
|
|
539
|
+
// reply answers one of Cursor's KV requests once the outbound queue has
|
|
540
|
+
// room. Cursor asks for every history blob at once, hundreds for a long
|
|
541
|
+
// conversation, faster than a slow link takes the replies: the decoder
|
|
542
|
+
// waits for the queue rather than failing the Run (#142).
|
|
543
|
+
async reply(message) {
|
|
544
|
+
await this.transport.room?.(message.length + 5)
|
|
545
|
+
this.send(message)
|
|
546
|
+
}
|
|
533
547
|
execClose(id) {
|
|
534
548
|
this.send(this.wire.pb().bytes(5, this.wire.pb().bytes(1, this.wire.pb().varint(1, id))).done())
|
|
535
549
|
}
|
|
@@ -615,6 +629,15 @@ class NativeSession {
|
|
|
615
629
|
missingCount() {
|
|
616
630
|
this.fail({ status: 502, message: "Cursor did not declare its native tool batch", retentionIneligible: !this.resultSubmitted })
|
|
617
631
|
}
|
|
632
|
+
// A call of the model's step the bridge answered itself: a refused tool (a
|
|
633
|
+
// name it doesn't serve, Cursor's own tools) or get_mcp_tools. Cursor's
|
|
634
|
+
// count is of every call the step made, so it counts toward the batch;
|
|
635
|
+
// never handed to the caller (#142).
|
|
636
|
+
answeredCall() {
|
|
637
|
+
if (this.terminal || this.state === "awaiting-results") return
|
|
638
|
+
this.segment.answered++
|
|
639
|
+
this.sealBatch()
|
|
640
|
+
}
|
|
618
641
|
listed(count) {
|
|
619
642
|
if (!Number.isSafeInteger(count) || count < 1) invalid("invalid native tool count", 502)
|
|
620
643
|
if (this.segment.expected != null && this.segment.expected !== count) invalid("inconsistent native tool count", 502)
|
|
@@ -626,8 +649,18 @@ class NativeSession {
|
|
|
626
649
|
sealBatch() {
|
|
627
650
|
const segment = this.segment
|
|
628
651
|
if (segment.expected == null) return
|
|
629
|
-
|
|
630
|
-
if (
|
|
652
|
+
const made = segment.calls.length + segment.answered
|
|
653
|
+
if (made > segment.expected) invalid("native tool batch exceeded its declared count", 502)
|
|
654
|
+
if (made !== segment.expected || segment.closed) return
|
|
655
|
+
if (!segment.calls.length) {
|
|
656
|
+
// Every call of the step was answered here: Cursor goes on with the
|
|
657
|
+
// turn by itself, and its next step lists a count of its own.
|
|
658
|
+
segment.expected = null
|
|
659
|
+
segment.answered = 0
|
|
660
|
+
clearTimeout(this.batchTimer)
|
|
661
|
+
this.batchTimer = null
|
|
662
|
+
return
|
|
663
|
+
}
|
|
631
664
|
clearTimeout(this.batchTimer)
|
|
632
665
|
this.batchTimer = null
|
|
633
666
|
this.state = "awaiting-results"
|
|
@@ -793,6 +826,8 @@ class NativeSession {
|
|
|
793
826
|
case 2: {
|
|
794
827
|
const c = exec(m.data, this.chatTools, (b) => this.send(b), (id) => this.execClose(id), null, true)
|
|
795
828
|
if (c) this.acceptCall(c)
|
|
829
|
+
// request_context is Cursor asking for the environment, not a call
|
|
830
|
+
else if (!nativeFields(m.data).some((e) => e.num === 10)) this.answeredCall()
|
|
796
831
|
break
|
|
797
832
|
}
|
|
798
833
|
case 3:
|
|
@@ -806,7 +841,7 @@ class NativeSession {
|
|
|
806
841
|
const want = field(nativeFields(k.data), 1)?.data.toString("hex") ?? ""
|
|
807
842
|
const b = this.blobs.get(want)
|
|
808
843
|
const res = b ? pb().bytes(1, b) : pb().bytes(2, pb().str(1, "blob not found"))
|
|
809
|
-
this.
|
|
844
|
+
await this.reply(pb().bytes(3, pb().varint(1, id).bytes(2, res)).done())
|
|
810
845
|
} else if (k.num === 3) {
|
|
811
846
|
try {
|
|
812
847
|
const set = nativeFields(k.data)
|
|
@@ -815,7 +850,7 @@ class NativeSession {
|
|
|
815
850
|
this.send(pb().bytes(3, pb().varint(1, id).bytes(3, pb().bytes(1, pb().str(1, "native blob could not be retained")))).done())
|
|
816
851
|
throw e
|
|
817
852
|
}
|
|
818
|
-
this.
|
|
853
|
+
await this.reply(pb().bytes(3, pb().varint(1, id).bytes(3, Buffer.alloc(0))).done())
|
|
819
854
|
}
|
|
820
855
|
}
|
|
821
856
|
break
|
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.15",
|
|
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",
|