@javargasm/pi-kiro 0.4.4 → 0.4.6
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/CHANGELOG.md +16 -0
- package/dist/core.js +499 -18
- package/dist/extension.d.ts.map +1 -1
- package/dist/extension.js +540 -29
- package/dist/models.d.ts +6 -0
- package/dist/models.d.ts.map +1 -1
- package/dist/oauth.d.ts +1 -1
- package/dist/oauth.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fix duplicate browser tab during Enterprise sign-in and resolve profileArn immediately after successful login and token refresh.
|
|
8
|
+
|
|
9
|
+
## 0.4.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- fix: resolve profileArn automatically for Builder ID accounts
|
|
14
|
+
|
|
15
|
+
Builder ID device-code login never receives a profileArn, which prevented
|
|
16
|
+
model fetching and streaming. Now the startup resolves it via
|
|
17
|
+
`ListAvailableProfiles` and persists it to auth.json.
|
|
18
|
+
|
|
3
19
|
## 0.4.4
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/dist/core.js
CHANGED
|
@@ -897,6 +897,24 @@ var kiroModels = [
|
|
|
897
897
|
maxTokens: 65536
|
|
898
898
|
}
|
|
899
899
|
];
|
|
900
|
+
async function resolveProfileArn(accessToken, apiRegion) {
|
|
901
|
+
const resp = await fetch(`https://management.${apiRegion}.kiro.dev/`, {
|
|
902
|
+
method: "POST",
|
|
903
|
+
headers: {
|
|
904
|
+
Authorization: `Bearer ${accessToken}`,
|
|
905
|
+
"Content-Type": "application/x-amz-json-1.0",
|
|
906
|
+
"X-Amz-Target": "AmazonCodeWhispererService.ListAvailableProfiles",
|
|
907
|
+
"User-Agent": "pi-kiro"
|
|
908
|
+
},
|
|
909
|
+
body: "{}"
|
|
910
|
+
});
|
|
911
|
+
if (!resp || !resp.ok)
|
|
912
|
+
return null;
|
|
913
|
+
const data = await resp.json();
|
|
914
|
+
const profiles = data.profiles ?? [];
|
|
915
|
+
const kiroProfile = profiles.find((p) => p.profileType === "KIRO" && p.status === "ACTIVE");
|
|
916
|
+
return kiroProfile?.arn ?? profiles[0]?.arn ?? null;
|
|
917
|
+
}
|
|
900
918
|
async function fetchAvailableModels(accessToken, apiRegion, profileArn) {
|
|
901
919
|
const url = `https://management.${apiRegion}.kiro.dev/?origin=KIRO_CLI&profileArn=${encodeURIComponent(profileArn)}`;
|
|
902
920
|
const resp = await fetch(url, {
|
|
@@ -916,7 +934,9 @@ async function fetchAvailableModels(accessToken, apiRegion, profileArn) {
|
|
|
916
934
|
},
|
|
917
935
|
body: JSON.stringify({ origin: "KIRO_CLI", profileArn })
|
|
918
936
|
});
|
|
919
|
-
if (!resp.ok) {
|
|
937
|
+
if (!resp || !resp.ok) {
|
|
938
|
+
if (!resp)
|
|
939
|
+
throw new Error("ListAvailableModels failed: fetch returned no response");
|
|
920
940
|
const body = await resp.text().catch(() => "");
|
|
921
941
|
if (resp.status === 401 || resp.status === 400 && body.includes("Invalid token")) {
|
|
922
942
|
throw new Error(`Authentication failed: 401 ListAvailableModels failed - ${body}`);
|
|
@@ -980,6 +1000,8 @@ function setCachedDynamicModels(models) {
|
|
|
980
1000
|
}
|
|
981
1001
|
|
|
982
1002
|
// src/oauth.ts
|
|
1003
|
+
import { createServer } from "node:http";
|
|
1004
|
+
import { randomBytes, createHash } from "node:crypto";
|
|
983
1005
|
var BUILDER_ID_START_URL = "https://view.awsapps.com/start";
|
|
984
1006
|
var BUILDER_ID_REGION = "us-east-1";
|
|
985
1007
|
var SSO_SCOPES = [
|
|
@@ -1089,6 +1111,417 @@ async function pollForToken(oidcEndpoint, clientId, clientSecret, devAuth, signa
|
|
|
1089
1111
|
}
|
|
1090
1112
|
throw new Error("Authorization timed out");
|
|
1091
1113
|
}
|
|
1114
|
+
var KIRO_SOCIAL_PORTAL = "https://app.kiro.dev";
|
|
1115
|
+
var KIRO_SOCIAL_AUTH_ENDPOINT = `https://prod.${BUILDER_ID_REGION}.auth.desktop.kiro.dev`;
|
|
1116
|
+
var SOCIAL_REDIRECT_PORT = 49153;
|
|
1117
|
+
var SOCIAL_REDIRECT_URI = `http://localhost:${SOCIAL_REDIRECT_PORT}`;
|
|
1118
|
+
function generateRandomState() {
|
|
1119
|
+
return randomBytes(16).toString("base64url");
|
|
1120
|
+
}
|
|
1121
|
+
function generateCodeVerifier() {
|
|
1122
|
+
return randomBytes(32).toString("base64url");
|
|
1123
|
+
}
|
|
1124
|
+
function generateCodeChallenge(verifier) {
|
|
1125
|
+
return createHash("sha256").update(verifier).digest("base64url");
|
|
1126
|
+
}
|
|
1127
|
+
function buildSocialSignInURL(redirectUri, codeChallenge, state) {
|
|
1128
|
+
const params = new URLSearchParams;
|
|
1129
|
+
params.set("code_challenge", codeChallenge);
|
|
1130
|
+
params.set("code_challenge_method", "S256");
|
|
1131
|
+
params.set("redirect_from", "kirocli");
|
|
1132
|
+
params.set("redirect_uri", redirectUri);
|
|
1133
|
+
params.set("state", state);
|
|
1134
|
+
return `${KIRO_SOCIAL_PORTAL}/signin?${params.toString()}`;
|
|
1135
|
+
}
|
|
1136
|
+
function parseAuthRedirectInput(input) {
|
|
1137
|
+
const trimmed = input.trim();
|
|
1138
|
+
if (!trimmed)
|
|
1139
|
+
return {};
|
|
1140
|
+
try {
|
|
1141
|
+
const url = new URL(trimmed);
|
|
1142
|
+
return {
|
|
1143
|
+
code: url.searchParams.get("code") ?? undefined,
|
|
1144
|
+
state: url.searchParams.get("state") ?? undefined
|
|
1145
|
+
};
|
|
1146
|
+
} catch {
|
|
1147
|
+
return { code: trimmed };
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
function buildTokenRedirectUri(callbackPath, loginOption) {
|
|
1151
|
+
const path = callbackPath || "/oauth/callback";
|
|
1152
|
+
const base = `${SOCIAL_REDIRECT_URI}${path}`;
|
|
1153
|
+
if (loginOption) {
|
|
1154
|
+
return `${base}?login_option=${encodeURIComponent(loginOption)}`;
|
|
1155
|
+
}
|
|
1156
|
+
return base;
|
|
1157
|
+
}
|
|
1158
|
+
function oauthCallbackPage(kind, title, message, redirectUrl = "https://app.kiro.dev") {
|
|
1159
|
+
const borderColor = kind === "success" ? "#22c55e" : "#ef4444";
|
|
1160
|
+
const iconSvg = kind === "success" ? `<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M16.67 5L7.5 14.17 3.33 10" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>` : `<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M15 5L5 15M5 5l10 10" stroke="#ef4444" stroke-width="2" stroke-linecap="round"/></svg>`;
|
|
1161
|
+
const ghostSvg = `<svg width="56" height="56" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
1162
|
+
<path d="M50 5C28.5 5 11 22.5 11 44v36c0 2.8 1.2 5.4 3.2 7.2 2 1.8 4.7 2.6 7.4 2.2l3.4-.5c3.2-.5 6.5.4 9 2.5l2.2 1.8c2.4 2 5.4 3 8.5 3h10.6c3.1 0 6.1-1.1 8.5-3l2.2-1.8c2.5-2.1 5.8-3 9-2.5l3.4.5c2.7.4 5.4-.4 7.4-2.2 2-1.8 3.2-4.4 3.2-7.2V44C89 22.5 71.5 5 50 5z" fill="white"/>
|
|
1163
|
+
<circle cx="37" cy="45" r="7" fill="#0a0a0a"/>
|
|
1164
|
+
<circle cx="63" cy="45" r="7" fill="#0a0a0a"/>
|
|
1165
|
+
</svg>`;
|
|
1166
|
+
const redirectHost = (() => {
|
|
1167
|
+
try {
|
|
1168
|
+
return new URL(redirectUrl).hostname;
|
|
1169
|
+
} catch {
|
|
1170
|
+
return redirectUrl;
|
|
1171
|
+
}
|
|
1172
|
+
})();
|
|
1173
|
+
const countdownHtml = kind === "success" ? `
|
|
1174
|
+
<div class="countdown" id="countdown">
|
|
1175
|
+
<svg class="ring" viewBox="0 0 60 60">
|
|
1176
|
+
<circle cx="30" cy="30" r="26" stroke="#1a1a1a" stroke-width="3" fill="none"/>
|
|
1177
|
+
<circle id="ring-progress" cx="30" cy="30" r="26" stroke="#22c55e" stroke-width="3" fill="none"
|
|
1178
|
+
stroke-dasharray="163.36" stroke-dashoffset="0" stroke-linecap="round"
|
|
1179
|
+
transform="rotate(-90 30 30)" style="transition:stroke-dashoffset 1s linear"/>
|
|
1180
|
+
</svg>
|
|
1181
|
+
<span class="countdown-num" id="countdown-num">3</span>
|
|
1182
|
+
</div>
|
|
1183
|
+
<p class="subtitle">Redirecting to <strong>${redirectHost}</strong>…</p>
|
|
1184
|
+
<script>
|
|
1185
|
+
(function(){
|
|
1186
|
+
var n=3, el=document.getElementById('countdown-num'),
|
|
1187
|
+
ring=document.getElementById('ring-progress'), circ=163.36;
|
|
1188
|
+
function tick(){
|
|
1189
|
+
if(n<=0){window.location.href=${JSON.stringify(redirectUrl)};return}
|
|
1190
|
+
el.textContent=n;
|
|
1191
|
+
ring.setAttribute('stroke-dashoffset', String(circ*(1-n/3)));
|
|
1192
|
+
n--;
|
|
1193
|
+
setTimeout(tick,1000);
|
|
1194
|
+
}
|
|
1195
|
+
tick();
|
|
1196
|
+
})();
|
|
1197
|
+
</script>` : `<p class="subtitle">Please close this window and try again</p>`;
|
|
1198
|
+
return `<!DOCTYPE html>
|
|
1199
|
+
<html lang="en">
|
|
1200
|
+
<head>
|
|
1201
|
+
<meta charset="utf-8">
|
|
1202
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1203
|
+
<title>PI-KIRO — Authentication</title>
|
|
1204
|
+
<style>
|
|
1205
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
1206
|
+
body{background:#0a0a0a;color:#e5e5e5;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;text-align:center}
|
|
1207
|
+
.container{max-width:420px;padding:2rem}
|
|
1208
|
+
.logo{display:flex;align-items:center;justify-content:center;gap:16px;margin-bottom:48px}
|
|
1209
|
+
.logo-text{font-size:42px;font-weight:700;letter-spacing:6px;color:#7c3aed;font-family:'Courier New',monospace}
|
|
1210
|
+
.status-box{border:1.5px solid ${borderColor};border-radius:12px;padding:20px 28px;display:flex;align-items:flex-start;gap:14px;text-align:left;margin-bottom:20px;background:rgba(${kind === "success" ? "34,197,94" : "239,68,68"},0.04)}
|
|
1211
|
+
.status-icon{flex-shrink:0;margin-top:2px}
|
|
1212
|
+
.status-title{font-size:15px;font-weight:600;color:${borderColor};margin-bottom:4px}
|
|
1213
|
+
.status-msg{font-size:13px;color:#a3a3a3;line-height:1.4}
|
|
1214
|
+
.subtitle{font-size:13px;color:#737373;margin-top:4px}
|
|
1215
|
+
.subtitle strong{color:#a3a3a3}
|
|
1216
|
+
.countdown{position:relative;width:60px;height:60px;margin:24px auto 12px}
|
|
1217
|
+
.ring{width:60px;height:60px}
|
|
1218
|
+
.countdown-num{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:24px;font-weight:700;color:#22c55e;font-variant-numeric:tabular-nums}
|
|
1219
|
+
</style>
|
|
1220
|
+
</head>
|
|
1221
|
+
<body>
|
|
1222
|
+
<div class="container">
|
|
1223
|
+
<div class="logo">
|
|
1224
|
+
${ghostSvg}
|
|
1225
|
+
<span class="logo-text">PI-KIRO</span>
|
|
1226
|
+
</div>
|
|
1227
|
+
<div class="status-box">
|
|
1228
|
+
<span class="status-icon">${iconSvg}</span>
|
|
1229
|
+
<div>
|
|
1230
|
+
<div class="status-title">${title}</div>
|
|
1231
|
+
<div class="status-msg">${message}</div>
|
|
1232
|
+
</div>
|
|
1233
|
+
</div>
|
|
1234
|
+
${countdownHtml}
|
|
1235
|
+
</div>
|
|
1236
|
+
</body>
|
|
1237
|
+
</html>`;
|
|
1238
|
+
}
|
|
1239
|
+
function oauthIdcDelegationPage() {
|
|
1240
|
+
const ghostSvg = `<svg width="56" height="56" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
1241
|
+
<path d="M50 5C28.5 5 11 22.5 11 44v36c0 2.8 1.2 5.4 3.2 7.2 2 1.8 4.7 2.6 7.4 2.2l3.4-.5c3.2-.5 6.5.4 9 2.5l2.2 1.8c2.4 2 5.4 3 8.5 3h10.6c3.1 0 6.1-1.1 8.5-3l2.2-1.8c2.5-2.1 5.8-3 9-2.5l3.4.5c2.7.4 5.4-.4 7.4-2.2 2-1.8 3.2-4.4 3.2-7.2V44C89 22.5 71.5 5 50 5z" fill="white"/>
|
|
1242
|
+
<circle cx="37" cy="45" r="7" fill="#0a0a0a"/>
|
|
1243
|
+
<circle cx="63" cy="45" r="7" fill="#0a0a0a"/>
|
|
1244
|
+
</svg>`;
|
|
1245
|
+
return `<!DOCTYPE html>
|
|
1246
|
+
<html lang="en">
|
|
1247
|
+
<head>
|
|
1248
|
+
<meta charset="utf-8">
|
|
1249
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1250
|
+
<title>PI-KIRO — Enterprise Sign-In</title>
|
|
1251
|
+
<style>
|
|
1252
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
1253
|
+
body{background:#0a0a0a;color:#e5e5e5;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;text-align:center}
|
|
1254
|
+
.container{max-width:420px;padding:2rem}
|
|
1255
|
+
.logo{display:flex;align-items:center;justify-content:center;gap:16px;margin-bottom:48px}
|
|
1256
|
+
.logo-text{font-size:42px;font-weight:700;letter-spacing:6px;color:#7c3aed;font-family:'Courier New',monospace}
|
|
1257
|
+
.status-box{border:1.5px solid #22c55e;border-radius:12px;padding:20px 28px;display:flex;align-items:flex-start;gap:14px;text-align:left;margin-bottom:20px;background:rgba(34,197,94,0.04)}
|
|
1258
|
+
.status-icon{flex-shrink:0;margin-top:2px}
|
|
1259
|
+
.status-title{font-size:15px;font-weight:600;color:#22c55e;margin-bottom:4px}
|
|
1260
|
+
.status-msg{font-size:13px;color:#a3a3a3;line-height:1.4}
|
|
1261
|
+
.subtitle{font-size:13px;color:#737373;margin-top:4px}
|
|
1262
|
+
.subtitle strong{color:#a3a3a3}
|
|
1263
|
+
.spinner{width:28px;height:28px;border:3px solid #1a1a1a;border-top-color:#22c55e;border-radius:50%;animation:spin 0.8s linear infinite;margin:24px auto 12px}
|
|
1264
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
1265
|
+
.countdown{position:relative;width:60px;height:60px;margin:24px auto 12px;display:none}
|
|
1266
|
+
.ring{width:60px;height:60px}
|
|
1267
|
+
.countdown-num{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:24px;font-weight:700;color:#22c55e;font-variant-numeric:tabular-nums}
|
|
1268
|
+
</style>
|
|
1269
|
+
</head>
|
|
1270
|
+
<body>
|
|
1271
|
+
<div class="container">
|
|
1272
|
+
<div class="logo">
|
|
1273
|
+
${ghostSvg}
|
|
1274
|
+
<span class="logo-text">PI-KIRO</span>
|
|
1275
|
+
</div>
|
|
1276
|
+
<div class="status-box">
|
|
1277
|
+
<span class="status-icon"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M16.67 5L7.5 14.17 3.33 10" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
|
|
1278
|
+
<div>
|
|
1279
|
+
<div class="status-title">Enterprise sign-in</div>
|
|
1280
|
+
<div class="status-msg" id="status-msg">Preparing device authorization…</div>
|
|
1281
|
+
</div>
|
|
1282
|
+
</div>
|
|
1283
|
+
<div class="spinner" id="spinner"></div>
|
|
1284
|
+
<div class="countdown" id="countdown">
|
|
1285
|
+
<svg class="ring" viewBox="0 0 60 60">
|
|
1286
|
+
<circle cx="30" cy="30" r="26" stroke="#1a1a1a" stroke-width="3" fill="none"/>
|
|
1287
|
+
<circle id="ring-progress" cx="30" cy="30" r="26" stroke="#22c55e" stroke-width="3" fill="none"
|
|
1288
|
+
stroke-dasharray="163.36" stroke-dashoffset="0" stroke-linecap="round"
|
|
1289
|
+
transform="rotate(-90 30 30)" style="transition:stroke-dashoffset 1s linear"/>
|
|
1290
|
+
</svg>
|
|
1291
|
+
<span class="countdown-num" id="countdown-num">3</span>
|
|
1292
|
+
</div>
|
|
1293
|
+
<p class="subtitle" id="subtitle">Waiting for device authorization…</p>
|
|
1294
|
+
<script>
|
|
1295
|
+
(function(){
|
|
1296
|
+
var msg=document.getElementById('status-msg'),
|
|
1297
|
+
spinner=document.getElementById('spinner'),
|
|
1298
|
+
cd=document.getElementById('countdown'),
|
|
1299
|
+
cdNum=document.getElementById('countdown-num'),
|
|
1300
|
+
ring=document.getElementById('ring-progress'),
|
|
1301
|
+
sub=document.getElementById('subtitle'),
|
|
1302
|
+
circ=163.36;
|
|
1303
|
+
|
|
1304
|
+
function poll(){
|
|
1305
|
+
fetch('/idc-verify').then(function(r){return r.json()}).then(function(d){
|
|
1306
|
+
if(d.url){
|
|
1307
|
+
spinner.style.display='none';
|
|
1308
|
+
cd.style.display='block';
|
|
1309
|
+
msg.textContent='Device authorization ready';
|
|
1310
|
+
try{sub.innerHTML='Redirecting to <strong>'+new URL(d.url).hostname+'</strong>…'}catch(e){}
|
|
1311
|
+
countdown(3,d.url);
|
|
1312
|
+
} else {
|
|
1313
|
+
setTimeout(poll,500);
|
|
1314
|
+
}
|
|
1315
|
+
}).catch(function(){setTimeout(poll,1000)});
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
function countdown(n,url){
|
|
1319
|
+
if(n<=0){window.location.href=url;return}
|
|
1320
|
+
cdNum.textContent=n;
|
|
1321
|
+
ring.setAttribute('stroke-dashoffset',String(circ*(1-n/3)));
|
|
1322
|
+
setTimeout(function(){countdown(n-1,url)},1000);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
poll();
|
|
1326
|
+
})();
|
|
1327
|
+
</script>
|
|
1328
|
+
</div>
|
|
1329
|
+
</body>
|
|
1330
|
+
</html>`;
|
|
1331
|
+
}
|
|
1332
|
+
function startCallbackServer(expectedState) {
|
|
1333
|
+
return new Promise((resolve2, reject) => {
|
|
1334
|
+
let settleWait;
|
|
1335
|
+
const waitForCodePromise = new Promise((res) => {
|
|
1336
|
+
settleWait = res;
|
|
1337
|
+
});
|
|
1338
|
+
let idcVerifyUrl = null;
|
|
1339
|
+
const server = createServer((req, res) => {
|
|
1340
|
+
try {
|
|
1341
|
+
const url = new URL(req.url ?? "", SOCIAL_REDIRECT_URI);
|
|
1342
|
+
if (url.pathname === "/idc-verify") {
|
|
1343
|
+
res.writeHead(200, {
|
|
1344
|
+
"Content-Type": "application/json",
|
|
1345
|
+
"Access-Control-Allow-Origin": "*",
|
|
1346
|
+
"Cache-Control": "no-store"
|
|
1347
|
+
});
|
|
1348
|
+
res.end(JSON.stringify({ url: idcVerifyUrl }));
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
const code = url.searchParams.get("code");
|
|
1352
|
+
const state = url.searchParams.get("state");
|
|
1353
|
+
const loginOption = url.searchParams.get("login_option");
|
|
1354
|
+
const issuerUrl = url.searchParams.get("issuer_url");
|
|
1355
|
+
const idcRegion = url.searchParams.get("idc_region");
|
|
1356
|
+
const isIdcDelegation = loginOption === "awsidc" && !!issuerUrl && !!state;
|
|
1357
|
+
if (!state || !code && !isIdcDelegation) {
|
|
1358
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1359
|
+
res.end("");
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
if (state !== expectedState) {
|
|
1363
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
1364
|
+
res.end(oauthCallbackPage("error", "State mismatch", "The OAuth state parameter did not match. Please try logging in again."));
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1368
|
+
if (isIdcDelegation) {
|
|
1369
|
+
res.end(oauthIdcDelegationPage());
|
|
1370
|
+
} else {
|
|
1371
|
+
res.end(oauthCallbackPage("success", "Request approved", "PI-KIRO has been given requested permissions."));
|
|
1372
|
+
}
|
|
1373
|
+
settleWait?.({
|
|
1374
|
+
code,
|
|
1375
|
+
state,
|
|
1376
|
+
callbackPath: url.pathname,
|
|
1377
|
+
loginOption,
|
|
1378
|
+
issuerUrl: issuerUrl ?? undefined,
|
|
1379
|
+
idcRegion: idcRegion ?? undefined
|
|
1380
|
+
});
|
|
1381
|
+
} catch {
|
|
1382
|
+
res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
|
|
1383
|
+
res.end("Internal error");
|
|
1384
|
+
}
|
|
1385
|
+
});
|
|
1386
|
+
server.on("error", (err) => {
|
|
1387
|
+
reject(err);
|
|
1388
|
+
});
|
|
1389
|
+
server.listen(SOCIAL_REDIRECT_PORT, "localhost", () => {
|
|
1390
|
+
resolve2({
|
|
1391
|
+
server,
|
|
1392
|
+
redirectUri: SOCIAL_REDIRECT_URI,
|
|
1393
|
+
cancelWait: () => {
|
|
1394
|
+
settleWait?.(null);
|
|
1395
|
+
},
|
|
1396
|
+
waitForCode: () => waitForCodePromise,
|
|
1397
|
+
setIdcVerifyUrl: (url) => {
|
|
1398
|
+
idcVerifyUrl = url;
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
});
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
async function runSocialSignInFlow(callbacks) {
|
|
1405
|
+
const verifier = generateCodeVerifier();
|
|
1406
|
+
const challenge = generateCodeChallenge(verifier);
|
|
1407
|
+
const state = generateRandomState();
|
|
1408
|
+
const callbackServer = await startCallbackServer(state);
|
|
1409
|
+
try {
|
|
1410
|
+
const signInUrl = buildSocialSignInURL(callbackServer.redirectUri, challenge, state);
|
|
1411
|
+
callbacks.onAuth({
|
|
1412
|
+
url: signInUrl,
|
|
1413
|
+
instructions: "Complete sign-in in your browser."
|
|
1414
|
+
});
|
|
1415
|
+
callbacks.onProgress?.("Waiting for browser sign-in…");
|
|
1416
|
+
let code;
|
|
1417
|
+
let tokenRedirectUri = SOCIAL_REDIRECT_URI;
|
|
1418
|
+
let callbackResult = null;
|
|
1419
|
+
if (callbacks.onManualCodeInput) {
|
|
1420
|
+
let manualInput;
|
|
1421
|
+
let manualError;
|
|
1422
|
+
const manualPromise = callbacks.onManualCodeInput().then((input) => {
|
|
1423
|
+
manualInput = input;
|
|
1424
|
+
callbackServer.cancelWait();
|
|
1425
|
+
}).catch((err) => {
|
|
1426
|
+
manualError = err instanceof Error ? err : new Error(String(err));
|
|
1427
|
+
callbackServer.cancelWait();
|
|
1428
|
+
});
|
|
1429
|
+
callbackResult = await callbackServer.waitForCode();
|
|
1430
|
+
if (manualError)
|
|
1431
|
+
throw manualError;
|
|
1432
|
+
if (callbackResult?.loginOption !== "awsidc" || !callbackResult.issuerUrl) {
|
|
1433
|
+
if (callbackResult?.code) {
|
|
1434
|
+
code = callbackResult.code;
|
|
1435
|
+
tokenRedirectUri = buildTokenRedirectUri(callbackResult.callbackPath, callbackResult.loginOption);
|
|
1436
|
+
} else if (manualInput) {
|
|
1437
|
+
code = parseAuthRedirectInput(manualInput).code;
|
|
1438
|
+
}
|
|
1439
|
+
if (!code) {
|
|
1440
|
+
await manualPromise;
|
|
1441
|
+
if (manualError)
|
|
1442
|
+
throw manualError;
|
|
1443
|
+
if (manualInput) {
|
|
1444
|
+
code = parseAuthRedirectInput(manualInput).code;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
} else {
|
|
1449
|
+
callbackResult = await callbackServer.waitForCode();
|
|
1450
|
+
if (callbackResult?.code) {
|
|
1451
|
+
code = callbackResult.code;
|
|
1452
|
+
tokenRedirectUri = buildTokenRedirectUri(callbackResult.callbackPath, callbackResult.loginOption);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
if (callbackResult?.loginOption === "awsidc" && callbackResult.issuerUrl) {
|
|
1456
|
+
callbacks.onProgress?.("Enterprise sign-in detected — starting device authorization…");
|
|
1457
|
+
const idcRegion = callbackResult.idcRegion || BUILDER_ID_REGION;
|
|
1458
|
+
const wrappedCallbacks = {
|
|
1459
|
+
...callbacks,
|
|
1460
|
+
onAuth: (info) => {
|
|
1461
|
+
callbackServer.setIdcVerifyUrl(info.url);
|
|
1462
|
+
if (info.instructions) {
|
|
1463
|
+
callbacks.onProgress?.(info.instructions);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
};
|
|
1467
|
+
try {
|
|
1468
|
+
return await runDeviceCodeFlow(wrappedCallbacks, callbackResult.issuerUrl, [idcRegion], "idc");
|
|
1469
|
+
} finally {
|
|
1470
|
+
callbackServer.server.close();
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
if (!code) {
|
|
1474
|
+
const input = await callbacks.onPrompt({
|
|
1475
|
+
message: "Paste the authorization code or the full redirect URL:",
|
|
1476
|
+
placeholder: SOCIAL_REDIRECT_URI
|
|
1477
|
+
});
|
|
1478
|
+
code = parseAuthRedirectInput(input).code;
|
|
1479
|
+
}
|
|
1480
|
+
if (!code) {
|
|
1481
|
+
throw new Error("Missing authorization code — sign-in was not completed");
|
|
1482
|
+
}
|
|
1483
|
+
callbacks.onProgress?.("Exchanging authorization code…");
|
|
1484
|
+
const resp = await fetch(`${KIRO_SOCIAL_AUTH_ENDPOINT}/oauth/token`, {
|
|
1485
|
+
method: "POST",
|
|
1486
|
+
headers: { "Content-Type": "application/json", "User-Agent": "pi-kiro" },
|
|
1487
|
+
body: JSON.stringify({
|
|
1488
|
+
code,
|
|
1489
|
+
code_verifier: verifier,
|
|
1490
|
+
redirect_uri: tokenRedirectUri
|
|
1491
|
+
})
|
|
1492
|
+
});
|
|
1493
|
+
if (!resp.ok) {
|
|
1494
|
+
const body = await resp.text().catch(() => "");
|
|
1495
|
+
throw new Error(`Token exchange failed: ${resp.status} ${body}`);
|
|
1496
|
+
}
|
|
1497
|
+
const data = await resp.json();
|
|
1498
|
+
if (!data.accessToken || !data.refreshToken) {
|
|
1499
|
+
throw new Error("Token exchange returned no tokens");
|
|
1500
|
+
}
|
|
1501
|
+
if (data.profileArn) {
|
|
1502
|
+
try {
|
|
1503
|
+
const apiRegion = resolveApiRegion(BUILDER_ID_REGION);
|
|
1504
|
+
const apiModels = await fetchAvailableModels(data.accessToken, apiRegion, data.profileArn);
|
|
1505
|
+
setCachedDynamicModels(buildModelsFromApi(apiModels));
|
|
1506
|
+
log.info(`Fetched and cached ${apiModels.length} models after social sign-in`);
|
|
1507
|
+
} catch (err) {
|
|
1508
|
+
log.warn(`Failed to fetch models after social sign-in: ${err}`);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
return {
|
|
1512
|
+
refresh: `${data.refreshToken}|||social`,
|
|
1513
|
+
access: data.accessToken,
|
|
1514
|
+
expires: Date.now() + (data.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS,
|
|
1515
|
+
clientId: "",
|
|
1516
|
+
clientSecret: "",
|
|
1517
|
+
region: BUILDER_ID_REGION,
|
|
1518
|
+
authMethod: "social",
|
|
1519
|
+
profileArn: data.profileArn
|
|
1520
|
+
};
|
|
1521
|
+
} finally {
|
|
1522
|
+
callbackServer.server.close();
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1092
1525
|
async function loginKiro(callbacks) {
|
|
1093
1526
|
const method = await callbacks.onSelect({
|
|
1094
1527
|
message: "Select login method:",
|
|
@@ -1108,7 +1541,7 @@ async function loginKiro(callbacks) {
|
|
|
1108
1541
|
return loginDesktopManual(callbacks);
|
|
1109
1542
|
}
|
|
1110
1543
|
if (method === "builder-id") {
|
|
1111
|
-
return
|
|
1544
|
+
return runSocialSignInFlow(callbacks);
|
|
1112
1545
|
}
|
|
1113
1546
|
const startUrl = (await callbacks.onPrompt({
|
|
1114
1547
|
message: "Paste your IAM Identity Center start URL:",
|
|
@@ -1205,6 +1638,27 @@ Complete authorization within 10 minutes.`
|
|
|
1205
1638
|
if (!tok.accessToken || !tok.refreshToken) {
|
|
1206
1639
|
throw new Error("Authorization completed but no tokens returned");
|
|
1207
1640
|
}
|
|
1641
|
+
callbacks.onProgress?.("Resolving Kiro profile…");
|
|
1642
|
+
let profileArn;
|
|
1643
|
+
try {
|
|
1644
|
+
const apiRegion = resolveApiRegion(detectedRegion);
|
|
1645
|
+
const resolved = await resolveProfileArn(tok.accessToken, apiRegion);
|
|
1646
|
+
if (resolved) {
|
|
1647
|
+
profileArn = resolved;
|
|
1648
|
+
log.info(`Resolved profileArn during login: ${profileArn}`);
|
|
1649
|
+
try {
|
|
1650
|
+
const apiModels = await fetchAvailableModels(tok.accessToken, apiRegion, profileArn);
|
|
1651
|
+
setCachedDynamicModels(buildModelsFromApi(apiModels));
|
|
1652
|
+
log.info(`Fetched and cached ${apiModels.length} models after login`);
|
|
1653
|
+
} catch (err) {
|
|
1654
|
+
log.warn(`Failed to fetch models during login: ${err}`);
|
|
1655
|
+
}
|
|
1656
|
+
} else {
|
|
1657
|
+
log.warn("Could not resolve profileArn during login");
|
|
1658
|
+
}
|
|
1659
|
+
} catch (err) {
|
|
1660
|
+
log.warn(`Failed to resolve profileArn during login: ${err}`);
|
|
1661
|
+
}
|
|
1208
1662
|
return {
|
|
1209
1663
|
refresh: `${tok.refreshToken}|${result.clientId}|${result.clientSecret}|${authMethod}`,
|
|
1210
1664
|
access: tok.accessToken,
|
|
@@ -1212,7 +1666,8 @@ Complete authorization within 10 minutes.`
|
|
|
1212
1666
|
clientId: result.clientId,
|
|
1213
1667
|
clientSecret: result.clientSecret,
|
|
1214
1668
|
region: detectedRegion,
|
|
1215
|
-
authMethod
|
|
1669
|
+
authMethod,
|
|
1670
|
+
...profileArn ? { profileArn } : {}
|
|
1216
1671
|
};
|
|
1217
1672
|
}
|
|
1218
1673
|
async function syncBackToKiroCli(result) {
|
|
@@ -1226,7 +1681,7 @@ async function syncBackToKiroCli(result) {
|
|
|
1226
1681
|
accessToken: result.access,
|
|
1227
1682
|
refreshToken: result.refresh.split("|")[0] ?? "",
|
|
1228
1683
|
region: result.region,
|
|
1229
|
-
authMethod: result.authMethod === "builder-id" ? "
|
|
1684
|
+
authMethod: result.authMethod === "builder-id" || result.authMethod === "social" ? "desktop" : result.authMethod,
|
|
1230
1685
|
source: result.kiroSyncSource,
|
|
1231
1686
|
tokenKey: result.kiroSyncTokenKey
|
|
1232
1687
|
});
|
|
@@ -1263,10 +1718,10 @@ async function refreshTokenInner(credentials) {
|
|
|
1263
1718
|
if (!refreshToken || !region) {
|
|
1264
1719
|
throw new Error("Refresh token is missing region — re-login required");
|
|
1265
1720
|
}
|
|
1266
|
-
if (authMethod !== "desktop" && (!clientId || !clientSecret)) {
|
|
1721
|
+
if (authMethod !== "desktop" && authMethod !== "social" && (!clientId || !clientSecret)) {
|
|
1267
1722
|
throw new Error("Refresh token is missing clientId/clientSecret — re-login required");
|
|
1268
1723
|
}
|
|
1269
|
-
if (authMethod === "desktop") {
|
|
1724
|
+
if (authMethod === "desktop" || authMethod === "social") {
|
|
1270
1725
|
const desktopEndpoint = `https://prod.${region}.auth.desktop.kiro.dev/refreshToken`;
|
|
1271
1726
|
const resp2 = await fetch(desktopEndpoint, {
|
|
1272
1727
|
method: "POST",
|
|
@@ -1278,10 +1733,23 @@ async function refreshTokenInner(credentials) {
|
|
|
1278
1733
|
throw new Error(`Desktop token refresh failed: ${resp2.status} ${body}`);
|
|
1279
1734
|
}
|
|
1280
1735
|
const data2 = await resp2.json();
|
|
1281
|
-
|
|
1736
|
+
let profileArn2 = credentials.profileArn;
|
|
1737
|
+
if (!profileArn2) {
|
|
1282
1738
|
try {
|
|
1283
1739
|
const apiRegion = resolveApiRegion(region);
|
|
1284
|
-
const
|
|
1740
|
+
const resolved = await resolveProfileArn(data2.accessToken, apiRegion);
|
|
1741
|
+
if (resolved) {
|
|
1742
|
+
profileArn2 = resolved;
|
|
1743
|
+
log.info(`Resolved profileArn during desktop refresh: ${profileArn2}`);
|
|
1744
|
+
}
|
|
1745
|
+
} catch (err) {
|
|
1746
|
+
log.warn(`Failed to resolve profileArn during desktop refresh: ${err}`);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
if (profileArn2) {
|
|
1750
|
+
try {
|
|
1751
|
+
const apiRegion = resolveApiRegion(region);
|
|
1752
|
+
const apiModels = await fetchAvailableModels(data2.accessToken, apiRegion, profileArn2);
|
|
1285
1753
|
setCachedDynamicModels(buildModelsFromApi(apiModels));
|
|
1286
1754
|
log.info(`Fetched and cached ${apiModels.length} models after desktop token refresh`);
|
|
1287
1755
|
} catch (err) {
|
|
@@ -1289,14 +1757,14 @@ async function refreshTokenInner(credentials) {
|
|
|
1289
1757
|
}
|
|
1290
1758
|
}
|
|
1291
1759
|
return {
|
|
1292
|
-
refresh: `${data2.refreshToken}
|
|
1760
|
+
refresh: `${data2.refreshToken}|||${authMethod}`,
|
|
1293
1761
|
access: data2.accessToken,
|
|
1294
1762
|
expires: Date.now() + (data2.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS,
|
|
1295
1763
|
clientId: "",
|
|
1296
1764
|
clientSecret: "",
|
|
1297
1765
|
region,
|
|
1298
|
-
authMethod
|
|
1299
|
-
profileArn:
|
|
1766
|
+
authMethod,
|
|
1767
|
+
profileArn: profileArn2,
|
|
1300
1768
|
kiroSyncSource: credentials.kiroSyncSource,
|
|
1301
1769
|
kiroSyncTokenKey: credentials.kiroSyncTokenKey
|
|
1302
1770
|
};
|
|
@@ -1312,10 +1780,23 @@ async function refreshTokenInner(credentials) {
|
|
|
1312
1780
|
throw new Error(`Token refresh failed: ${resp.status} ${body}`);
|
|
1313
1781
|
}
|
|
1314
1782
|
const data = await resp.json();
|
|
1315
|
-
|
|
1783
|
+
let profileArn = credentials.profileArn;
|
|
1784
|
+
if (!profileArn) {
|
|
1785
|
+
try {
|
|
1786
|
+
const apiRegion = resolveApiRegion(region);
|
|
1787
|
+
const resolved = await resolveProfileArn(data.accessToken, apiRegion);
|
|
1788
|
+
if (resolved) {
|
|
1789
|
+
profileArn = resolved;
|
|
1790
|
+
log.info(`Resolved profileArn during OIDC refresh: ${profileArn}`);
|
|
1791
|
+
}
|
|
1792
|
+
} catch (err) {
|
|
1793
|
+
log.warn(`Failed to resolve profileArn during OIDC refresh: ${err}`);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
if (profileArn) {
|
|
1316
1797
|
try {
|
|
1317
1798
|
const apiRegion = resolveApiRegion(region);
|
|
1318
|
-
const apiModels = await fetchAvailableModels(data.accessToken, apiRegion,
|
|
1799
|
+
const apiModels = await fetchAvailableModels(data.accessToken, apiRegion, profileArn);
|
|
1319
1800
|
setCachedDynamicModels(buildModelsFromApi(apiModels));
|
|
1320
1801
|
log.info(`Fetched and cached ${apiModels.length} models after token refresh`);
|
|
1321
1802
|
} catch (err) {
|
|
@@ -1330,15 +1811,15 @@ async function refreshTokenInner(credentials) {
|
|
|
1330
1811
|
clientSecret,
|
|
1331
1812
|
region,
|
|
1332
1813
|
authMethod,
|
|
1333
|
-
profileArn
|
|
1814
|
+
profileArn,
|
|
1334
1815
|
kiroSyncSource: credentials.kiroSyncSource,
|
|
1335
1816
|
kiroSyncTokenKey: credentials.kiroSyncTokenKey
|
|
1336
1817
|
};
|
|
1337
1818
|
}
|
|
1338
1819
|
async function refreshKiroToken(credentials) {
|
|
1339
1820
|
const inputMethod = credentials.authMethod;
|
|
1340
|
-
const authMethod = inputMethod === "builder-id" || inputMethod === "idc" || inputMethod === "desktop" ? inputMethod : "idc";
|
|
1341
|
-
if (inputMethod !== undefined && inputMethod !== "builder-id" && inputMethod !== "idc" && inputMethod !== "desktop") {
|
|
1821
|
+
const authMethod = inputMethod === "builder-id" || inputMethod === "idc" || inputMethod === "desktop" || inputMethod === "social" ? inputMethod : "idc";
|
|
1822
|
+
if (inputMethod !== undefined && inputMethod !== "builder-id" && inputMethod !== "idc" && inputMethod !== "desktop" && inputMethod !== "social") {
|
|
1342
1823
|
log.warn(`refreshKiroToken: unrecognized authMethod "${String(inputMethod)}" — defaulting to "idc"`);
|
|
1343
1824
|
}
|
|
1344
1825
|
const baseCreds = {
|
|
@@ -1868,7 +2349,7 @@ function countTokens(text) {
|
|
|
1868
2349
|
}
|
|
1869
2350
|
|
|
1870
2351
|
// src/transform.ts
|
|
1871
|
-
import { createHash } from "node:crypto";
|
|
2352
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1872
2353
|
function normalizeMessages(messages) {
|
|
1873
2354
|
return messages.filter((msg) => msg.role !== "assistant" || msg.stopReason !== "error" && msg.stopReason !== "aborted");
|
|
1874
2355
|
}
|
|
@@ -1921,7 +2402,7 @@ var KIRO_TOOL_USE_ID_RE = /^tooluse_[A-Za-z0-9]+$/;
|
|
|
1921
2402
|
function toKiroToolUseId(id) {
|
|
1922
2403
|
if (KIRO_TOOL_USE_ID_RE.test(id))
|
|
1923
2404
|
return id;
|
|
1924
|
-
const digest =
|
|
2405
|
+
const digest = createHash2("sha256").update(id).digest("hex").slice(0, 22);
|
|
1925
2406
|
return `tooluse_${digest}`;
|
|
1926
2407
|
}
|
|
1927
2408
|
function convertImagesToKiro(images) {
|
package/dist/extension.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,GAAG,EACH,2BAA2B,EAC3B,OAAO,EACP,KAAK,EACL,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,GAAG,EACH,2BAA2B,EAC3B,OAAO,EACP,KAAK,EACL,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAuB/B,UAAU,mBAAmB;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,CAAC,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;IAC5B,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/E,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;CAC3D;AAED,UAAU,cAAc;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,YAAY,CAAC,EAAE,CACb,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,mBAAmB,KAC1B,2BAA2B,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC/B,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,CAAC,SAAS,EAAE,mBAAmB,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACrE,YAAY,EAAE,CAAC,WAAW,EAAE,gBAAgB,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAC3E,SAAS,EAAE,CAAC,WAAW,EAAE,gBAAgB,KAAK,MAAM,CAAC;QACrD,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,WAAW,EAAE,gBAAgB,KAAK,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;KACtF,CAAC;CACH;AAED,UAAU,YAAY;IACpB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;CAC9D;AA4HD,yBAA+B,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAmG9D"}
|