@magpie-community/opencode-kiro-auth 0.1.2 → 0.1.4
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 +57 -24
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -189,6 +189,11 @@ async function saveBack(c) {
|
|
|
189
189
|
// ---- tokens --------------------------------------------------------------------
|
|
190
190
|
|
|
191
191
|
const EXPIRED = "Kiro's sign-in has expired; sign in again"
|
|
192
|
+
|
|
193
|
+
// gone is an error saying the sign-in itself is gone — refused, or never
|
|
194
|
+
// there — which alone answers a request with the 401 magpie marks the
|
|
195
|
+
// account lapsed on; a refresh that timed out or met a 5xx may pass.
|
|
196
|
+
const gone = (msg) => Object.assign(new Error(msg), { gone: true })
|
|
192
197
|
const fresh = (c) => c.method === "apikey" || !c.expires || c.expires - Date.now() > 2 * 60 * 1000
|
|
193
198
|
|
|
194
199
|
async function post(url, contentType, body, headers = {}) {
|
|
@@ -201,7 +206,7 @@ async function post(url, contentType, body, headers = {}) {
|
|
|
201
206
|
}
|
|
202
207
|
const text = await res.text()
|
|
203
208
|
if (!res.ok) {
|
|
204
|
-
if ([400, 401, 403].includes(res.status)) throw
|
|
209
|
+
if ([400, 401, 403].includes(res.status)) throw gone(EXPIRED)
|
|
205
210
|
throw new Error(`refreshing Kiro's sign-in: ${res.status}`)
|
|
206
211
|
}
|
|
207
212
|
return JSON.parse(text)
|
|
@@ -218,7 +223,7 @@ async function refresh(c) {
|
|
|
218
223
|
if (n && n.dbKey === c.dbKey && n.access !== c.access && fresh(n)) return n
|
|
219
224
|
}
|
|
220
225
|
}
|
|
221
|
-
if (!c.refresh) throw
|
|
226
|
+
if (!c.refresh) throw gone(EXPIRED)
|
|
222
227
|
let out
|
|
223
228
|
switch (c.method) {
|
|
224
229
|
case "social": {
|
|
@@ -234,16 +239,16 @@ async function refresh(c) {
|
|
|
234
239
|
break
|
|
235
240
|
}
|
|
236
241
|
case "external-idp": {
|
|
237
|
-
if (!c.tokenURL) throw
|
|
242
|
+
if (!c.tokenURL) throw gone(EXPIRED + " with `kiro-cli login`")
|
|
238
243
|
const form = new URLSearchParams({ grant_type: "refresh_token", client_id: c.clientId ?? "", refresh_token: c.refresh })
|
|
239
244
|
const r = await post(c.tokenURL, "application/x-www-form-urlencoded", form.toString())
|
|
240
245
|
out = { access: r.access_token, refresh: r.refresh_token, expiresIn: r.expires_in }
|
|
241
246
|
break
|
|
242
247
|
}
|
|
243
248
|
default:
|
|
244
|
-
throw
|
|
249
|
+
throw gone(EXPIRED)
|
|
245
250
|
}
|
|
246
|
-
if (!out.access) throw
|
|
251
|
+
if (!out.access) throw gone(EXPIRED)
|
|
247
252
|
const n = { ...c, access: out.access, expires: Date.now() + (out.expiresIn > 0 ? out.expiresIn : 3600) * 1000 }
|
|
248
253
|
if (out.refresh) n.refresh = out.refresh
|
|
249
254
|
await saveBack(n)
|
|
@@ -309,7 +314,10 @@ async function profileOf(c) {
|
|
|
309
314
|
body: "{}",
|
|
310
315
|
signal: AbortSignal.timeout(20_000),
|
|
311
316
|
})
|
|
312
|
-
if (!res.ok)
|
|
317
|
+
if (!res.ok) {
|
|
318
|
+
const msg = `Kiro didn't take the API key: ${res.status}`
|
|
319
|
+
throw res.status === 401 || res.status === 403 ? gone(msg) : new Error(msg)
|
|
320
|
+
}
|
|
313
321
|
const arn = (await res.json().catch(() => null))?.profile?.arn
|
|
314
322
|
if (!arn) throw new Error("Kiro didn't say which profile the API key is for")
|
|
315
323
|
return arn
|
|
@@ -411,7 +419,7 @@ function account(client) {
|
|
|
411
419
|
const read = await credOf(auth)
|
|
412
420
|
if (!read) {
|
|
413
421
|
held = null
|
|
414
|
-
throw
|
|
422
|
+
throw gone(
|
|
415
423
|
auth?.source
|
|
416
424
|
? "Kiro isn't signed in; add the Kiro subscription in magpie, sign in with `kiro-cli login` or the Kiro IDE, or save a Kiro API key on the provider"
|
|
417
425
|
: "this Kiro account's sign-in is gone; add it again in magpie",
|
|
@@ -423,7 +431,7 @@ function account(client) {
|
|
|
423
431
|
if (stale && c && read.access !== c.access && fresh(read)) stale = false // the owner refreshed it already
|
|
424
432
|
c = read
|
|
425
433
|
if (!fresh(c) || stale) {
|
|
426
|
-
if (c.method === "apikey") throw
|
|
434
|
+
if (c.method === "apikey") throw gone("Kiro turned down the API key")
|
|
427
435
|
c = await refresh(c)
|
|
428
436
|
await save(auth, c)
|
|
429
437
|
}
|
|
@@ -716,7 +724,7 @@ const ERROR_TYPES = { 400: "invalid_request_error", 401: "authentication_error",
|
|
|
716
724
|
404: "not_found_error", 413: "request_too_large", 429: "rate_limit_error", 503: "overloaded_error", 529: "overloaded_error" }
|
|
717
725
|
const errorType = (status) => ERROR_TYPES[status] ?? "api_error"
|
|
718
726
|
const errorResponse = (status, message) =>
|
|
719
|
-
new Response(JSON.stringify({ type: "error", error: { type: errorType(status), message
|
|
727
|
+
new Response(JSON.stringify({ type: "error", error: { type: errorType(status), message } }), {
|
|
720
728
|
status,
|
|
721
729
|
headers: { "Content-Type": "application/json" },
|
|
722
730
|
})
|
|
@@ -944,6 +952,19 @@ async function* events(body, model, budget) {
|
|
|
944
952
|
yield { kind: "stop", reason, usage }
|
|
945
953
|
}
|
|
946
954
|
|
|
955
|
+
// finished is the message's blocks with each call's arguments read.
|
|
956
|
+
function finished(content) {
|
|
957
|
+
for (const b of content)
|
|
958
|
+
if (b.type === "tool_use") {
|
|
959
|
+
try {
|
|
960
|
+
b.input = b.input ? JSON.parse(b.input) : {}
|
|
961
|
+
} catch {
|
|
962
|
+
b.input = {}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
return content
|
|
966
|
+
}
|
|
967
|
+
|
|
947
968
|
const anthropicUsage = (u) => ({
|
|
948
969
|
input_tokens: u.input,
|
|
949
970
|
output_tokens: u.output,
|
|
@@ -951,6 +972,16 @@ const anthropicUsage = (u) => ({
|
|
|
951
972
|
cache_creation_input_tokens: u.cacheWrite,
|
|
952
973
|
})
|
|
953
974
|
|
|
975
|
+
// quotaWords are magpie's (internal/gateway/fallback.go): how a vendor says
|
|
976
|
+
// "out of quota" or "slow down".
|
|
977
|
+
const QUOTA_WORDS = /quota|insufficient|balance|credit|billing|exceeded|rate.?limit|usage.?limit|limit.?reached|hit your .*limit|limit.{0,24}resets|too many requests|overloaded|余额|额度|欠费|限流|频率|套餐|用量|上限/i
|
|
978
|
+
|
|
979
|
+
// failedBefore is the reply to an error before any of the answer, as
|
|
980
|
+
// magpie's built-in relay gives it: a status, not a stream, so another
|
|
981
|
+
// account can take over — a 429 when it reads as a quota or rate limit,
|
|
982
|
+
// else a 502.
|
|
983
|
+
const failedBefore = (text) => errorResponse(QUOTA_WORDS.test(text) ? 429 : 502, text)
|
|
984
|
+
|
|
954
985
|
// reply answers the Messages request from Kiro's events: a stream of
|
|
955
986
|
// Messages' events, or one message.
|
|
956
987
|
async function reply(it, model, stream) {
|
|
@@ -974,23 +1005,24 @@ async function reply(it, model, stream) {
|
|
|
974
1005
|
} else if (e.kind === "toolStart") add({ type: "tool_use", id: e.id, name: e.name, input: "" })
|
|
975
1006
|
else if (e.kind === "toolArgs") {
|
|
976
1007
|
if (last?.type === "tool_use") last.input += e.text
|
|
977
|
-
} else if (e.kind === "error")
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
b.input = {}
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
return Response.json({ id, type: "message", role: "assistant", model, content, stop_reason: e.reason, stop_sequence: null,
|
|
1008
|
+
} else if (e.kind === "error") {
|
|
1009
|
+
// with none of the answer said, a status; else what was said
|
|
1010
|
+
if (!content.length) return failedBefore(e.text)
|
|
1011
|
+
return Response.json({ id, type: "message", role: "assistant", model, content: finished(content), stop_reason: "end_turn",
|
|
1012
|
+
stop_sequence: null, usage: anthropicUsage({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }) })
|
|
1013
|
+
} else if (e.kind === "stop") {
|
|
1014
|
+
return Response.json({ id, type: "message", role: "assistant", model, content: finished(content), stop_reason: e.reason, stop_sequence: null,
|
|
988
1015
|
usage: anthropicUsage(e.usage) })
|
|
989
1016
|
}
|
|
990
1017
|
}
|
|
991
1018
|
return errorResponse(502, "the reply ended before it was complete")
|
|
992
1019
|
}
|
|
993
1020
|
|
|
1021
|
+
// an error before any of the answer is a status, as the built-in's
|
|
1022
|
+
const head = await it.next()
|
|
1023
|
+
if (head.value?.kind === "error") return failedBefore(head.value.text)
|
|
1024
|
+
let pending = head
|
|
1025
|
+
|
|
994
1026
|
const enc = new TextEncoder()
|
|
995
1027
|
const sse = (type, data) => enc.encode(`event: ${type}\ndata: ${JSON.stringify({ type, ...data })}\n\n`)
|
|
996
1028
|
let index = -1
|
|
@@ -1014,7 +1046,8 @@ async function reply(it, model, stream) {
|
|
|
1014
1046
|
open = kind
|
|
1015
1047
|
ctl.enqueue(sse("content_block_start", { index, content_block: block }))
|
|
1016
1048
|
}
|
|
1017
|
-
const r = await it.next()
|
|
1049
|
+
const r = pending ?? (await it.next())
|
|
1050
|
+
pending = null
|
|
1018
1051
|
if (r.done) return ctl.close()
|
|
1019
1052
|
const e = r.value
|
|
1020
1053
|
switch (e.kind) {
|
|
@@ -1037,7 +1070,7 @@ async function reply(it, model, stream) {
|
|
|
1037
1070
|
break
|
|
1038
1071
|
case "error":
|
|
1039
1072
|
close()
|
|
1040
|
-
ctl.enqueue(sse("error", { error: { type: "api_error", message:
|
|
1073
|
+
ctl.enqueue(sse("error", { error: { type: "api_error", message: e.text } }))
|
|
1041
1074
|
return ctl.close()
|
|
1042
1075
|
case "stop":
|
|
1043
1076
|
close()
|
|
@@ -1062,7 +1095,7 @@ async function generate(creds, auth, req, signal) {
|
|
|
1062
1095
|
try {
|
|
1063
1096
|
a = await creds(auth)
|
|
1064
1097
|
} catch (e) {
|
|
1065
|
-
return errorResponse(401, e.message)
|
|
1098
|
+
return errorResponse(e?.gone ? 401 : 502, e.message)
|
|
1066
1099
|
}
|
|
1067
1100
|
let res
|
|
1068
1101
|
try {
|
|
@@ -1076,7 +1109,7 @@ async function generate(creds, auth, req, signal) {
|
|
|
1076
1109
|
try {
|
|
1077
1110
|
a = await creds(auth, true)
|
|
1078
1111
|
} catch (e) {
|
|
1079
|
-
return errorResponse(401, e.message)
|
|
1112
|
+
return errorResponse(e?.gone ? 401 : 502, e.message)
|
|
1080
1113
|
}
|
|
1081
1114
|
try {
|
|
1082
1115
|
res = await sendKiro(a, buildKiro(req, model, a.profile, budget), signal)
|