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

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 +47 -8
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -723,17 +723,20 @@ function pick(entry, effort, fast, seed) {
723
723
  // without thinking stops at high), the variant with those parameters at the
724
724
  // nearest effort it has, not the default's id with parameters that
725
725
  // contradict it (claude-opus-5-thinking-high with thinking=false). Where
726
- // the default differs from what is asked in the effort alone, the effort
727
- // goes as asked, as for a listed model.
726
+ // the default differs from what is asked in the effort alone, and no
727
+ // variant at all is named for that effort, the effort goes as asked, as for
728
+ // a listed model (claude-opus-5-5-xhigh). When another branch has it, the
729
+ // default's own branch stops short there, and its nearest goes (#55).
728
730
  function branch(entry, want) {
729
731
  if (!entry.pid || entry.pid === "thinking") return undefined
732
+ const level = (x) => (x === "extra-high" ? "xhigh" : x)
730
733
  const def = valuesOf(entry.def)
731
- if (Object.keys(want).every((k) => k === entry.pid || k === "context" || def[k] === undefined || def[k] === want[k])) return undefined
734
+ const named = entry.variants.some((v) => level(valuesOf(v)[entry.pid]) === level(want[entry.pid]))
735
+ if (!named && Object.keys(want).every((k) => k === entry.pid || k === "context" || def[k] === undefined || def[k] === want[k])) return undefined
732
736
  const vs = entry.variants.filter((v) => {
733
737
  const have = valuesOf(v)
734
738
  return entry.pid in have && Object.keys(want).every((k) => k === entry.pid || have[k] === undefined || have[k] === want[k])
735
739
  })
736
- const level = (x) => (x === "extra-high" ? "xhigh" : x)
737
740
  const at = fitEffort(level(want[entry.pid]), vs.map((v) => level(valuesOf(v)[entry.pid])))
738
741
  return at ? vs.find((v) => level(valuesOf(v)[entry.pid]) === at) : undefined
739
742
  }
@@ -1896,6 +1899,12 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
1896
1899
  let said = 0
1897
1900
  // wrote: the reply's text alone; a turn of thinking only says nothing
1898
1901
  let wrote = 0
1902
+ // what the Run sent, for an empty reply to name (#60): the thinking's
1903
+ // length, Cursor's own tools it asked for and was refused, and updates
1904
+ // and messages this doesn't read
1905
+ const seen = { thinking: 0, refused: new Map(), updates: new Map(), messages: new Map() }
1906
+ const count = (m, k) => m.set(k, (m.get(k) ?? 0) + 1)
1907
+ const empty = (how) => ({ error: { status: 502, message: "an empty reply" + emptyWhy(how, seen, ended ? usage : null) } })
1899
1908
  let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0 }
1900
1909
  // counted: the step's usage is the dashboard's events, its own counted
1901
1910
  // with it or by a later step (ledger), never guessed
@@ -1941,7 +1950,7 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
1941
1950
  if (f.end) {
1942
1951
  const e = failure(200, f.data)
1943
1952
  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" } }
1953
+ else if (wrote === 0 && calls === 0) yield empty(endFrame(f.data))
1945
1954
  else yield await settled()
1946
1955
  return
1947
1956
  }
@@ -1966,6 +1975,7 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
1966
1975
  const t = pbStr(uf, 1)
1967
1976
  if (t) {
1968
1977
  said += t.length
1978
+ seen.thinking += t.length
1969
1979
  yield { reasoning: t }
1970
1980
  }
1971
1981
  break
@@ -1976,7 +1986,7 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
1976
1986
  if (calls === 0) {
1977
1987
  // nothing written and nothing called: an empty reply,
1978
1988
  // which the client asks again, not a finished turn
1979
- yield wrote === 0 ? { error: { status: 502, message: "an empty reply" } } : await settled()
1989
+ yield wrote === 0 ? empty("Cursor ended the turn") : await settled()
1980
1990
  return
1981
1991
  }
1982
1992
  break
@@ -1987,12 +1997,14 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
1987
1997
  return
1988
1998
  }
1989
1999
  break
2000
+ default:
2001
+ count(seen.updates, u.num)
1990
2002
  }
1991
2003
  }
1992
2004
  break
1993
2005
  case 2: {
1994
2006
  // exec_server_message: the server asks the client to run something
1995
- const call = exec(m.data, tools, send, closeExec)
2007
+ const call = exec(m.data, tools, send, closeExec, seen.refused)
1996
2008
  if (call) {
1997
2009
  said += call.args.length
1998
2010
  yield { tool: { index: calls++, ...call } }
@@ -2021,6 +2033,8 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
2021
2033
  }
2022
2034
  break
2023
2035
  }
2036
+ default:
2037
+ count(seen.messages, m.num)
2024
2038
  }
2025
2039
  }
2026
2040
  }
@@ -2038,10 +2052,33 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage,
2038
2052
  }
2039
2053
  }
2040
2054
 
2055
+ // endFrame is how a Run's Connect end frame that said no error reads in
2056
+ // an empty reply's words: its JSON, cut short, or that it was empty.
2057
+ function endFrame(data) {
2058
+ const t = Buffer.from(data ?? []).toString("utf8").trim()
2059
+ if (!t || t === "{}") return "the Run's stream ended (an empty end frame)"
2060
+ return `the Run's stream ended (end frame: ${t.length > 300 ? t.slice(0, 300) + "…" : t})`
2061
+ }
2062
+
2063
+ // emptyWhy is what an empty reply says of what came before it (#60): how
2064
+ // it ended, the thinking, the turn's own count of output, Cursor's own
2065
+ // tools refused, and what Cursor sent that isn't read. Field numbers are
2066
+ // agent.v1's, so a report names the message that wasn't understood.
2067
+ function emptyWhy(how, seen, usage) {
2068
+ const list = (m) => [...m].map(([k, n]) => (n > 1 ? `#${k} ×${n}` : `#${k}`)).join(", ")
2069
+ const parts = [how]
2070
+ 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")
2071
+ if (usage) parts.push(`Cursor counted ${usage.output} output tokens`)
2072
+ if (seen.refused.size) parts.push(`Cursor's own tools asked for and refused: ${list(seen.refused)}`)
2073
+ if (seen.updates.size) parts.push(`updates not read: ${list(seen.updates)}`)
2074
+ if (seen.messages.size) parts.push(`messages not read: ${list(seen.messages)}`)
2075
+ return ` (${parts.join("; ")}; try again)`
2076
+ }
2077
+
2041
2078
  // exec answers what the server asks the client to run: a call of the
2042
2079
  // caller's tools is returned, the list of them is given, and anything else
2043
2080
  // is refused.
2044
- function exec(msg, tools, send, closeExec) {
2081
+ function exec(msg, tools, send, closeExec, refused) {
2045
2082
  const es = fields(msg)
2046
2083
  const id = pbNum(es, 1)
2047
2084
  const execID = pbStr(es, 15)
@@ -2080,6 +2117,8 @@ function exec(msg, tools, send, closeExec) {
2080
2117
  }
2081
2118
  }
2082
2119
  // a shell, a file read or an edit of Cursor's own: never run
2120
+ const kind = es.find((e) => e.num !== 1 && e.num !== 15)?.num ?? 0
2121
+ refused?.set(kind, (refused.get(kind) ?? 0) + 1)
2083
2122
  send(pb().bytes(5, pb().bytes(2, pb().varint(1, id).str(2, "not available"))).done())
2084
2123
  closeExec(id)
2085
2124
  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.9",
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",