@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 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 runDeviceCodeFlow(callbacks, BUILDER_ID_START_URL, [BUILDER_ID_REGION], "builder-id");
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" ? "idc" : result.authMethod,
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
- if (credentials.profileArn) {
1736
+ let profileArn2 = credentials.profileArn;
1737
+ if (!profileArn2) {
1282
1738
  try {
1283
1739
  const apiRegion = resolveApiRegion(region);
1284
- const apiModels = await fetchAvailableModels(data2.accessToken, apiRegion, credentials.profileArn);
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}|||desktop`,
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: "desktop",
1299
- profileArn: credentials.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
- if (credentials.profileArn) {
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, credentials.profileArn);
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: credentials.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 = createHash("sha256").update(id).digest("hex").slice(0, 22);
2405
+ const digest = createHash2("sha256").update(id).digest("hex").slice(0, 22);
1925
2406
  return `tooluse_${digest}`;
1926
2407
  }
1927
2408
  function convertImagesToKiro(images) {
@@ -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;AAsB/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;AA8GD,yBAA+B,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA+E9D"}
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"}