@magpie-community/opencode-kiro-auth 0.1.14 → 0.1.16
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 +6 -2
- package/index.mjs +86 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,8 +46,12 @@ Power) and makes its requests, in OpenCode and in magpie. Provider id:
|
|
|
46
46
|
kiro-cli refreshes its own token first (`kiro-cli debug refresh-auth-token`).
|
|
47
47
|
A token the plugin refreshes itself is written back, so kiro-cli and the
|
|
48
48
|
IDE go on with it.
|
|
49
|
-
- **Kiro API key (`ksk_…`)**: sent with `tokentype: API_KEY
|
|
50
|
-
|
|
49
|
+
- **Kiro API key (`ksk_…`)**: sent with `tokentype: API_KEY`, as kiro-cli
|
|
50
|
+
sends it. `GetProfile` is asked in us-east-1, then eu-central-1, for the
|
|
51
|
+
region the key's profile is in; a key it finds none for is used in
|
|
52
|
+
us-east-1, as kiro-cli uses it. Its models are listed with the control
|
|
53
|
+
plane's `ListAvailableModels` and its credits with `GetUsageLimits`, and
|
|
54
|
+
no profile is named in its calls, as kiro-cli names none.
|
|
51
55
|
|
|
52
56
|
A browser sign-in is kept where OpenCode keeps sign-ins (`auth.json`; in
|
|
53
57
|
magpie, `plugin-auth.json`) as an `oauth` entry. The entry holds the
|
package/index.mjs
CHANGED
|
@@ -333,25 +333,48 @@ async function managementCall(region, a, method, query, body) {
|
|
|
333
333
|
return JSON.parse(text)
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
-
//
|
|
337
|
-
// key
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
336
|
+
// controlCall is one of Kiro's management operations as kiro-cli sends it
|
|
337
|
+
// for an API key: AWS JSON 1.0, the operation named in X-Amz-Target, POSTed
|
|
338
|
+
// to the endpoint's root (with ?origin=KIRO_CLI where kiro-cli adds it).
|
|
339
|
+
async function controlCall(region, a, target, body, query) {
|
|
340
|
+
const res = await fetch(management(region) + "/" + (query ? "?" + new URLSearchParams(query) : ""), {
|
|
341
|
+
method: "POST",
|
|
342
|
+
headers: { ...authHeaders(a), "Content-Type": "application/x-amz-json-1.0", "X-Amz-Target": target,
|
|
343
|
+
"x-amzn-codewhisperer-optout": "false" },
|
|
344
|
+
body: JSON.stringify(body),
|
|
345
|
+
signal: AbortSignal.timeout(20_000),
|
|
346
|
+
})
|
|
347
|
+
const text = await res.text()
|
|
348
|
+
if (!res.ok) throw new KiroError(res.status, text)
|
|
349
|
+
return JSON.parse(text)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// KEY_REGIONS are where kiro-cli asks GetProfile for an API key's profile,
|
|
353
|
+
// in its order (the GovCloud and ISO ones after them aren't Kiro's
|
|
354
|
+
// subscriptions).
|
|
355
|
+
const KEY_REGIONS = ["us-east-1", "eu-central-1"]
|
|
356
|
+
|
|
357
|
+
// keyProfile is an API key's profile, as kiro-cli finds it: GetProfile in
|
|
358
|
+
// each region until one answers with a profile. kiro-cli goes on without
|
|
359
|
+
// one when every region turns it down (a key from an account with no
|
|
360
|
+
// CodeWhisperer profile answers 400), and so does this: "" then, and the
|
|
361
|
+
// key's calls are made in us-east-1 without a profile, as kiro-cli makes
|
|
362
|
+
// them (Hollowhere, on magpie's Discord: a key kiro-cli 2.33.1 served
|
|
363
|
+
// failed every request here with "Kiro didn't take the API key: 400").
|
|
364
|
+
async function keyProfile(c) {
|
|
365
|
+
for (const region of KEY_REGIONS) {
|
|
366
|
+
try {
|
|
367
|
+
const arn = (await controlCall(region, { token: c.access, tokenType: "API_KEY" }, "AmazonCodeWhispererService.GetProfile", {}))
|
|
368
|
+
?.profile?.arn
|
|
369
|
+
if (arn) return arn
|
|
370
|
+
} catch {}
|
|
354
371
|
}
|
|
372
|
+
return ""
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// profileOf finds the profile a sign-in that doesn't name one uses:
|
|
376
|
+
// Builder ID's, or the first Kiro lists in either of its regions.
|
|
377
|
+
async function profileOf(c) {
|
|
355
378
|
if (c.builderID) return BUILDER_ID_PROFILE
|
|
356
379
|
let last
|
|
357
380
|
for (const region of ["us-east-1", "eu-central-1"]) {
|
|
@@ -370,6 +393,8 @@ async function profileOf(c) {
|
|
|
370
393
|
|
|
371
394
|
// usageLimits is Get-Usage-Limits: the plan, the account's email, credits.
|
|
372
395
|
function usageLimits(a, email) {
|
|
396
|
+
// an API key's, as kiro-cli asks for it: no profile, no email asked
|
|
397
|
+
if (a.tokenType === "API_KEY") return controlCall(a.region, a, "AmazonCodeWhispererService.GetUsageLimits", { origin: "KIRO_CLI" }, { origin: "KIRO_CLI" })
|
|
373
398
|
return managementCall(a.region, a, "Get-Usage-Limits",
|
|
374
399
|
{ origin: "KIRO_CLI", profileArn: a.profile, resourceType: "CREDIT", isEmailRequired: String(email) })
|
|
375
400
|
}
|
|
@@ -497,7 +522,12 @@ function account(client) {
|
|
|
497
522
|
saved = true
|
|
498
523
|
}
|
|
499
524
|
}
|
|
500
|
-
if (
|
|
525
|
+
if (c.method === "apikey") {
|
|
526
|
+
if (!c.asked) {
|
|
527
|
+
c.profile = await keyProfile(c)
|
|
528
|
+
c.asked = true
|
|
529
|
+
}
|
|
530
|
+
} else if (!c.profile) {
|
|
501
531
|
c.profile = await profileOf(c)
|
|
502
532
|
if (!auth?.profileArn && !saved) {
|
|
503
533
|
await save(auth, c)
|
|
@@ -510,7 +540,10 @@ function account(client) {
|
|
|
510
540
|
await save(auth, c)
|
|
511
541
|
}
|
|
512
542
|
held = { id, c }
|
|
513
|
-
|
|
543
|
+
// kiro-cli names no profile in an API key's calls, even one GetProfile
|
|
544
|
+
// found; the profile only says the key's region
|
|
545
|
+
const profile = c.method === "apikey" ? "" : c.profile
|
|
546
|
+
return { token: c.access, tokenType: tokenType(c), profile, region: regionOf(c.profile, c.region) }
|
|
514
547
|
})
|
|
515
548
|
// renew is auth.refresh's: a sign-in kept here (not kiro-cli's or the
|
|
516
549
|
// IDE's, which their owners keep) renewed under the same lock as the
|
|
@@ -584,9 +617,25 @@ function runtimeModel(m) {
|
|
|
584
617
|
// picks a model for each request.
|
|
585
618
|
const MODELS = [{ id: "auto", name: "Auto" }]
|
|
586
619
|
|
|
620
|
+
// keyModels is an API key's list, asked as kiro-cli asks it: Kiro's
|
|
621
|
+
// control plane's ListAvailableModels, with no profile, each page.
|
|
622
|
+
async function keyModels(a) {
|
|
623
|
+
const out = { models: [] }
|
|
624
|
+
let next
|
|
625
|
+
for (let i = 0; i < 20; i++) {
|
|
626
|
+
const page = await controlCall(a.region, a, "KiroControlPlaneBearerService.ListAvailableModels",
|
|
627
|
+
{ origin: "KIRO_CLI", ...(next ? { nextToken: next } : {}) }, { origin: "KIRO_CLI" })
|
|
628
|
+
out.models.push(...(page?.models ?? []))
|
|
629
|
+
out.defaultModel ??= page?.defaultModel
|
|
630
|
+
next = page?.nextToken
|
|
631
|
+
if (!next) break
|
|
632
|
+
}
|
|
633
|
+
return out
|
|
634
|
+
}
|
|
635
|
+
|
|
587
636
|
// listModels is List-Available-Models, the default first.
|
|
588
637
|
async function listModels(a) {
|
|
589
|
-
const out = await managementCall(a.region, a, "List-Available-Models", { origin: "KIRO_CLI", profileArn: a.profile })
|
|
638
|
+
const out = a.tokenType === "API_KEY" ? await keyModels(a) : await managementCall(a.region, a, "List-Available-Models", { origin: "KIRO_CLI", profileArn: a.profile })
|
|
590
639
|
const list = []
|
|
591
640
|
for (const m of out?.models ?? []) {
|
|
592
641
|
if (!m?.modelId) continue
|
|
@@ -1048,7 +1097,12 @@ async function* events(body, model, budget) {
|
|
|
1048
1097
|
let tool = ""
|
|
1049
1098
|
let tools = 0
|
|
1050
1099
|
let stop = ""
|
|
1051
|
-
|
|
1100
|
+
// reported: Kiro sent a tokenUsage. It doesn't today: a reply has a
|
|
1101
|
+
// contextUsageEvent and a meteringEvent (credits) and nothing more, and
|
|
1102
|
+
// kiro-cli 2.32's own trace log shows its MetadataEvent with every token
|
|
1103
|
+
// count None, while a repeated prompt is billed about half the credits,
|
|
1104
|
+
// so Kiro does cache and doesn't say how much
|
|
1105
|
+
const usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reported: false }
|
|
1052
1106
|
let pct = 0
|
|
1053
1107
|
let said = 0
|
|
1054
1108
|
let failed = ""
|
|
@@ -1098,6 +1152,7 @@ async function* events(body, model, budget) {
|
|
|
1098
1152
|
if (m.stopReason) stop = m.stopReason
|
|
1099
1153
|
const tu = m.tokenUsage
|
|
1100
1154
|
if (tu && typeof tu === "object") {
|
|
1155
|
+
usage.reported = true
|
|
1101
1156
|
usage.input += tu.uncachedInputTokens || tu.inputTokens || 0
|
|
1102
1157
|
usage.output += tu.outputTokens || 0
|
|
1103
1158
|
usage.cacheRead += tu.cacheReadInputTokens || 0
|
|
@@ -1143,12 +1198,15 @@ function finished(content) {
|
|
|
1143
1198
|
return content
|
|
1144
1199
|
}
|
|
1145
1200
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1201
|
+
// anthropicUsage is the usage in Messages' words. With no tokenUsage from
|
|
1202
|
+
// Kiro the input is reckoned from how full the context is, and what of it
|
|
1203
|
+
// Kiro read from its cache is unknown, not 0: the cache fields are left
|
|
1204
|
+
// out and magpie is told so (magpie_cache_unknown), so it shows no hit
|
|
1205
|
+
// rate rather than 0%.
|
|
1206
|
+
const anthropicUsage = (u) =>
|
|
1207
|
+
u.reported
|
|
1208
|
+
? { input_tokens: u.input, output_tokens: u.output, cache_read_input_tokens: u.cacheRead, cache_creation_input_tokens: u.cacheWrite }
|
|
1209
|
+
: { input_tokens: u.input, output_tokens: u.output, magpie_cache_unknown: true }
|
|
1152
1210
|
|
|
1153
1211
|
// quotaWords are magpie's (internal/gateway/fallback.go): how a vendor says
|
|
1154
1212
|
// "out of quota" or "slow down".
|