@magpie-community/opencode-cursor-auth 0.2.18 → 0.2.20

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
@@ -1179,6 +1179,19 @@ function toolsOf(chat) {
1179
1179
  })
1180
1180
  }
1181
1181
 
1182
+ // NS is the MCP server the caller's tools are served as, the way Cursor's
1183
+ // own client names a user's server by its mcp.json label (providerIdentifier,
1184
+ // with "<label>-<tool>" as a call's name). It goes to Cursor in the prompt,
1185
+ // each call and each tool definition, so it names nothing of the plugin or
1186
+ // the app it runs in. Calls are read back under NAMESPACES: before 0.2.20
1187
+ // the server was named "magpie", and a session begun then can still call it.
1188
+ const NS = "tools"
1189
+ const NAMESPACES = [NS, "magpie"]
1190
+ const bareName = (s) => {
1191
+ for (const ns of NAMESPACES) if (s.startsWith(ns + "-")) return s.slice(ns.length + 1)
1192
+ return s
1193
+ }
1194
+
1182
1195
  // catalog lists the caller's tools for the model. It sees MCP tools only
1183
1196
  // through Cursor's GetDynamicTools and CallDynamicTool, and Cursor names
1184
1197
  // them in a prompt of its own, which this conversation goes without:
@@ -1188,8 +1201,8 @@ function toolsOf(chat) {
1188
1201
  // those calls went nowhere (#11).
1189
1202
  function catalog(tools) {
1190
1203
  if (!tools.length) return ""
1191
- let s = `\n\n<dynamic_tool_catalog>\nThe MCP namespace "magpie" has the tools below. They are reached only through \`${CALL}\`, never as tools of their own. Each entry is the exact call to make; its arguments must match the schema given. The schemas are complete, so there is no need to call \`GetDynamicTools\` first.\n`
1192
- for (const t of tools) s += `<call>${CALL}({"namespace":"magpie","toolName":"${t.name}","arguments":{...}})\n${t.description}\narguments schema: ${t.schemaText}\n</call>\n`
1204
+ let s = `\n\n<dynamic_tool_catalog>\nThe MCP namespace "${NS}" has the tools below. They are reached only through \`${CALL}\`, never as tools of their own. Each entry is the exact call to make; its arguments must match the schema given. The schemas are complete, so there is no need to call \`GetDynamicTools\` first.\n`
1205
+ for (const t of tools) s += `<call>${CALL}({"namespace":"${NS}","toolName":"${t.name}","arguments":{...}})\n${t.description}\narguments schema: ${t.schemaText}\n</call>\n`
1193
1206
  return s + "</dynamic_tool_catalog>"
1194
1207
  }
1195
1208
 
@@ -1263,7 +1276,7 @@ function conversation(chat, tools) {
1263
1276
  if (c?.type && c.type !== "function") continue
1264
1277
  const args = parseArgs(c.function?.arguments)
1265
1278
  size += (c.function?.arguments ?? "").length + (c.function?.name ?? "").length
1266
- content.push({ type: "tool-call", toolCallId: callID(c.id), toolName: CALL, args: { namespace: "magpie", toolName: c.function?.name, arguments: args } })
1279
+ content.push({ type: "tool-call", toolCallId: callID(c.id), toolName: CALL, args: { namespace: NS, toolName: c.function?.name, arguments: args } })
1267
1280
  pending.push(c.id)
1268
1281
  }
1269
1282
  if (content.length) add({ role: "assistant", content })
@@ -1303,7 +1316,7 @@ function toolDef(t) {
1303
1316
  const d = pb().str(1, t.name)
1304
1317
  if (t.description) d.str(2, t.description)
1305
1318
  d.bytes(3, pbValue(t.schema))
1306
- return d.str(4, "magpie").str(5, t.name).str(6, t.schemaText).done()
1319
+ return d.str(4, NS).str(5, t.name).str(6, t.schemaText).done()
1307
1320
  }
1308
1321
 
1309
1322
  const env = () => pb().str(1, GOOS).str(2, tmpdir()).str(10, "UTC")
@@ -2527,13 +2540,13 @@ function exec(msg, tools, send, closeExec, refused, native = false) {
2527
2540
  if (native) for (const e of es) if (e.num === 11) {
2528
2541
  if (e.wire !== 2) throw new NativeSessionError(502, "invalid native MCP argument envelope")
2529
2542
  const { fields: a, args } = nativeMcpArgs(e.data)
2530
- const name = pbStr(a, 5) || pbStr(a, 1).replace(/^magpie-/, "")
2543
+ const name = pbStr(a, 5) || bareName(pbStr(a, 1))
2531
2544
  const provider = pbStr(a, 4)
2532
2545
  const server = pbStr(a, 9)
2533
- if (provider && provider !== "magpie" || server && server !== "magpie") nativeUnavailable = true
2546
+ if (provider && !NAMESPACES.includes(provider) || server && !NAMESPACES.includes(server)) nativeUnavailable = true
2534
2547
  if (pbNum(a, 7) || field(a, 6)) nativeUnavailable = true
2535
2548
  if (!tools.some((t) => t.name === name)) nativeUnavailable = true
2536
- if (!pbStr(a, 5) && pbStr(a, 1) !== "magpie-" + name) nativeUnavailable = true
2549
+ if (!pbStr(a, 5) && !NAMESPACES.some((ns) => pbStr(a, 1) === ns + "-" + name)) nativeUnavailable = true
2537
2550
  const call = { requestID: id, execID, nativeID: pbStr(a, 3), name, args }
2538
2551
  if (nativeCall && !isDeepStrictEqual(nativeCall, call)) throw new NativeSessionError(502, "ambiguous repeated native MCP argument envelopes")
2539
2552
  nativeCall ??= call
@@ -2565,7 +2578,7 @@ function exec(msg, tools, send, closeExec, refused, native = false) {
2565
2578
  const v = field(ef, 2)
2566
2579
  args[pbStr(ef, 1)] = v ? pbAny(v.data) : null
2567
2580
  }
2568
- const name = pbStr(a, 5) || pbStr(a, 1).replace(/^magpie-/, "")
2581
+ const name = pbStr(a, 5) || bareName(pbStr(a, 1))
2569
2582
  // an OpenAI model's id is its call's and its item's, a line apart,
2570
2583
  // which no caller would take as an id: the line goes as __
2571
2584
  const cid = pbStr(a, 3).replaceAll("\n", "__") || "call_" + randomBytes(12).toString("hex")
@@ -2573,7 +2586,7 @@ function exec(msg, tools, send, closeExec, refused, native = false) {
2573
2586
  }
2574
2587
  case 36: {
2575
2588
  // mcp_state_exec_args: the tools there are
2576
- const srv = pb().str(1, "magpie").str(2, "magpie").str(7, "connected")
2589
+ const srv = pb().str(1, NS).str(2, NS).str(7, "connected")
2577
2590
  for (const t of tools) srv.bytes(5, toolDef(t))
2578
2591
  answer(36, pb().bytes(1, pb().bytes(1, srv)))
2579
2592
  return null
@@ -2656,12 +2669,12 @@ async function answerOf(auth, chat, signal, session = "") {
2656
2669
  let response = native ? await runNative({ ...args, native }) : await runOnce(args)
2657
2670
  const rebuild = async () => {
2658
2671
  const fallback = await runOnce(args)
2659
- fallback.diagnostic = "stateless; bootstrap=imported-history; reason=missing-batch-count"
2672
+ fallback.diagnostic = "stateless; bootstrap=imported-history; reason=" + (response.error?.continuityReason || "missing-batch-count")
2660
2673
  return fallback
2661
2674
  }
2662
- // A missing native batch count may be retried once, only before Chat
2663
- // output is exposed or any native result was submitted. Never repeat an
2664
- // uncertain tool result write or duplicate a progressive stream's output.
2675
+ // A missing or conflicting native batch count may be retried once,
2676
+ // only before Chat output is exposed or any native result was submitted.
2677
+ // Never repeat an uncertain tool write or progressive stream output.
2665
2678
  if (response.error?.retentionIneligible && !signal?.aborted) response = await rebuild()
2666
2679
  else if (native) response.rebuild = rebuild
2667
2680
  response.diagnostic ??= "stateless; bootstrap=imported-history; reason=" + (prepared.fallback || "retention capacity or reservation unavailable")
@@ -2719,8 +2732,10 @@ async function answerOf(auth, chat, signal, session = "") {
2719
2732
  for await (const e of events) {
2720
2733
  if (e.error) {
2721
2734
  if (!e.error.retentionIneligible || !r.rebuild || signal?.aborted) return errorResponse(e.error)
2735
+ const reason = e.error.continuityReason
2722
2736
  r = await r.rebuild()
2723
2737
  if (r.error) return errorResponse(r.error)
2738
+ if (reason) r.diagnostic = "stateless; bootstrap=imported-history; reason=" + reason
2724
2739
  events = r.events
2725
2740
  responseHeaders["X-Magpie-Cursor-Continuity"] = r.diagnostic
2726
2741
  retry = true
@@ -675,7 +675,13 @@ class NativeSession {
675
675
  }
676
676
  listed(count) {
677
677
  if (!Number.isSafeInteger(count) || count < 1) invalid("invalid native tool count", 502)
678
- if (this.segment.expected != null && this.segment.expected !== count) invalid("inconsistent native tool count", 502)
678
+ if (this.segment.expected != null && this.segment.expected !== count) {
679
+ // Conflicting counts leave the native step boundary ambiguous. Retire
680
+ // this owner; Chat may rebuild from history only if no native result
681
+ // was submitted to this Run.
682
+ this.fail({ status: 502, message: "inconsistent native tool count", retentionIneligible: !this.resultSubmitted, continuityReason: "inconsistent-batch-count" })
683
+ return
684
+ }
679
685
  this.segment.expected = count
680
686
  if (this.state === "awaiting-results") return // an identical retransmission
681
687
  this.waitForBatch()
@@ -876,7 +882,10 @@ class NativeSession {
876
882
  } else if (u.num === 14) {
877
883
  if (!this.segment.content && !this.segment.calls.length) invalid("an empty reply (Cursor ended the turn without text or a tool call)", 502)
878
884
  this.terminalSeen(uf)
879
- } else if (u.num === 27) this.listed(num(uf, 1))
885
+ } else if (u.num === 27) {
886
+ this.listed(num(uf, 1))
887
+ if (this.error) return
888
+ }
880
889
  }
881
890
  break
882
891
  case 2: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-cursor-auth",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
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",