@magpie-community/opencode-cursor-auth 0.2.13 → 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.
@@ -0,0 +1,845 @@
1
+ import { createHash, randomBytes } from "node:crypto"
2
+
3
+ // Retention is process-local and optional. A lost owner rebuilds Chat history
4
+ // on a fresh Run; it never resubmits a result to an uncertain native exec.
5
+ // Checkpoints are retained evidence, not restart handles.
6
+ export const NATIVE_LIMITS = {
7
+ resultWait: 120_000,
8
+ lifetime: 15 * 60_000,
9
+ batchWait: 30_000,
10
+ drain: 6000,
11
+ replayWait: 120_000,
12
+ blobBytes: 64 << 20,
13
+ outputBytes: 16 << 20,
14
+ argumentBytes: 1 << 20,
15
+ argumentFields: 100_000,
16
+ argumentDepth: 64,
17
+ outboundBytes: 16 << 20,
18
+ outboundMessages: 256,
19
+ perAccount: 8,
20
+ perProcess: 32,
21
+ }
22
+
23
+ const HANDLE = /^call_cursor_[a-f0-9]+_\d+_\d+$/
24
+ const hash = (s) => createHash("sha256").update(s).digest("hex")
25
+ const canonical = (v) => JSON.stringify(v, (_k, x) => x && typeof x === "object" && !Array.isArray(x)
26
+ ? Object.fromEntries(Object.keys(x).sort().map((k) => [k, x[k]])) : x)
27
+ const bytes = (v) => Buffer.byteLength(canonical(v))
28
+ const later = (fn, ms) => {
29
+ const timer = setTimeout(fn, ms)
30
+ timer.unref?.()
31
+ return timer
32
+ }
33
+
34
+ export class NativeSessionError extends Error {
35
+ constructor(status, message) {
36
+ super(message)
37
+ this.status = status
38
+ }
39
+ }
40
+ const invalid = (message, status = 409) => { throw new NativeSessionError(status, message) }
41
+ const failureOf = (e) => ({ status: e?.status ?? 502, message: e?.message ?? String(e) })
42
+
43
+ // Unlike the compatibility codec, malformed protobuf tails must fail a live
44
+ // continuation. Dropping one could turn a truncated batch into a valid handoff.
45
+ export function nativeFields(input, maxFields = Infinity) {
46
+ const b = Buffer.from(input)
47
+ const out = []
48
+ let at = 0
49
+ const uv = () => {
50
+ let n = 0
51
+ let scale = 1
52
+ for (let i = 0; i < 10 && at < b.length; i++) {
53
+ const c = b[at++]
54
+ n += (c & 127) * scale
55
+ if (c < 128 && Number.isSafeInteger(n)) return n
56
+ scale *= 128
57
+ }
58
+ invalid("malformed native protobuf", 502)
59
+ }
60
+ while (at < b.length) {
61
+ const key = uv()
62
+ const num = Math.floor(key / 8)
63
+ const wire = key % 8
64
+ if (!num || num > 0x1fffffff) invalid("malformed native protobuf field", 502)
65
+ let n = 0
66
+ let data = Buffer.alloc(0)
67
+ if (wire === 0) n = uv()
68
+ else if (wire === 2 || wire === 1 || wire === 5) {
69
+ const size = wire === 2 ? uv() : wire === 1 ? 8 : 4
70
+ if (at + size > b.length) invalid("truncated native protobuf", 502)
71
+ data = b.subarray(at, at + size)
72
+ at += size
73
+ } else invalid("unsupported native protobuf wire type", 502)
74
+ if (out.length >= maxFields) invalid("native protobuf field limit reached", 502)
75
+ out.push({ num, wire, n, data })
76
+ }
77
+ return out
78
+ }
79
+ // Decode every known Value kind, including overwritten oneof/map fields, so
80
+ // corrupt bytes cannot become different, valid JSON through prefix decoding.
81
+ // Missing scalar/map values keep protobuf defaults; unknown fields are skipped
82
+ // only after their complete wire envelope has passed validation.
83
+ export function nativeMcpArgs(input, limits = NATIVE_LIMITS) {
84
+ if (input.length > limits.argumentBytes) invalid("native MCP argument byte limit reached", 502)
85
+ let remaining = limits.argumentFields
86
+ const read = (b) => {
87
+ const fs = nativeFields(b, remaining)
88
+ remaining -= fs.length
89
+ return fs
90
+ }
91
+ const typed = (f, wire) => {
92
+ if (f.wire !== wire) invalid("invalid native MCP argument field type", 502)
93
+ }
94
+ const utf8 = (b) => {
95
+ try { return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(b) }
96
+ catch { invalid("invalid UTF-8 in native MCP arguments", 502) }
97
+ }
98
+ function mapEntry(b, depth) {
99
+ let key = "", value = null
100
+ for (const f of read(b)) {
101
+ if (f.num === 1) { typed(f, 2); key = utf8(f.data) }
102
+ else if (f.num === 2) { typed(f, 2); value = decodeValue(f.data, depth) }
103
+ }
104
+ return [key, value]
105
+ }
106
+ function decodeStruct(b, depth) {
107
+ const entries = []
108
+ for (const f of read(b)) if (f.num === 1) {
109
+ typed(f, 2)
110
+ entries.push(mapEntry(f.data, depth))
111
+ }
112
+ return Object.fromEntries(entries)
113
+ }
114
+ function decodeValue(b, depth = 0) {
115
+ if (depth > limits.argumentDepth) invalid("native MCP argument nesting limit reached", 502)
116
+ let value = null
117
+ for (const f of read(b)) {
118
+ if (f.num < 1 || f.num > 6) continue
119
+ typed(f, [0, 1, 2, 0, 2, 2][f.num - 1])
120
+ switch (f.num) {
121
+ case 1:
122
+ if (f.n !== 0) invalid("invalid native protobuf NullValue", 502)
123
+ value = null
124
+ break
125
+ case 2:
126
+ value = f.data.readDoubleLE(0)
127
+ if (!Number.isFinite(value)) invalid("nonfinite native MCP argument number", 502)
128
+ break
129
+ case 3: value = utf8(f.data); break
130
+ case 4: value = f.n !== 0; break
131
+ case 5: value = decodeStruct(f.data, depth + 1); break
132
+ case 6:
133
+ value = []
134
+ for (const item of read(f.data)) if (item.num === 1) {
135
+ typed(item, 2)
136
+ value.push(decodeValue(item.data, depth + 1))
137
+ }
138
+ break
139
+ }
140
+ }
141
+ return value
142
+ }
143
+ const fs = read(input), entries = []
144
+ for (const f of fs) {
145
+ if ([1, 3, 4, 5, 9].includes(f.num)) { typed(f, 2); utf8(f.data) }
146
+ else if (f.num === 2) { typed(f, 2); entries.push(mapEntry(f.data, 0)) }
147
+ else if (f.num === 6) typed(f, 2)
148
+ else if (f.num === 7) typed(f, 0)
149
+ }
150
+ return { fields: fs, args: Object.fromEntries(entries) }
151
+ }
152
+
153
+ const field = (fs, n) => fs.find((f) => f.num === n)
154
+ const str = (fs, n) => field(fs, n)?.data.toString("utf8") ?? ""
155
+ const num = (fs, n) => field(fs, n)?.n ?? 0
156
+
157
+ function argsOf(s) {
158
+ try {
159
+ const v = typeof s === "string" ? JSON.parse(s) : s
160
+ if (v && typeof v === "object" && !Array.isArray(v)) return v
161
+ } catch {}
162
+ invalid("invalid tool arguments in native continuation", 400)
163
+ }
164
+ // These conversions only decide whether a retained exec can be continued.
165
+ // Unsupported parts make that request use the legacy history builder instead
166
+ // of imposing a new Chat request contract on all callers.
167
+ function assistantDisplay(message) {
168
+ const parts = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content ?? []
169
+ if (!Array.isArray(parts) || parts.some((p) => !["text", "reasoning"].includes(p?.type) || typeof p.text !== "string")) invalid("unsupported native assistant display")
170
+ const reasoning = [message.reasoning_content, message.reasoning, message.reasoning_text].find((v) => typeof v === "string" && v.trim()) ?? ""
171
+ return {
172
+ text: parts.filter((p) => p.type === "text").map((p) => p.text).join(""),
173
+ reasoning: reasoning + parts.filter((p) => p.type === "reasoning").map((p) => p.text).join(""),
174
+ }
175
+ }
176
+ function resultParts(message, limits) {
177
+ const parts = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content
178
+ if (!Array.isArray(parts)) invalid("unsupported native tool result")
179
+ let retained = 0
180
+ return parts.map((part) => {
181
+ if (part?.type === "text" && typeof part.text === "string") {
182
+ retained += Buffer.byteLength(part.text)
183
+ if (retained > limits.outputBytes) invalid("native tool result limit reached")
184
+ return { text: part.text }
185
+ }
186
+ const url = part?.type === "image_url" ? (typeof part.image_url === "string" ? part.image_url : part.image_url?.url)
187
+ : part?.type === "image" && part.source?.type === "base64" ? `data:${part.source.media_type};base64,${part.source.data}` : ""
188
+ const image = /^data:(image\/(?:png|jpeg|gif|webp));base64,([A-Za-z0-9+/]*={0,2})$/.exec(url ?? "")
189
+ if (!image) invalid("unsupported native tool result content")
190
+ const data = Buffer.from(image[2], "base64")
191
+ retained += data.length
192
+ if (retained > limits.outputBytes) invalid("native tool result limit reached")
193
+ if (!data.length || data.toString("base64").replace(/=+$/, "") !== image[2].replace(/=+$/, "")) invalid("invalid native result image")
194
+ return { data, mimeType: image[1] }
195
+ })
196
+ }
197
+ function normalizeMessage(m) {
198
+ if (m?.role !== "assistant") return m
199
+ const { text, reasoning } = assistantDisplay(m)
200
+ const out = { ...m, content: text }
201
+ delete out.reasoning
202
+ delete out.reasoning_content
203
+ delete out.reasoning_text
204
+ if (reasoning) out.reasoning_content = reasoning
205
+ if (m.tool_calls) out.tool_calls = m.tool_calls.map((c) => ({
206
+ ...c, type: c.type ?? "function", function: { ...c.function, arguments: argsOf(c.function?.arguments ?? "{}") },
207
+ }))
208
+ return out
209
+ }
210
+ const historyDigest = (messages) => hash(canonical(messages.map(normalizeMessage)))
211
+ function controlsOf(chat, selector, tools) {
212
+ const controls = { ...chat }
213
+ for (const k of ["messages", "stream", "stream_options"]) delete controls[k]
214
+ return hash(canonical({ selector, controls, tools }))
215
+ }
216
+ const toolAttachment = (m) => m?.role === "user" && Array.isArray(m.content) && m.content.length > 1
217
+ && m.content[0]?.type === "text" && m.content[0].text === "Attached image(s) from tool result:"
218
+ && m.content.slice(1).every((p) => p?.type === "image_url")
219
+ // A complete result batch is validated before reserving or writing any exec.
220
+ // Only the current exported assistant may omit display reasoning. All earlier
221
+ // accepted history must match, including instruction order and attachments.
222
+ function matchResults(batch, messages) {
223
+ if (historyDigest(messages.slice(0, batch.prefixLength)) !== batch.prefixDigest) invalid("native continuation history diverged")
224
+ const suffix = messages.slice(batch.prefixLength)
225
+ const assistants = []
226
+ while (suffix[assistants.length]?.role === "assistant") assistants.push(suffix[assistants.length])
227
+ if (!assistants.length) invalid("native continuation is missing its assistant tool calls", 400)
228
+ let content = ""
229
+ let reasoning = ""
230
+ const calls = []
231
+ for (const m of assistants) {
232
+ const unknown = Object.keys(m).filter((k) => !["role", "content", "tool_calls", "reasoning", "reasoning_content", "reasoning_text"].includes(k))
233
+ if (unknown.length) invalid("unsupported assistant fields in native continuation", 400)
234
+ const display = assistantDisplay(m)
235
+ content += display.text
236
+ reasoning += display.reasoning
237
+ for (const c of m.tool_calls ?? []) {
238
+ if (c.type && c.type !== "function") invalid("unsupported native continuation tool call", 400)
239
+ calls.push({ id: c.id, name: c.function?.name, args: argsOf(c.function?.arguments ?? "{}") })
240
+ }
241
+ }
242
+ if (content !== batch.content || reasoning && reasoning !== batch.reasoning) invalid("native continuation assistant output changed")
243
+ const expected = batch.calls.map((c) => ({ id: c.id, name: c.name, args: argsOf(c.args) }))
244
+ const sorted = (v) => [...v].sort((a, b) => String(a.id).localeCompare(String(b.id)))
245
+ if (canonical(sorted(calls)) !== canonical(sorted(expected))) invalid("native continuation tool calls changed")
246
+ let results = suffix.slice(assistants.length)
247
+ // Pi's exact image suffix has no call IDs. Only a single complete result
248
+ // gives it unambiguous ownership; all other shapes rebuild legacy history.
249
+ if (batch.calls.length === 1 && results.length === 2 && results[0]?.role === "tool" && toolAttachment(results[1])) {
250
+ const [result, attachment] = results
251
+ const content = typeof result.content === "string" ? [{ type: "text", text: result.content }] : result.content
252
+ if (!Array.isArray(content)) invalid("unsupported native tool result")
253
+ results = [{ ...result, content: [...content, ...attachment.content.slice(1)] }]
254
+ }
255
+ if (results.some((m) => m?.role !== "tool")) invalid("new input cannot be interleaved with pending native results")
256
+ const ids = new Set()
257
+ for (const m of results) {
258
+ if (ids.has(m.tool_call_id)) invalid("duplicate native tool result", 400)
259
+ ids.add(m.tool_call_id)
260
+ if (!expected.some((c) => c.id === m.tool_call_id)) invalid("unknown native tool result", 400)
261
+ }
262
+ if (ids.size !== expected.length) invalid("all parallel native tool results must be submitted together", 400)
263
+ return [...results].sort((a, b) => a.tool_call_id.localeCompare(b.tool_call_id))
264
+ }
265
+
266
+ class Segment {
267
+ constructor(owner, chat) {
268
+ this.owner = owner
269
+ this.prefixLength = chat.messages.length
270
+ this.prefixDigest = historyDigest(chat.messages)
271
+ this.events = []
272
+ this.calls = []
273
+ this.content = ""
274
+ this.reasoning = ""
275
+ this.expected = null
276
+ this.closed = false
277
+ this.clean = false
278
+ this.waiters = new Set()
279
+ }
280
+ wake() {
281
+ for (const wake of this.waiters) wake()
282
+ this.waiters.clear()
283
+ }
284
+ emit(e) {
285
+ this.owner.retain(bytes(e))
286
+ if (e.text) this.content += e.text
287
+ if (e.reasoning) this.reasoning += e.reasoning
288
+ this.events.push(e)
289
+ this.wake()
290
+ }
291
+ finish(stop) {
292
+ for (const [index, c] of this.calls.entries()) {
293
+ const e = { tool: { index, id: c.id, name: c.name, args: c.args } }
294
+ this.owner.retain(bytes(e))
295
+ this.events.push(e)
296
+ }
297
+ this.owner.retain(bytes(stop))
298
+ this.events.push(stop)
299
+ this.closed = true
300
+ this.wake()
301
+ }
302
+ fail(error) {
303
+ // Errors also override a sealed, as yet undelivered handoff.
304
+ this.events = [{ error }]
305
+ this.closed = true
306
+ this.wake()
307
+ }
308
+ iterator({ replay = false } = {}) {
309
+ const segment = this
310
+ let at = 0
311
+ let errorSent = false
312
+ return {
313
+ async next() {
314
+ while (!segment.closed && at >= segment.events.length) await new Promise((r) => segment.waiters.add(r))
315
+ if (!replay && segment.owner.error) {
316
+ if (errorSent) return { done: true }
317
+ errorSent = true
318
+ return { value: { error: segment.owner.error }, done: false }
319
+ }
320
+ if (at < segment.events.length) {
321
+ const event = segment.events[at++]
322
+ if (replay && event.stop) return { value: { ...event, usage: {
323
+ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0,
324
+ prompt_tokens_details: { cached_tokens: 0, cache_write_tokens: 0 },
325
+ magpie_native: { accounting: "replayed" },
326
+ } }, done: false }
327
+ return { value: event, done: false }
328
+ }
329
+ return { done: true }
330
+ },
331
+ async return() {
332
+ if (!replay) await segment.owner.cancel("the Chat response was cancelled")
333
+ return { done: true }
334
+ },
335
+ [Symbol.asyncIterator]() { return this },
336
+ }
337
+ }
338
+ }
339
+
340
+ export class NativeSessionPool {
341
+ constructor(limits = NATIVE_LIMITS) {
342
+ this.limits = limits
343
+ this.entries = new Set()
344
+ this.handles = new Map()
345
+ this.selectors = new Map()
346
+ }
347
+ // selector is conversationID(chat, session): session/cache key AND first
348
+ // user identity. Task subagents sharing a session must not own its parent.
349
+ prepare(tok, chat, selector, tools) {
350
+ const fallback = (reason) => ({ fallback: reason })
351
+ if (!Array.isArray(chat.messages) || bytes(chat) > this.limits.outputBytes) return fallback("history retention limit")
352
+ const account = hash(tok)
353
+ const controls = controlsOf(chat, selector, tools)
354
+ const trailing = []
355
+ const end = chat.messages.length - (toolAttachment(chat.messages.at(-1)) ? 2 : 1)
356
+ for (let i = end; i >= 0 && chat.messages[i]?.role === "tool"; i--) trailing.push(chat.messages[i].tool_call_id)
357
+ const handles = trailing.filter((id) => typeof id === "string" && HANDLE.test(id))
358
+ if (handles.length) {
359
+ // Look up ownership before inspecting native history. Restarted callers
360
+ // only need the same permissive history shape the stateless path accepts.
361
+ const found = handles.map((id) => this.handles.get(id))
362
+ if (found.some((v) => !v)) return fallback("owner unavailable")
363
+ const { owner, batch } = found[0]
364
+ if (found.some((v) => v.owner !== owner || v.batch !== batch)) return fallback("different native batches")
365
+ if (owner.account !== account || owner.controls !== controls || owner.error) return fallback("owner incompatible or expired")
366
+ try {
367
+ const results = matchResults(batch, chat.messages)
368
+ // Encoding is pure for the entire batch before any reservation or write.
369
+ const encoded = results.map((m) => owner.encodeResult(m))
370
+ const digest = hash(canonical(results))
371
+ if (batch.resultDigest) {
372
+ // An uncertain or concurrent write is never submitted twice to this
373
+ // exec. History reconstruction uses provided results as imported data.
374
+ if (batch.resultDigest !== digest || !batch.next?.clean) return fallback("result already submitted or busy")
375
+ return { owner, batch, replay: true, segment: batch.next }
376
+ }
377
+ if (owner.state !== "awaiting-results" || owner.segment !== batch || !batch.clean) return fallback("owner busy")
378
+ return { owner, batch, results, encoded, digest, chat }
379
+ } catch (e) {
380
+ if (!(e instanceof NativeSessionError)) throw e
381
+ return fallback("history incompatible with retained owner")
382
+ }
383
+ }
384
+ // Pending ancestors, new input, and terminal old handles are ordinary
385
+ // imported history, even while a parent Run is still awaiting its results.
386
+ if (chat.messages.some((m) => m?.tool_calls?.some((c) => HANDLE.test(c?.id ?? "")))) return fallback("historical native calls")
387
+ if (selector && this.selectors.has(account + ":" + selector)) return fallback("owner busy")
388
+ return { account, selector, controls, selected: !!selector, chat }
389
+ }
390
+ create(prepared, resolution, wire) {
391
+ if (prepared.fallback || prepared.selected && this.selectors.has(prepared.account + ":" + prepared.selector)) return null
392
+ const crowded = () => this.entries.size >= this.limits.perProcess || [...this.entries].filter((e) => e.account === prepared.account).length >= this.limits.perAccount
393
+ while (crowded()) {
394
+ const accountFull = [...this.entries].filter((e) => e.account === prepared.account).length >= this.limits.perAccount
395
+ const finished = [...this.entries].find((e) => ["complete", "failed", "cancelled", "expired"].includes(e.state) && (!accountFull || e.account === prepared.account))
396
+ if (!finished) return null
397
+ this.forget(finished)
398
+ }
399
+ let owner
400
+ try { owner = new NativeSession(this, prepared, resolution, wire) }
401
+ catch (e) {
402
+ if (!(e instanceof NativeSessionError)) throw e
403
+ return null
404
+ }
405
+ this.entries.add(owner)
406
+ if (prepared.selected) this.selectors.set(owner.account + ":" + owner.selector, owner)
407
+ return owner
408
+ }
409
+ release(owner) {
410
+ const key = owner.account + ":" + owner.selector
411
+ if (this.selectors.get(key) === owner) this.selectors.delete(key)
412
+ }
413
+ forget(owner) {
414
+ owner.accounting?.abandon()
415
+ clearTimeout(owner.replayTimer)
416
+ this.release(owner)
417
+ this.entries.delete(owner)
418
+ for (const [id, h] of this.handles) if (h.owner === owner) this.handles.delete(id)
419
+ owner.blobs.clear()
420
+ owner.checkpoint = null
421
+ owner.execs.clear()
422
+ owner.nativeCalls.clear()
423
+ }
424
+ async clear() {
425
+ await Promise.all([...this.entries].map((e) => e.cancel("native owner removed")))
426
+ for (const e of this.entries) {
427
+ clearTimeout(e.replayTimer)
428
+ this.forget(e)
429
+ }
430
+ }
431
+ }
432
+
433
+ class NativeSession {
434
+ constructor(pool, prepared, resolution, wire) {
435
+ Object.assign(this, prepared)
436
+ this.pool = pool
437
+ this.limits = pool.limits
438
+ this.resolution = resolution
439
+ this.wire = wire
440
+ this.identity = randomBytes(12).toString("hex")
441
+ this.state = "opening"
442
+ this.ac = new AbortController()
443
+ this.execs = new Map()
444
+ this.nativeCalls = new Map()
445
+ this.blobs = new Map()
446
+ this.knownHashes = new Set()
447
+ this.blobSize = 0
448
+ this.retainedBytes = 0
449
+ this.sequence = 0
450
+ this.segments = 0
451
+ this.said = 0
452
+ this.wrote = 0
453
+ this.terminal = false
454
+ this.segment = new Segment(this, prepared.chat)
455
+ this.retain(bytes(prepared.chat))
456
+ this.lifetimeTimer = later(() => this.fail({ status: 410, message: "native Run lifetime expired" }, "expired"), this.limits.lifetime)
457
+ }
458
+ retain(n) {
459
+ if (this.retainedBytes + n > this.limits.outputBytes) invalid("native output/history retention limit reached", 503)
460
+ this.retainedBytes += n
461
+ }
462
+ attach(signal, segment = this.segment) {
463
+ if (signal?.aborted) {
464
+ this.fail({ status: 410, message: "the Chat request was already cancelled" }, "cancelled")
465
+ return
466
+ }
467
+ const abort = () => this.fail({ status: 410, message: "the Chat request was cancelled" }, "cancelled")
468
+ signal?.addEventListener("abort", abort, { once: true })
469
+ segment.detach = () => signal?.removeEventListener("abort", abort)
470
+ }
471
+ delivered(segment = this.segment) {
472
+ if (this.error) return
473
+ segment.clean = true
474
+ segment.detach?.()
475
+ if (this.state === "complete" && segment === this.segment) this.accounting?.delivered()
476
+ }
477
+ closeTransport() {
478
+ if (this.transportClosed) return
479
+ this.transportClosed = true
480
+ clearInterval(this.runHeartbeat)
481
+ clearInterval(this.execHeartbeat)
482
+ clearTimeout(this.batchTimer)
483
+ clearTimeout(this.resultTimer)
484
+ clearTimeout(this.drainTimer)
485
+ clearTimeout(this.lifetimeTimer)
486
+ this.ac.abort()
487
+ this.transport?.close()
488
+ }
489
+ fail(error, state = "failed") {
490
+ if (this.error || this.state === "complete") return
491
+ this.error = error
492
+ this.state = state
493
+ this.segment.fail(error)
494
+ for (const segment of this.batches ?? []) segment.detach?.()
495
+ this.segment.detach?.()
496
+ this.closeTransport()
497
+ // A failed transport has no reusable execs. Forget its handles immediately;
498
+ // subsequent caller-supplied history takes the stateless path.
499
+ this.pool.forget(this)
500
+ }
501
+ async cancel(message) {
502
+ this.fail({ status: 410, message }, "cancelled")
503
+ await this.pumpTask
504
+ }
505
+ bind(transport, blobs, estimate) {
506
+ if (this.transportClosed || this.error) {
507
+ transport.close()
508
+ return
509
+ }
510
+ this.transport = transport
511
+ this.estimate = estimate
512
+ this.blobs = blobs
513
+ this.knownHashes = new Set(blobs.keys())
514
+ this.blobSize = [...blobs.values()].reduce((n, b) => n + b.length, 0)
515
+ if (this.blobSize > this.limits.blobBytes) invalid("native bootstrap blob limit reached", 503)
516
+ if (this.error) return this.closeTransport()
517
+ this.state = "running"
518
+ const { pb } = this.wire
519
+ const heartbeat = (message) => {
520
+ try { this.send(message) } catch (e) { this.fail(failureOf(e)) }
521
+ }
522
+ this.runHeartbeat = setInterval(() => heartbeat(pb().bytes(7, Buffer.alloc(0)).done()), 5000)
523
+ this.execHeartbeat = setInterval(() => {
524
+ for (const c of this.execs.values()) if (!c.submitted) heartbeat(pb().bytes(5, pb().bytes(3, pb().varint(1, c.requestID))).done())
525
+ }, 3000)
526
+ this.runHeartbeat.unref?.()
527
+ this.execHeartbeat.unref?.()
528
+ }
529
+ send(message) {
530
+ if (this.error || this.transportClosed) invalid("native transport is no longer writable", 410)
531
+ this.transport.send(message)
532
+ }
533
+ execClose(id) {
534
+ this.send(this.wire.pb().bytes(5, this.wire.pb().bytes(1, this.wire.pb().varint(1, id))).done())
535
+ }
536
+ encodeResult(message) {
537
+ const { pb } = this.wire
538
+ const success = pb()
539
+ for (const item of resultParts(message, this.limits)) {
540
+ if (item.text !== undefined) success.bytes(1, pb().bytes(1, pb().str(1, item.text)))
541
+ else success.bytes(1, pb().bytes(2, pb().bytes(1, item.data).str(2, item.mimeType)))
542
+ }
543
+ if (message.is_error === true || message.isError === true) success.varint(2, 1)
544
+ return pb().bytes(1, success).done() // McpResult.success, not stored McpToolResult
545
+ }
546
+ resume(prepared, signal) {
547
+ if (prepared.replay) return this.response(prepared.segment, true)
548
+ const batch = prepared.batch
549
+ if (this.error || this.state !== "awaiting-results" || this.segment !== batch || batch.resultDigest) invalid("native continuation is already reserved")
550
+ const next = new Segment(this, prepared.chat)
551
+ this.attach(signal, next)
552
+ if (this.error) invalid(this.error.message, this.error.status)
553
+ // Reserve synchronously. Competing continuations cannot interleave writes.
554
+ batch.resultDigest = prepared.digest
555
+ batch.next = next
556
+ this.segment = next
557
+ this.state = "submitting"
558
+ clearTimeout(this.resultTimer)
559
+ clearTimeout(this.batchTimer)
560
+ try {
561
+ for (let i = 0; i < prepared.results.length; i++) {
562
+ const call = batch.calls.find((c) => c.id === prepared.results[i].tool_call_id)
563
+ const msg = this.wire.pb().varint(1, call.requestID)
564
+ if (call.execID) msg.str(15, call.execID)
565
+ msg.bytes(11, prepared.encoded[i])
566
+ this.resultSubmitted = true
567
+ this.send(this.wire.pb().bytes(2, msg).done())
568
+ call.submitted = true
569
+ this.execClose(call.requestID)
570
+ }
571
+ this.state = "running"
572
+ } catch (e) {
573
+ const error = failureOf(e)
574
+ this.fail({ status: error.status, message: `${error.message}; native result submission failed; its outcome is uncertain and cannot be replayed` })
575
+ }
576
+ return this.response(next)
577
+ }
578
+ response(segment = this.segment, replay = false) {
579
+ return {
580
+ events: segment.iterator({ replay }),
581
+ cancel: () => replay ? Promise.resolve() : this.cancel("the Chat response was cancelled"),
582
+ resolution: this.resolution,
583
+ delivered: () => replay || this.delivered(segment),
584
+ diagnostic: `same-run; bootstrap=imported-history; new-turn=stateless; missing-owner=stateless${replay ? "; replay=true" : ""}`,
585
+ }
586
+ }
587
+ acceptCall(call) {
588
+ if (!Number.isInteger(call.requestID)) invalid("native tool call is missing its exec request ID", 502)
589
+ const prior = this.execs.get(call.requestID)
590
+ const digest = hash(canonical({ requestID: call.requestID, execID: call.execID, nativeID: call.nativeID, name: call.name, args: argsOf(call.args) }))
591
+ if (prior) {
592
+ if (prior.digest !== digest) invalid("native exec identity changed during retransmission", 502)
593
+ return
594
+ }
595
+ // A retransmission of the final exec can arrive after it sealed its batch.
596
+ // Validate that identity first; only a new exec violates the sealed phase.
597
+ if (this.terminal || this.state === "awaiting-results") invalid("native call arrived after its batch was sealed", 502)
598
+ if (call.nativeID && this.nativeCalls.has(call.nativeID)) invalid("native tool-call ID was reused for another exec", 502)
599
+ call.id = `call_cursor_${this.identity}_${this.segments}_${this.segment.calls.length}`
600
+ call.digest = digest
601
+ this.execs.set(call.requestID, call)
602
+ if (call.nativeID) this.nativeCalls.set(call.nativeID, call)
603
+ this.segment.calls.push(call)
604
+ this.retain(bytes(call))
605
+ this.said += call.args.length
606
+ this.waitForBatch()
607
+ this.sealBatch()
608
+ }
609
+ waitForBatch() {
610
+ this.batchTimer ??= later(() => {
611
+ if (this.segment.expected == null && this.segment.calls.length && !this.resultSubmitted) this.missingCount()
612
+ else this.fail({ status: 502, message: "Cursor did not complete its declared native tool batch" })
613
+ }, this.limits.batchWait)
614
+ }
615
+ missingCount() {
616
+ this.fail({ status: 502, message: "Cursor did not declare its native tool batch", retentionIneligible: !this.resultSubmitted })
617
+ }
618
+ listed(count) {
619
+ if (!Number.isSafeInteger(count) || count < 1) invalid("invalid native tool count", 502)
620
+ if (this.segment.expected != null && this.segment.expected !== count) invalid("inconsistent native tool count", 502)
621
+ this.segment.expected = count
622
+ if (this.state === "awaiting-results") return // an identical retransmission
623
+ this.waitForBatch()
624
+ this.sealBatch()
625
+ }
626
+ sealBatch() {
627
+ const segment = this.segment
628
+ if (segment.expected == null) return
629
+ if (segment.calls.length > segment.expected) invalid("native tool batch exceeded its declared count", 502)
630
+ if (segment.calls.length !== segment.expected || segment.closed) return
631
+ clearTimeout(this.batchTimer)
632
+ this.batchTimer = null
633
+ this.state = "awaiting-results"
634
+ this.batches ??= []
635
+ this.batches.push(segment)
636
+ for (const c of segment.calls) this.pool.handles.set(c.id, { owner: this, batch: segment })
637
+ segment.finish({ stop: "tool_calls", usage: {
638
+ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0,
639
+ prompt_tokens_details: { cached_tokens: 0, cache_write_tokens: 0 },
640
+ magpie_native: { accounting: "pending" },
641
+ } })
642
+ this.segments++
643
+ this.resultTimer = later(() => this.fail({ status: 410, message: "native tool-result deadline expired" }, "expired"), this.limits.resultWait)
644
+ }
645
+ store(id, data) {
646
+ if (!id.length) invalid("native blob ID is empty", 502)
647
+ const key = id.toString("hex")
648
+ if (this.knownHashes.has(key) && hash(data) !== key) invalid("native content-addressed bootstrap blob changed", 502)
649
+ const size = this.blobSize - (this.blobs.get(key)?.length ?? 0) + data.length
650
+ if (size + (this.checkpoint?.bytes.length ?? 0) > this.limits.blobBytes) invalid("native blob retention limit reached", 503)
651
+ // Incoming IDs are opaque: a fixed-root ID need not be SHA-256(data).
652
+ this.blobs.set(key, Buffer.from(data))
653
+ this.blobSize = size
654
+ this.validateCheckpoint()
655
+ }
656
+ checkpointReceived(data) {
657
+ if (data.length + this.blobSize > this.limits.blobBytes) invalid("native checkpoint retention limit reached", 503)
658
+ nativeFields(data)
659
+ this.checkpoint = { bytes: Buffer.from(data), sequence: ++this.sequence, status: "incomplete", terminal: this.terminal }
660
+ this.validateCheckpoint()
661
+ }
662
+ validateCheckpoint() {
663
+ if (!this.checkpoint) return
664
+ const state = nativeFields(this.checkpoint.bytes)
665
+ const missing = new Set()
666
+ let supported = true
667
+ const get = (id) => {
668
+ const key = id.toString("hex")
669
+ const b = this.blobs.get(key)
670
+ if (!b) missing.add(key)
671
+ return b
672
+ }
673
+ const refs = new Set([1, 3, 6, 7, 11, 13])
674
+ const known = new Set(Array.from({ length: 34 }, (_, i) => i + 1).filter((n) => n !== 2))
675
+ // These shapes contain additional state/reference contracts which are not
676
+ // implemented by the caller-MCP bridge. Preserve their bytes without
677
+ // claiming reference closure or restart eligibility.
678
+ const complex = new Set([3, 6, 7, 11, 12, 13, 15, 16, 20, 24, 29, 30, 31, 32])
679
+ for (const f of state) {
680
+ if (!known.has(f.num) || complex.has(f.num)) supported = false
681
+ if (refs.has(f.num) && f.data.length) get(f.data)
682
+ if (f.num !== 8) continue
683
+ const turn = get(f.data)
684
+ if (!turn) continue
685
+ const tf = nativeFields(turn)
686
+ const agent = field(tf, 1)
687
+ if (!agent || tf.some((f) => f.num !== 1)) { supported = false; continue }
688
+ const agentFields = nativeFields(agent.data)
689
+ if (!field(agentFields, 1)?.data.length) supported = false
690
+ for (const a of agentFields) {
691
+ if (a.num === 1) {
692
+ const user = get(a.data)
693
+ if (user) nativeFields(user)
694
+ } else if (a.num === 2) {
695
+ const step = get(a.data)
696
+ if (step) {
697
+ const sf = nativeFields(step)
698
+ if (sf.length !== 1 || ![1, 2, 3].includes(sf[0].num)) supported = false
699
+ else {
700
+ const stepFields = nativeFields(sf[0].data)
701
+ if (sf[0].num === 2 && (stepFields.length !== 1 || stepFields[0].num !== 15)) supported = false
702
+ }
703
+ }
704
+ } else if (![3, 4, 5, 6].includes(a.num)) supported = false
705
+ }
706
+ }
707
+ this.checkpoint.missing = missing.size
708
+ this.checkpoint.status = !supported ? "unsupported" : missing.size ? "incomplete" : "complete"
709
+ this.checkpoint.terminal = this.terminal
710
+ }
711
+ terminalSeen(fields) {
712
+ if (this.segment.calls.length) invalid("Cursor ended a turn with unresolved native tool calls", 502)
713
+ this.terminal = true
714
+ this.state = "finalizing"
715
+ this.usageFields = fields
716
+ if (this.checkpoint) this.checkpoint.terminal = true
717
+ clearTimeout(this.batchTimer)
718
+ this.drainTimer = later(() => {
719
+ Promise.resolve().then(() => this.complete()).catch((e) => this.fail(failureOf(e)))
720
+ }, this.limits.drain)
721
+ }
722
+ complete() {
723
+ if (this.completionTask) return this.completionTask
724
+ if (this.error || this.state === "complete") return
725
+ if (!this.terminal) invalid("native Run ended before TurnEnded or a complete final answer", 502)
726
+ const { usageOf } = this.wire
727
+ const measuredInput = this.usageFields?.some((f) => [1, 3, 4].includes(f.num))
728
+ const measuredOutput = this.usageFields?.some((f) => f.num === 2)
729
+ const u = usageOf(this.usageFields ?? [])
730
+ if (!measuredInput) u.input = this.estimate
731
+ if (!measuredOutput) u.output = Math.floor((this.said + 3) / 4)
732
+ const prompt = u.input + u.cacheRead + u.cacheWrite
733
+ this.finalUsage = {
734
+ prompt_tokens: prompt, completion_tokens: u.output, total_tokens: prompt + u.output,
735
+ prompt_tokens_details: { cached_tokens: u.cacheRead, cache_write_tokens: u.cacheWrite },
736
+ ...(this.usageFields?.some((f) => f.num === 5) ? { completion_tokens_details: { reasoning_tokens: u.reasoning } } : {}),
737
+ ...(!measuredInput || !measuredOutput ? { magpie_native: { accounting: "estimated" } } : {}),
738
+ }
739
+ this.state = "finalizing"
740
+ this.closeTransport()
741
+ const finish = (more) => {
742
+ if (this.error) return
743
+ const deferred = more && more.input + more.output + more.cacheRead + more.cacheWrite > 0
744
+ this.segment.finish({ stop: "stop", usage: {
745
+ ...this.finalUsage,
746
+ ...(deferred ? { magpie_deferred: {
747
+ prompt_tokens: more.input + more.cacheRead + more.cacheWrite,
748
+ completion_tokens: more.output,
749
+ cached_tokens: more.cacheRead,
750
+ cache_write_tokens: more.cacheWrite,
751
+ } } : {}),
752
+ } })
753
+ this.state = "complete"
754
+ this.pool.release(this)
755
+ this.replayTimer = later(() => this.pool.forget(this), this.limits.replayWait)
756
+ }
757
+ if (!this.accounting) return finish(null)
758
+ this.completionTask = this.accounting.settle().then(finish)
759
+ return this.completionTask
760
+ }
761
+ async pump(stream) {
762
+ const { pb, exec, failure } = this.wire
763
+ try {
764
+ for await (const f of stream) {
765
+ if (this.error || this.state === "complete") return
766
+ if (f.end) {
767
+ const e = failure(200, f.data)
768
+ if (e.status < 200 || e.status > 299) throw new NativeSessionError(e.status, e.message)
769
+ if (this.segment.calls.length && this.segment.expected == null) { this.missingCount(); return }
770
+ if (!this.terminal && !this.segment.calls.length && this.segment.content) this.terminalSeen([])
771
+ await this.complete()
772
+ return
773
+ }
774
+ for (const m of nativeFields(f.data)) {
775
+ switch (m.num) {
776
+ case 1:
777
+ for (const u of nativeFields(m.data)) {
778
+ const uf = nativeFields(u.data)
779
+ if (u.num === 1 || u.num === 4) {
780
+ const text = str(uf, 1)
781
+ if (this.terminal || this.state === "awaiting-results") invalid("native output arrived outside a running Chat segment", 502)
782
+ if (text) {
783
+ this.said += text.length
784
+ if (u.num === 1) this.wrote += text.length
785
+ this.segment.emit(u.num === 1 ? { text } : { reasoning: text })
786
+ }
787
+ } else if (u.num === 14) {
788
+ if (!this.segment.content && !this.segment.calls.length) invalid("an empty reply (Cursor ended the turn without text or a tool call)", 502)
789
+ this.terminalSeen(uf)
790
+ } else if (u.num === 27) this.listed(num(uf, 1))
791
+ }
792
+ break
793
+ case 2: {
794
+ const c = exec(m.data, this.chatTools, (b) => this.send(b), (id) => this.execClose(id), null, true)
795
+ if (c) this.acceptCall(c)
796
+ break
797
+ }
798
+ case 3:
799
+ this.checkpointReceived(m.data)
800
+ break
801
+ case 4: {
802
+ const kv = nativeFields(m.data)
803
+ const id = num(kv, 1)
804
+ for (const k of kv) {
805
+ if (k.num === 2) {
806
+ const want = field(nativeFields(k.data), 1)?.data.toString("hex") ?? ""
807
+ const b = this.blobs.get(want)
808
+ const res = b ? pb().bytes(1, b) : pb().bytes(2, pb().str(1, "blob not found"))
809
+ this.send(pb().bytes(3, pb().varint(1, id).bytes(2, res)).done())
810
+ } else if (k.num === 3) {
811
+ try {
812
+ const set = nativeFields(k.data)
813
+ this.store(field(set, 1)?.data ?? Buffer.alloc(0), field(set, 2)?.data ?? Buffer.alloc(0))
814
+ } catch (e) {
815
+ 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
+ throw e
817
+ }
818
+ this.send(pb().bytes(3, pb().varint(1, id).bytes(3, Buffer.alloc(0))).done())
819
+ }
820
+ }
821
+ break
822
+ }
823
+ case 5:
824
+ for (const c of nativeFields(m.data)) if (c.num === 1) {
825
+ const id = num(nativeFields(c.data), 1)
826
+ const pending = this.execs.get(id)
827
+ if (pending && !pending.submitted) invalid("Cursor cancelled a pending native tool exec; late results cannot be submitted", 410)
828
+ }
829
+ break
830
+ case 7:
831
+ invalid("unsupported native interaction query", 502)
832
+ }
833
+ }
834
+ }
835
+ if (!this.error && this.state !== "complete") {
836
+ if (this.terminal) await this.complete()
837
+ else invalid("native reply broke off before completing its turn or tool batch: EOF", 502)
838
+ }
839
+ } catch (e) {
840
+ if (!this.error && this.state !== "complete") this.fail(failureOf(e))
841
+ }
842
+ }
843
+ }
844
+
845
+ export const nativeSessions = new NativeSessionPool()