@magpie-community/opencode-kiro-auth 0.1.23 → 0.1.25

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 (3) hide show
  1. package/README.md +13 -1
  2. package/index.mjs +53 -10
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -152,7 +152,10 @@ request:
152
152
  - Anthropic's hosted web search is answered with Kiro's own web search
153
153
  (see [Web search](#web-search)).
154
154
  - Kiro's failures keep their meaning:
155
- - input too long → 400
155
+ - input too long → 400 "input is too long for the model's context", whether
156
+ Kiro says it as an HTTP answer, as an exception in the stream, or by
157
+ stopping with `MODEL_CONTEXT_WINDOW_EXCEEDED` before a word (after some
158
+ of the reply, it ends with stop reason `model_context_window_exceeded`)
156
159
  - no capacity → 503
157
160
  - usage limit → 429
158
161
  - throttling → "rate limited"
@@ -237,6 +240,15 @@ the `provider.models` hook replaces it with the account's own list from
237
240
  context window, output limit and whether it takes images. Claude models
238
241
  and Auto have the thinking variants.
239
242
 
243
+ ## Usage in magpie
244
+
245
+ `auth.usage` reads `Get-Usage-Limits`: the month's credits, the Free trial
246
+ while it is active, and each bonus (`bonuses`: a first upgrade's trial
247
+ credits, a promotion's) while it is `ACTIVE` and not expired, as a window of
248
+ its own with its count and when it lapses. Bonus windows are shown only.
249
+ Kiro spends the plan's credits first, so a month used up while a bonus has
250
+ credits left is shown only too, and the account isn't taken for spent.
251
+
240
252
  ## Not included
241
253
 
242
254
  - Usage and quota (credits, the monthly window).
package/index.mjs CHANGED
@@ -419,7 +419,14 @@ function goG(x) {
419
419
  // magpie can say what is left of it when the user shows what is left
420
420
  // (#408): display alone is the vendor's text, which magpie can't turn
421
421
  // round.
422
- function usageOf(l) {
422
+ //
423
+ // Bonus credits (each allowance's bonuses: a first upgrade's trial credits,
424
+ // a promotion's) are a window each while ACTIVE and not expired, after the
425
+ // month's: their count, and when they lapse (runsOutAt), as they don't
426
+ // reset. They are shown only (aside). Kiro spends the plan's credits
427
+ // first, then these, so a month used up while a bonus has credits left
428
+ // is set aside too, and the account isn't taken for spent.
429
+ function usageOf(l, now = Date.now()) {
423
430
  const at = (secs) => (secs > 0 ? new Date(Math.trunc(secs) * 1000).toISOString() : undefined)
424
431
  const count = (used, limit) => used.toFixed(2).replace(/0+$/, "").replace(/\.$/, "") + " / " + goG(limit)
425
432
  const num = (x) => (typeof x === "number" && Number.isFinite(x) ? x : 0)
@@ -431,11 +438,22 @@ function usageOf(l) {
431
438
  windows.push({ name: "Free trial", used: (100 * used) / limit, resetsAt: at(num(t.freeTrialExpiry)), display: count(used, limit),
432
439
  amount: used, limit, unit: "credits" })
433
440
  }
441
+ const bonuses = []
442
+ for (const b of Array.isArray(u?.bonuses) ? u.bonuses : []) {
443
+ const used = num(b?.currentUsage), limit = num(b?.usageLimit), expires = num(b?.expiresAt)
444
+ if (String(b?.status ?? "").toUpperCase() !== "ACTIVE" || limit <= 0 || (expires > 0 && expires * 1000 <= now)) continue
445
+ const left = Math.max(0, limit - used)
446
+ bonuses.push({ name: b?.displayName || "Bonus credits", used: (100 * used) / limit, display: count(used, limit),
447
+ amount: used, limit, unit: "credits", ...(expires > 0 && left > 0 ? { runsOutAt: at(expires), runsOut: left } : {}), aside: true })
448
+ }
434
449
  const used = num(u?.currentUsageWithPrecision), limit = num(u?.usageLimitWithPrecision)
435
- if (limit <= 0) continue
436
- const reset = num(u?.nextDateReset) || num(l?.nextDateReset)
437
- windows.push({ name: u?.displayNamePlural || u?.displayName || "Credits", used: (100 * used) / limit, resetsAt: at(reset),
438
- display: count(used, limit), amount: used, limit, unit: "credits", span: 30 * 24 * 3600 })
450
+ if (limit > 0) {
451
+ const reset = num(u?.nextDateReset) || num(l?.nextDateReset)
452
+ const carried = used >= limit && bonuses.some((b) => b.amount < b.limit)
453
+ windows.push({ name: u?.displayNamePlural || u?.displayName || "Credits", used: (100 * used) / limit, resetsAt: at(reset),
454
+ display: count(used, limit), amount: used, limit, unit: "credits", span: 30 * 24 * 3600, ...(carried ? { aside: true } : {}) })
455
+ }
456
+ windows.push(...bonuses)
439
457
  }
440
458
  const email = l?.userInfo?.email
441
459
  return { plan: planName(l?.subscriptionInfo?.subscriptionTitle), ...(typeof email === "string" && email ? { user: email } : {}), windows }
@@ -1155,6 +1173,16 @@ function sendKiro(a, body, signal) {
1155
1173
  })
1156
1174
  }
1157
1175
 
1176
+ // tooLongSaid is whether Kiro's words say the conversation is past the
1177
+ // model's window: its ValidationException "Input is too long." with reason
1178
+ // CONTENT_LENGTH_EXCEEDS_THRESHOLD, as an HTTP answer or as an exception
1179
+ // in the event stream, or a reply it stopped with MODEL_CONTEXT_WINDOW_EXCEEDED.
1180
+ const tooLongSaid = (msg) => /CONTENT_LENGTH_EXCEEDS_THRESHOLD|MODEL_CONTEXT_WINDOW_EXCEEDED|input is too long/i.test(msg)
1181
+ // TOO_LONG heads a too-long failure in the words magpie's gateway takes
1182
+ // for one (tooLong): another member with a larger window is asked, no
1183
+ // account rests, and with none left the agent is told to compact.
1184
+ const TOO_LONG = "input is too long for the model's context: "
1185
+
1158
1186
  // failure is the status and message for an error Kiro answered with:
1159
1187
  // {"message": "...", "reason": "..."}.
1160
1188
  function failure(status, body) {
@@ -1164,8 +1192,7 @@ function failure(status, body) {
1164
1192
  if (e?.message) msg = e.message + (e.reason ? ` (${e.reason})` : "")
1165
1193
  } catch {}
1166
1194
  msg ||= STATUS_TEXT[status] ?? ""
1167
- if (msg.includes("CONTENT_LENGTH_EXCEEDS_THRESHOLD") || msg.toLowerCase().includes("input is too long"))
1168
- return { status: 400, message: "input is too long for the model's context: " + msg }
1195
+ if (tooLongSaid(msg)) return { status: 400, message: TOO_LONG + msg }
1169
1196
  if (msg.includes("INSUFFICIENT_MODEL_CAPACITY")) return { status: 503, message: msg }
1170
1197
  if (msg.includes("MONTHLY_REQUEST_COUNT") || msg.includes("USAGE_LIMIT")) return { status: 429, message: "usage limit reached: " + msg }
1171
1198
  return { status, message: msg }
@@ -1413,8 +1440,15 @@ async function* events(body, model, budget) {
1413
1440
  // Kiro says how full the context is rather than how many tokens
1414
1441
  if (!usage.input && pct > 0 && window > 0) usage.input = Math.floor((pct / 100) * window)
1415
1442
  if (!usage.output) usage.output = Math.floor((said + 3) / 4)
1443
+ const full = /^model_context_window_exceeded$/i.test(stop)
1444
+ if (full && !said && !tools) {
1445
+ // stopped for the window before a word: the conversation doesn't fit
1446
+ yield { kind: "error", text: "the reply stopped as the model's context window was full (MODEL_CONTEXT_WINDOW_EXCEEDED)" }
1447
+ return
1448
+ }
1416
1449
  let reason = "end_turn"
1417
1450
  if (tools > 0) reason = "tool_use"
1451
+ else if (full) reason = "model_context_window_exceeded" // cut short, as Anthropic's API says it
1418
1452
  else if (/^max_tokens$/i.test(stop)) reason = "max_tokens"
1419
1453
  else if (/^content_filtered$/i.test(stop)) reason = "refusal"
1420
1454
  yield { kind: "stop", reason, usage }
@@ -1454,7 +1488,14 @@ const QUOTA_WORDS = /quota|insufficient|balance|credit|billing|exceeded|rate.?li
1454
1488
  // magpie's built-in relay gives it: a status, not a stream, so another
1455
1489
  // account can take over — a 429 when it reads as a quota or rate limit,
1456
1490
  // else a 502.
1457
- const failedBefore = (text) => errorResponse(QUOTA_WORDS.test(text) ? 429 : 502, text)
1491
+ //
1492
+ // A conversation past the model's window is a 400 that says so, whichever
1493
+ // way Kiro said it, before the quota words ("exceeded"): as a 502, magpie
1494
+ // rested the account, asked it again, and told a lone agent of a vendor
1495
+ // error, so it never compacted (ergou0954_54426 on Discord: Kiro in a
1496
+ // group routed by intent, "超出上下文", the turn failed).
1497
+ const failedBefore = (text) =>
1498
+ tooLongSaid(text) ? errorResponse(400, TOO_LONG + text) : errorResponse(QUOTA_WORDS.test(text) ? 429 : 502, text)
1458
1499
 
1459
1500
  // reply answers the Messages request from Kiro's events: a stream of
1460
1501
  // Messages' events, or one message. names gives back the caller's name of
@@ -1887,8 +1928,10 @@ async function* searching(creds, auth, req, model, budget, signal, hosted, first
1887
1928
  else said.push({ type: "text", text: e.text })
1888
1929
  }
1889
1930
  if (e.kind === "error") {
1890
- // in a turn after a search, a status, not a finished answer (reply)
1891
- yield turn > 1 ? { ...e, status: 502 } : e
1931
+ // in a turn after a search, a status, not a finished answer
1932
+ // (reply): one past the window says so, as failedBefore does
1933
+ if (turn > 1 && tooLongSaid(e.text)) yield { ...e, status: 400, text: TOO_LONG + e.text }
1934
+ else yield turn > 1 ? { ...e, status: 502 } : e
1892
1935
  return
1893
1936
  }
1894
1937
  yield e
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-kiro-auth",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "Kiro subscriptions (Free, Pro, Pro+, Power) in OpenCode and magpie",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",