@magpie-community/opencode-cursor-auth 0.2.7 → 0.2.8

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.
Files changed (2) hide show
  1. package/index.mjs +40 -4
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -1896,6 +1896,12 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
1896
1896
  let said = 0
1897
1897
  // wrote: the reply's text alone; a turn of thinking only says nothing
1898
1898
  let wrote = 0
1899
+ // what the Run sent, for an empty reply to name (#60): the thinking's
1900
+ // length, Cursor's own tools it asked for and was refused, and updates
1901
+ // and messages this doesn't read
1902
+ const seen = { thinking: 0, refused: new Map(), updates: new Map(), messages: new Map() }
1903
+ const count = (m, k) => m.set(k, (m.get(k) ?? 0) + 1)
1904
+ const empty = (how) => ({ error: { status: 502, message: "an empty reply" + emptyWhy(how, seen, ended ? usage : null) } })
1899
1905
  let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0 }
1900
1906
  // counted: the step's usage is the dashboard's events, its own counted
1901
1907
  // with it or by a later step (ledger), never guessed
@@ -1941,7 +1947,7 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
1941
1947
  if (f.end) {
1942
1948
  const e = failure(200, f.data)
1943
1949
  if (e.status < 200 || e.status > 299) yield { error: e }
1944
- else if (wrote === 0 && calls === 0) yield { error: { status: 502, message: "an empty reply" } }
1950
+ else if (wrote === 0 && calls === 0) yield empty(endFrame(f.data))
1945
1951
  else yield await settled()
1946
1952
  return
1947
1953
  }
@@ -1966,6 +1972,7 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
1966
1972
  const t = pbStr(uf, 1)
1967
1973
  if (t) {
1968
1974
  said += t.length
1975
+ seen.thinking += t.length
1969
1976
  yield { reasoning: t }
1970
1977
  }
1971
1978
  break
@@ -1976,7 +1983,7 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
1976
1983
  if (calls === 0) {
1977
1984
  // nothing written and nothing called: an empty reply,
1978
1985
  // which the client asks again, not a finished turn
1979
- yield wrote === 0 ? { error: { status: 502, message: "an empty reply" } } : await settled()
1986
+ yield wrote === 0 ? empty("Cursor ended the turn") : await settled()
1980
1987
  return
1981
1988
  }
1982
1989
  break
@@ -1987,12 +1994,14 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
1987
1994
  return
1988
1995
  }
1989
1996
  break
1997
+ default:
1998
+ count(seen.updates, u.num)
1990
1999
  }
1991
2000
  }
1992
2001
  break
1993
2002
  case 2: {
1994
2003
  // exec_server_message: the server asks the client to run something
1995
- const call = exec(m.data, tools, send, closeExec)
2004
+ const call = exec(m.data, tools, send, closeExec, seen.refused)
1996
2005
  if (call) {
1997
2006
  said += call.args.length
1998
2007
  yield { tool: { index: calls++, ...call } }
@@ -2021,6 +2030,8 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
2021
2030
  }
2022
2031
  break
2023
2032
  }
2033
+ default:
2034
+ count(seen.messages, m.num)
2024
2035
  }
2025
2036
  }
2026
2037
  }
@@ -2038,10 +2049,33 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
2038
2049
  }
2039
2050
  }
2040
2051
 
2052
+ // endFrame is how a Run's Connect end frame that said no error reads in
2053
+ // an empty reply's words: its JSON, cut short, or that it was empty.
2054
+ function endFrame(data) {
2055
+ const t = Buffer.from(data ?? []).toString("utf8").trim()
2056
+ if (!t || t === "{}") return "the Run's stream ended (an empty end frame)"
2057
+ return `the Run's stream ended (end frame: ${t.length > 300 ? t.slice(0, 300) + "…" : t})`
2058
+ }
2059
+
2060
+ // emptyWhy is what an empty reply says of what came before it (#60): how
2061
+ // it ended, the thinking, the turn's own count of output, Cursor's own
2062
+ // tools refused, and what Cursor sent that isn't read. Field numbers are
2063
+ // agent.v1's, so a report names the message that wasn't understood.
2064
+ function emptyWhy(how, seen, usage) {
2065
+ const list = (m) => [...m].map(([k, n]) => (n > 1 ? `#${k} ×${n}` : `#${k}`)).join(", ")
2066
+ const parts = [how]
2067
+ parts.push(seen.thinking ? `after ${seen.thinking} chars of thinking, no text and no tool call` : "with no thinking, no text and no tool call")
2068
+ if (usage) parts.push(`Cursor counted ${usage.output} output tokens`)
2069
+ if (seen.refused.size) parts.push(`Cursor's own tools asked for and refused: ${list(seen.refused)}`)
2070
+ if (seen.updates.size) parts.push(`updates not read: ${list(seen.updates)}`)
2071
+ if (seen.messages.size) parts.push(`messages not read: ${list(seen.messages)}`)
2072
+ return ` (${parts.join("; ")}; try again)`
2073
+ }
2074
+
2041
2075
  // exec answers what the server asks the client to run: a call of the
2042
2076
  // caller's tools is returned, the list of them is given, and anything else
2043
2077
  // is refused.
2044
- function exec(msg, tools, send, closeExec) {
2078
+ function exec(msg, tools, send, closeExec, refused) {
2045
2079
  const es = fields(msg)
2046
2080
  const id = pbNum(es, 1)
2047
2081
  const execID = pbStr(es, 15)
@@ -2080,6 +2114,8 @@ function exec(msg, tools, send, closeExec) {
2080
2114
  }
2081
2115
  }
2082
2116
  // a shell, a file read or an edit of Cursor's own: never run
2117
+ const kind = es.find((e) => e.num !== 1 && e.num !== 15)?.num ?? 0
2118
+ refused?.set(kind, (refused.get(kind) ?? 0) + 1)
2083
2119
  send(pb().bytes(5, pb().bytes(2, pb().varint(1, id).str(2, "not available"))).done())
2084
2120
  closeExec(id)
2085
2121
  return null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-cursor-auth",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
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",