@magpie-community/opencode-cursor-auth 0.1.9 → 0.1.11
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 +237 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,15 @@ plugin's `fetch` answers them on Cursor's agent API:
|
|
|
58
58
|
(Cursor tells it only once the tools' results come back in the same
|
|
59
59
|
Run), so once the Run is closed its usage is read from the dashboard's
|
|
60
60
|
usage events, by the Run's conversation id: it shows in about 2.5 s,
|
|
61
|
-
and is looked for up to 6 s
|
|
61
|
+
and is looked for up to 6 s. Far from Cursor it shows later than that
|
|
62
|
+
(yetone/magpie#1053), so the step says 0 used and the conversation owes
|
|
63
|
+
it: its next steps collect the event in the background and count it
|
|
64
|
+
with their own, and a step that ends the turn waits for what is still
|
|
65
|
+
owed, so the conversation adds up to what Cursor counted. An account
|
|
66
|
+
whose events miss the wait 3 steps in a row stops waiting for them.
|
|
67
|
+
With nothing naming the session (so no later step to collect it), or
|
|
68
|
+
an account that can't read its usage events, the step is estimated as
|
|
69
|
+
before.
|
|
62
70
|
Failures keep their statuses: 401 to sign in again, 429 at the
|
|
63
71
|
usage limit, 400 for a prompt too long, 403 for a region refusal.
|
|
64
72
|
|
package/index.mjs
CHANGED
|
@@ -954,6 +954,26 @@ function statusOf(status, code, msg) {
|
|
|
954
954
|
const textOf = (c) =>
|
|
955
955
|
typeof c === "string" ? c : Array.isArray(c) ? c.filter((p) => p?.type === "text").map((p) => p.text ?? "").join("") : ""
|
|
956
956
|
|
|
957
|
+
// joinSplitCalls joins an assistant message onto the assistant message
|
|
958
|
+
// before it when that one made tool calls: Anthropic takes consecutive
|
|
959
|
+
// assistant messages as one turn, and an agent can send a turn's parallel
|
|
960
|
+
// calls split over two of them, answered together after (yetone/magpie#1275).
|
|
961
|
+
// Answered at the second message, the first calls would be marked
|
|
962
|
+
// interrupted and their real results dropped. msgs is left as it is.
|
|
963
|
+
function joinSplitCalls(msgs) {
|
|
964
|
+
const out = []
|
|
965
|
+
for (const m of msgs) {
|
|
966
|
+
const prev = out.at(-1)
|
|
967
|
+
if (m?.role === "assistant" && prev?.role === "assistant" && prev.tool_calls?.length) {
|
|
968
|
+
const content = [textOf(prev.content), textOf(m.content)].filter(Boolean).join("\n\n")
|
|
969
|
+
out[out.length - 1] = { ...prev, content, tool_calls: [...prev.tool_calls, ...(m.tool_calls ?? [])] }
|
|
970
|
+
continue
|
|
971
|
+
}
|
|
972
|
+
out.push(m)
|
|
973
|
+
}
|
|
974
|
+
return out
|
|
975
|
+
}
|
|
976
|
+
|
|
957
977
|
function parseArgs(s) {
|
|
958
978
|
if (s && typeof s === "object") return s
|
|
959
979
|
try {
|
|
@@ -1035,7 +1055,7 @@ function conversation(chat, tools) {
|
|
|
1035
1055
|
if (results.length) add({ role: "tool", content: results })
|
|
1036
1056
|
results = []
|
|
1037
1057
|
}
|
|
1038
|
-
for (const m of chat.messages ?? []) {
|
|
1058
|
+
for (const m of joinSplitCalls(chat.messages ?? [])) {
|
|
1039
1059
|
if (m.role === "system" || m.role === "developer") continue
|
|
1040
1060
|
if (m.role === "tool") {
|
|
1041
1061
|
const id = m.tool_call_id ?? ""
|
|
@@ -1174,47 +1194,218 @@ function usageOf(uf) {
|
|
|
1174
1194
|
// was read from the cache and written to it, as TurnEndedUpdate does; one
|
|
1175
1195
|
// shows about 2.5 s after the Run is closed. STEP_WAIT is how long a step's
|
|
1176
1196
|
// is looked for: first after that long, then every so often until the
|
|
1177
|
-
// end
|
|
1178
|
-
|
|
1197
|
+
// end (each ask given `ask` to answer).
|
|
1198
|
+
//
|
|
1199
|
+
// Far from Cursor none shows in that time (yetone/magpie#1053: a round
|
|
1200
|
+
// trip of 4.2 s, the event 4–5 s after the Run closes), and waiting longer
|
|
1201
|
+
// would hold every tool step longer. So a tool step's Run is owed by its
|
|
1202
|
+
// conversation (owed): the step counts every event of the conversation's
|
|
1203
|
+
// owed Runs that has shown by the end of its wait, its own or earlier
|
|
1204
|
+
// ones, and what hasn't shown is collected by the conversation's next
|
|
1205
|
+
// steps, in the background from `owedAt` after the Run closed. A step
|
|
1206
|
+
// whose count is still owed says 0 used; the conversation's steps add up
|
|
1207
|
+
// to what Cursor counted, some of it a step or two late. A step that ends
|
|
1208
|
+
// the turn waits for what is still owed, as nothing may follow it soon.
|
|
1209
|
+
// After `skipAfter` steps in a row of an account's left something owed,
|
|
1210
|
+
// its steps stop waiting (but every `retryEvery`th), and leave it to the
|
|
1211
|
+
// next. A debt not collected in `owedFor` is let go. With no
|
|
1212
|
+
// conversation_id naming the session nothing later could collect a Run's
|
|
1213
|
+
// event: its step looks for it as before, and is guessed without it.
|
|
1214
|
+
const STEP_WAIT = { first: 1500, every: 500, until: 6000, ask: 5000, skipAfter: 3, retryEvery: 10, owedAt: 5000, owedFor: 600_000 }
|
|
1179
1215
|
|
|
1180
1216
|
// claimed are the usage events already counted, by conversation and time,
|
|
1181
1217
|
// so that two steps of one conversation never count the same one.
|
|
1182
1218
|
const claimed = new Map()
|
|
1183
1219
|
|
|
1184
|
-
//
|
|
1185
|
-
//
|
|
1186
|
-
|
|
1220
|
+
// owed are what each conversation still owes, by conversation_id: n Runs
|
|
1221
|
+
// whose events are to be counted, the earliest started at since, looked
|
|
1222
|
+
// for from ready on; and carry, a count collected that no step gave out.
|
|
1223
|
+
const owed = new Map()
|
|
1224
|
+
|
|
1225
|
+
// flights are each conversation's collections still going, which a step
|
|
1226
|
+
// that ends the turn waits for.
|
|
1227
|
+
const flights = new Map()
|
|
1228
|
+
|
|
1229
|
+
// unreadable are the accounts (by token) whose usage events Cursor won't
|
|
1230
|
+
// give (a 4xx), for owedFor: their steps are guessed, as before, and owe
|
|
1231
|
+
// nothing. misses are how many steps in a row of an account's left
|
|
1232
|
+
// something owed.
|
|
1233
|
+
const unreadable = new Map()
|
|
1234
|
+
const misses = new Map()
|
|
1235
|
+
const tokKey = (tok) => createHash("sha256").update(tok).digest("hex")
|
|
1236
|
+
function canRead(tok) {
|
|
1237
|
+
const at = unreadable.get(tokKey(tok))
|
|
1238
|
+
if (at === undefined) return true
|
|
1239
|
+
if (Date.now() - at < STEP_WAIT.owedFor) return false
|
|
1240
|
+
unreadable.delete(tokKey(tok))
|
|
1241
|
+
return true
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
const ZERO = () => ({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0 })
|
|
1245
|
+
function addUsage(a, b) {
|
|
1246
|
+
if (!b) return a
|
|
1247
|
+
if (!a) return { ...b }
|
|
1248
|
+
for (const k of Object.keys(a)) a[k] += b[k] ?? 0
|
|
1249
|
+
return a
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// owe adds to conversation conv's debt.
|
|
1253
|
+
function owe(conv, { since = Date.now(), n = 0, ready = Date.now(), carry = null }) {
|
|
1254
|
+
for (const [k, d] of owed) if (Date.now() - d.since > STEP_WAIT.owedFor) owed.delete(k)
|
|
1255
|
+
if (n <= 0 && !carry) return
|
|
1256
|
+
const d = owed.get(conv)
|
|
1257
|
+
if (!d) return void owed.set(conv, { since, n, ready, carry: carry && { ...carry } })
|
|
1258
|
+
d.since = Math.min(d.since, since)
|
|
1259
|
+
d.n += n
|
|
1260
|
+
d.ready = Math.min(d.ready, ready)
|
|
1261
|
+
d.carry = addUsage(d.carry, carry)
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// usageEvents are the dashboard's events of conversation conv from since
|
|
1265
|
+
// on, the `want` oldest not counted yet, summed: usage (null when none),
|
|
1266
|
+
// how many were found (got), and refused when the account can't read them.
|
|
1267
|
+
// Polling, they are looked for as STEP_WAIT says, from `first` on, until
|
|
1268
|
+
// all show; else asked for once.
|
|
1269
|
+
async function usageEvents(tok, conv, since, want, { poll = false, first = STEP_WAIT.first, signal } = {}) {
|
|
1187
1270
|
const end = Date.now() + STEP_WAIT.until
|
|
1188
1271
|
const wait = (ms) => new Promise((r) => {
|
|
1189
1272
|
const t = setTimeout(r, ms)
|
|
1190
1273
|
signal?.addEventListener("abort", () => (clearTimeout(t), r()), { once: true })
|
|
1191
1274
|
})
|
|
1192
|
-
for (const [k, at] of claimed) if (Date.now() - at >
|
|
1193
|
-
await wait(STEP_WAIT.first)
|
|
1275
|
+
for (const [k, at] of claimed) if (Date.now() - at > 2 * STEP_WAIT.owedFor) claimed.delete(k)
|
|
1194
1276
|
const num = (v) => (Number.isFinite(Number(v)) ? Number(v) : 0)
|
|
1277
|
+
let usage = null
|
|
1278
|
+
let got = 0
|
|
1279
|
+
if (poll) await wait(first)
|
|
1195
1280
|
while (!signal?.aborted) {
|
|
1196
1281
|
try {
|
|
1197
1282
|
const res = await fetch(API + "/aiserver.v1.DashboardService/GetFilteredUsageEvents", {
|
|
1198
1283
|
method: "POST",
|
|
1199
1284
|
headers: await headersFor(tok),
|
|
1200
1285
|
body: JSON.stringify({ teamId: 0, startDate: String(since - 60_000), endDate: String(Date.now() + 60_000), page: 1, pageSize: 50 }),
|
|
1201
|
-
signal: AbortSignal.timeout(
|
|
1286
|
+
signal: AbortSignal.timeout(STEP_WAIT.ask),
|
|
1202
1287
|
})
|
|
1203
|
-
if (res.status >= 400 && res.status < 500 && res.status !== 429)
|
|
1288
|
+
if (res.status >= 400 && res.status < 500 && res.status !== 429) {
|
|
1289
|
+
// not this account's to read
|
|
1290
|
+
unreadable.set(tokKey(tok), Date.now())
|
|
1291
|
+
return { usage, got, refused: true }
|
|
1292
|
+
}
|
|
1204
1293
|
const evs = res.ok ? ((await res.json())?.usageEventsDisplay ?? []) : []
|
|
1205
|
-
const
|
|
1294
|
+
const found = evs
|
|
1206
1295
|
.filter((e) => e?.conversationId === conv && e.tokenUsage && num(e.timestamp) >= since - 1000 && !claimed.has(conv + "\0" + e.timestamp))
|
|
1207
|
-
.sort((a, b) => num(a.timestamp) - num(b.timestamp))
|
|
1208
|
-
|
|
1296
|
+
.sort((a, b) => num(a.timestamp) - num(b.timestamp))
|
|
1297
|
+
.slice(0, want - got)
|
|
1298
|
+
for (const ev of found) {
|
|
1209
1299
|
claimed.set(conv + "\0" + ev.timestamp, Date.now())
|
|
1210
1300
|
const t = ev.tokenUsage
|
|
1211
|
-
|
|
1301
|
+
usage = addUsage(usage ?? ZERO(), { input: num(t.inputTokens), output: num(t.outputTokens), cacheRead: num(t.cacheReadTokens), cacheWrite: num(t.cacheWriteTokens) })
|
|
1302
|
+
got++
|
|
1212
1303
|
}
|
|
1304
|
+
if (got >= want) break
|
|
1213
1305
|
} catch {}
|
|
1214
|
-
if (Date.now() + STEP_WAIT.every > end)
|
|
1306
|
+
if (!poll || Date.now() + STEP_WAIT.every > end) break
|
|
1215
1307
|
await wait(STEP_WAIT.every)
|
|
1216
1308
|
}
|
|
1217
|
-
return
|
|
1309
|
+
return { usage, got, refused: false }
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// stepUsage is the usage of the Run of conversation conv started at since,
|
|
1313
|
+
// as the dashboard's usage events count it; null when none shows in time.
|
|
1314
|
+
async function stepUsage(tok, conv, since, signal) {
|
|
1315
|
+
return (await usageEvents(tok, conv, since, 1, { poll: true, signal })).usage
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// collect takes conversation conv's debt and asks for its events (polling
|
|
1319
|
+
// when poll): what was carried and what was found (usage), and whether
|
|
1320
|
+
// any is still owed (short), which is owed on. What comes back once the
|
|
1321
|
+
// Run that asked is over (over() true) is carried to the next instead.
|
|
1322
|
+
function collect(tok, conv, { poll = false, first, signal, over = () => false } = {}) {
|
|
1323
|
+
const d = owed.get(conv)
|
|
1324
|
+
if (!d) return Promise.resolve({ usage: null, short: false })
|
|
1325
|
+
owed.delete(conv)
|
|
1326
|
+
const p = (async () => {
|
|
1327
|
+
let r = { usage: null, got: 0 }
|
|
1328
|
+
if (d.n > 0 && canRead(tok)) r = await usageEvents(tok, conv, d.since, d.n, { poll, first, signal }).catch(() => r)
|
|
1329
|
+
const short = d.n > r.got && canRead(tok)
|
|
1330
|
+
if (short) owe(conv, { since: d.since, n: d.n - r.got, ready: d.ready })
|
|
1331
|
+
const usage = addUsage(d.carry, r.usage)
|
|
1332
|
+
if (!over()) return { usage, short }
|
|
1333
|
+
owe(conv, { carry: usage })
|
|
1334
|
+
return { usage: null, short }
|
|
1335
|
+
})()
|
|
1336
|
+
const set = flights.get(conv) ?? new Set()
|
|
1337
|
+
flights.set(conv, set.add(p))
|
|
1338
|
+
p.finally(() => {
|
|
1339
|
+
set.delete(p)
|
|
1340
|
+
if (!set.size && flights.get(conv) === set) flights.delete(conv)
|
|
1341
|
+
})
|
|
1342
|
+
return p
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// ledger is how a Run of conversation conv, started at since, has its
|
|
1346
|
+
// usage counted, for decode; kept when the conversation_id names the
|
|
1347
|
+
// session, so that a later Run of it can collect what this one owes. A
|
|
1348
|
+
// debt the conversation has is collected in the background. own() is a
|
|
1349
|
+
// step's count once it has called tools: its Run owed, what has shown of
|
|
1350
|
+
// the debt by the end of the wait (or at once, when the account's never
|
|
1351
|
+
// show in time); false when nothing has, null when the step is to be
|
|
1352
|
+
// guessed (not kept, and its event not found, or an account that can't
|
|
1353
|
+
// read its events). owed(final) is what the Run collected otherwise,
|
|
1354
|
+
// all that can be had when final. done() ends the Run: what was collected
|
|
1355
|
+
// and not given out goes back.
|
|
1356
|
+
function ledger(tok, conv, since, signal, kept) {
|
|
1357
|
+
const key = tokKey(tok)
|
|
1358
|
+
let pending = null // collected, not given out yet
|
|
1359
|
+
let over = false
|
|
1360
|
+
const gather = (opts) =>
|
|
1361
|
+
collect(tok, conv, { ...opts, over: () => over }).then((r) => {
|
|
1362
|
+
pending = addUsage(pending, r.usage)
|
|
1363
|
+
return r
|
|
1364
|
+
})
|
|
1365
|
+
let collecting = null
|
|
1366
|
+
const debt = kept ? owed.get(conv) : undefined
|
|
1367
|
+
const timer = debt ? setTimeout(() => (collecting = gather()), Math.max(0, debt.ready - Date.now())) : null
|
|
1368
|
+
timer?.unref?.()
|
|
1369
|
+
const take = () => {
|
|
1370
|
+
const u = pending
|
|
1371
|
+
pending = null
|
|
1372
|
+
return u
|
|
1373
|
+
}
|
|
1374
|
+
return {
|
|
1375
|
+
async own() {
|
|
1376
|
+
if (!canRead(tok)) return null
|
|
1377
|
+
const m = misses.get(key) ?? 0
|
|
1378
|
+
const waits = m < STEP_WAIT.skipAfter || m % STEP_WAIT.retryEvery === 0
|
|
1379
|
+
if (!kept) {
|
|
1380
|
+
const r = waits ? await usageEvents(tok, conv, since, 1, { poll: true, signal }).catch(() => ({})) : {}
|
|
1381
|
+
if (r.usage) misses.delete(key)
|
|
1382
|
+
else if (!r.refused) misses.set(key, m + 1)
|
|
1383
|
+
return r.usage ?? null
|
|
1384
|
+
}
|
|
1385
|
+
owe(conv, { since, n: 1, ready: Date.now() + STEP_WAIT.owedAt })
|
|
1386
|
+
if (waits) {
|
|
1387
|
+
const r = await gather({ poll: true, signal })
|
|
1388
|
+
if (!canRead(tok)) return null
|
|
1389
|
+
if (r.short) misses.set(key, m + 1)
|
|
1390
|
+
else misses.delete(key)
|
|
1391
|
+
} else misses.set(key, m + 1)
|
|
1392
|
+
return take() ?? false
|
|
1393
|
+
},
|
|
1394
|
+
async owed(final) {
|
|
1395
|
+
if (!kept) return null
|
|
1396
|
+
if (final) {
|
|
1397
|
+
await collecting
|
|
1398
|
+
await Promise.all([...(flights.get(conv) ?? [])])
|
|
1399
|
+
if (owed.has(conv)) await gather({ poll: owed.get(conv).n > 0, first: 0, signal })
|
|
1400
|
+
}
|
|
1401
|
+
return take()
|
|
1402
|
+
},
|
|
1403
|
+
done() {
|
|
1404
|
+
over = true
|
|
1405
|
+
clearTimeout(timer)
|
|
1406
|
+
if (pending) owe(conv, { carry: take() })
|
|
1407
|
+
},
|
|
1408
|
+
}
|
|
1218
1409
|
}
|
|
1219
1410
|
|
|
1220
1411
|
// buildRun is the Run's first message, an AgentClientMessage with its
|
|
@@ -1332,6 +1523,10 @@ async function runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, pa
|
|
|
1332
1523
|
// its usage event is found by it
|
|
1333
1524
|
const cid = convID || randomUUID()
|
|
1334
1525
|
const since = Date.now()
|
|
1526
|
+
// how its usage is counted: a debt of the conversation's is collected
|
|
1527
|
+
// while it goes, which only a conversation_id that names the session
|
|
1528
|
+
// could have run up
|
|
1529
|
+
const book = ledger(tok, cid, since, signal, !!convID)
|
|
1335
1530
|
const { first, blobs } = buildRun(conv.msgs, conv.last, tools, id, cid, { maxMode, params })
|
|
1336
1531
|
const o = await open(
|
|
1337
1532
|
base,
|
|
@@ -1348,7 +1543,7 @@ async function runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, pa
|
|
|
1348
1543
|
},
|
|
1349
1544
|
signal,
|
|
1350
1545
|
)
|
|
1351
|
-
if (o.error) return o
|
|
1546
|
+
if (o.error) return book.done(), o
|
|
1352
1547
|
const { session, req } = o
|
|
1353
1548
|
let closed = false
|
|
1354
1549
|
const send = (msg) => {
|
|
@@ -1373,6 +1568,7 @@ async function runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, pa
|
|
|
1373
1568
|
for await (const c of req) chunks.push(c)
|
|
1374
1569
|
} catch {}
|
|
1375
1570
|
close()
|
|
1571
|
+
book.done()
|
|
1376
1572
|
return { error: failure(o.status, Buffer.concat(chunks).toString("utf8").slice(0, 1 << 20)) }
|
|
1377
1573
|
}
|
|
1378
1574
|
const it = decode(frames(req), {
|
|
@@ -1381,19 +1577,21 @@ async function runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, pa
|
|
|
1381
1577
|
tools,
|
|
1382
1578
|
estimate: conv.estimate,
|
|
1383
1579
|
close,
|
|
1384
|
-
stepUsage:
|
|
1580
|
+
stepUsage: book.own,
|
|
1581
|
+
owedUsage: book.owed,
|
|
1385
1582
|
})
|
|
1386
1583
|
const wrapped = (async function* () {
|
|
1387
1584
|
try {
|
|
1388
1585
|
yield* it
|
|
1389
1586
|
} finally {
|
|
1390
1587
|
close()
|
|
1588
|
+
book.done()
|
|
1391
1589
|
}
|
|
1392
1590
|
})()
|
|
1393
1591
|
// an error comes before anything of the answer: out of quota, a model the
|
|
1394
1592
|
// plan doesn't have — answered with its own status
|
|
1395
1593
|
const head = await wrapped.next()
|
|
1396
|
-
if (head.done) return { error: { status: 502, message: "an empty reply" } }
|
|
1594
|
+
if (head.done) return book.done(), { error: { status: 502, message: "an empty reply" } }
|
|
1397
1595
|
if (head.value.error) {
|
|
1398
1596
|
await wrapped.return()
|
|
1399
1597
|
return { error: head.value.error }
|
|
@@ -1408,7 +1606,7 @@ async function runOnce({ tok, base, id, tools, conv, convID, signal, maxMode, pa
|
|
|
1408
1606
|
|
|
1409
1607
|
// decode follows the server's messages into the answer's pieces until the
|
|
1410
1608
|
// turn ends, or the model has made its tool calls.
|
|
1411
|
-
async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage }) {
|
|
1609
|
+
async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage, owedUsage }) {
|
|
1412
1610
|
let calls = 0
|
|
1413
1611
|
// ended: Cursor said what the turn used
|
|
1414
1612
|
let ended = false
|
|
@@ -1417,11 +1615,19 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage
|
|
|
1417
1615
|
// wrote: the reply's text alone; a turn of thinking only says nothing
|
|
1418
1616
|
let wrote = 0
|
|
1419
1617
|
let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0 }
|
|
1420
|
-
|
|
1421
|
-
|
|
1618
|
+
// counted: the step's usage is the dashboard's events, its own counted
|
|
1619
|
+
// with it or by a later step (ledger), never guessed
|
|
1620
|
+
let counted = false
|
|
1621
|
+
// finish is the step's end, with what earlier steps owed (more), which
|
|
1622
|
+
// is no reason not to guess the step's own
|
|
1623
|
+
const finish = (more) => {
|
|
1624
|
+
if (!counted && !usage.output) usage.output = Math.floor((said + 3) / 4)
|
|
1422
1625
|
// a guess only when Cursor counted nothing: a prompt read whole from
|
|
1423
|
-
// the cache leaves no uncached rest, and is no reason to guess
|
|
1424
|
-
|
|
1626
|
+
// the cache leaves no uncached rest, and is no reason to guess; nor is
|
|
1627
|
+
// a prompt counted from the events
|
|
1628
|
+
if (!counted && !usage.input && !usage.cacheRead && !usage.cacheWrite) usage.input = estimate
|
|
1629
|
+
// the events' output is their steps', counted here as their prompt is
|
|
1630
|
+
if (more) for (const k of ["input", "output", "cacheRead", "cacheWrite"]) usage[k] += more[k]
|
|
1425
1631
|
const prompt = usage.input + usage.cacheRead + usage.cacheWrite
|
|
1426
1632
|
return {
|
|
1427
1633
|
stop: calls > 0 ? "tool_calls" : "stop",
|
|
@@ -1435,14 +1641,17 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage
|
|
|
1435
1641
|
}
|
|
1436
1642
|
}
|
|
1437
1643
|
// settled is finish once a step that called tools has its usage: the
|
|
1438
|
-
// dashboard's count, the Run closed first, when the turn didn't end
|
|
1644
|
+
// dashboard's count, the Run closed first, when the turn didn't end (or
|
|
1645
|
+
// owed); and with what the conversation's earlier steps owed, which a
|
|
1646
|
+
// step that ends the turn waits for
|
|
1439
1647
|
const settled = async () => {
|
|
1440
1648
|
if (calls > 0 && !ended && stepUsage) {
|
|
1441
1649
|
close?.()
|
|
1442
1650
|
const u = await stepUsage().catch(() => null)
|
|
1443
1651
|
if (u) usage = u
|
|
1652
|
+
counted = u === false || !!u
|
|
1444
1653
|
}
|
|
1445
|
-
return finish()
|
|
1654
|
+
return finish(owedUsage ? await owedUsage(calls === 0).catch(() => null) : null)
|
|
1446
1655
|
}
|
|
1447
1656
|
const closeExec = (id) => send(pb().bytes(5, pb().bytes(1, pb().varint(1, id))).done())
|
|
1448
1657
|
try {
|
|
@@ -1485,7 +1694,7 @@ async function* decode(stream, { send, blobs, tools, estimate, close, stepUsage
|
|
|
1485
1694
|
if (calls === 0) {
|
|
1486
1695
|
// nothing written and nothing called: an empty reply,
|
|
1487
1696
|
// which the client asks again, not a finished turn
|
|
1488
|
-
yield wrote === 0 ? { error: { status: 502, message: "an empty reply" } } :
|
|
1697
|
+
yield wrote === 0 ? { error: { status: 502, message: "an empty reply" } } : await settled()
|
|
1489
1698
|
return
|
|
1490
1699
|
}
|
|
1491
1700
|
break
|
|
@@ -1810,4 +2019,4 @@ export async function CursorAuthPlugin() {
|
|
|
1810
2019
|
}
|
|
1811
2020
|
|
|
1812
2021
|
// for tests
|
|
1813
|
-
export const _internal = { STEP_WAIT, stepUsage, catalog, usable, maxRequired, conversationID, usageOf, sessionOf, SESSION, errorResponse, kept, poolBase, splitID, families, byEffort, offered, modelID, conversation, buildRun, decode, exec, fields, pb, pbValue, pbAny, frame, frames, failure, toolsOf, expiry }
|
|
2022
|
+
export const _internal = { STEP_WAIT, stepUsage, ledger, owed, flights, misses, unreadable, catalog, usable, maxRequired, conversationID, usageOf, sessionOf, SESSION, errorResponse, kept, poolBase, splitID, families, byEffort, offered, modelID, conversation, buildRun, decode, exec, fields, pb, pbValue, pbAny, frame, frames, failure, toolsOf, expiry }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-cursor-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
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",
|