@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/dist/extension.js CHANGED
@@ -899,6 +899,24 @@ var kiroModels = [
899
899
  maxTokens: 65536
900
900
  }
901
901
  ];
902
+ async function resolveProfileArn(accessToken, apiRegion) {
903
+ const resp = await fetch(`https://management.${apiRegion}.kiro.dev/`, {
904
+ method: "POST",
905
+ headers: {
906
+ Authorization: `Bearer ${accessToken}`,
907
+ "Content-Type": "application/x-amz-json-1.0",
908
+ "X-Amz-Target": "AmazonCodeWhispererService.ListAvailableProfiles",
909
+ "User-Agent": "pi-kiro"
910
+ },
911
+ body: "{}"
912
+ });
913
+ if (!resp || !resp.ok)
914
+ return null;
915
+ const data = await resp.json();
916
+ const profiles = data.profiles ?? [];
917
+ const kiroProfile = profiles.find((p) => p.profileType === "KIRO" && p.status === "ACTIVE");
918
+ return kiroProfile?.arn ?? profiles[0]?.arn ?? null;
919
+ }
902
920
  async function fetchAvailableModels(accessToken, apiRegion, profileArn) {
903
921
  const url = `https://management.${apiRegion}.kiro.dev/?origin=KIRO_CLI&profileArn=${encodeURIComponent(profileArn)}`;
904
922
  const resp = await fetch(url, {
@@ -918,7 +936,9 @@ async function fetchAvailableModels(accessToken, apiRegion, profileArn) {
918
936
  },
919
937
  body: JSON.stringify({ origin: "KIRO_CLI", profileArn })
920
938
  });
921
- if (!resp.ok) {
939
+ if (!resp || !resp.ok) {
940
+ if (!resp)
941
+ throw new Error("ListAvailableModels failed: fetch returned no response");
922
942
  const body = await resp.text().catch(() => "");
923
943
  if (resp.status === 401 || resp.status === 400 && body.includes("Invalid token")) {
924
944
  throw new Error(`Authentication failed: 401 ListAvailableModels failed - ${body}`);
@@ -983,6 +1003,8 @@ function setCachedDynamicModels(models) {
983
1003
 
984
1004
  // src/oauth.ts
985
1005
  init_debug();
1006
+ import { createServer } from "node:http";
1007
+ import { randomBytes, createHash } from "node:crypto";
986
1008
  var BUILDER_ID_START_URL = "https://view.awsapps.com/start";
987
1009
  var BUILDER_ID_REGION = "us-east-1";
988
1010
  var SSO_SCOPES = [
@@ -1092,6 +1114,417 @@ async function pollForToken(oidcEndpoint, clientId, clientSecret, devAuth, signa
1092
1114
  }
1093
1115
  throw new Error("Authorization timed out");
1094
1116
  }
1117
+ var KIRO_SOCIAL_PORTAL = "https://app.kiro.dev";
1118
+ var KIRO_SOCIAL_AUTH_ENDPOINT = `https://prod.${BUILDER_ID_REGION}.auth.desktop.kiro.dev`;
1119
+ var SOCIAL_REDIRECT_PORT = 49153;
1120
+ var SOCIAL_REDIRECT_URI = `http://localhost:${SOCIAL_REDIRECT_PORT}`;
1121
+ function generateRandomState() {
1122
+ return randomBytes(16).toString("base64url");
1123
+ }
1124
+ function generateCodeVerifier() {
1125
+ return randomBytes(32).toString("base64url");
1126
+ }
1127
+ function generateCodeChallenge(verifier) {
1128
+ return createHash("sha256").update(verifier).digest("base64url");
1129
+ }
1130
+ function buildSocialSignInURL(redirectUri, codeChallenge, state) {
1131
+ const params = new URLSearchParams;
1132
+ params.set("code_challenge", codeChallenge);
1133
+ params.set("code_challenge_method", "S256");
1134
+ params.set("redirect_from", "kirocli");
1135
+ params.set("redirect_uri", redirectUri);
1136
+ params.set("state", state);
1137
+ return `${KIRO_SOCIAL_PORTAL}/signin?${params.toString()}`;
1138
+ }
1139
+ function parseAuthRedirectInput(input) {
1140
+ const trimmed = input.trim();
1141
+ if (!trimmed)
1142
+ return {};
1143
+ try {
1144
+ const url = new URL(trimmed);
1145
+ return {
1146
+ code: url.searchParams.get("code") ?? undefined,
1147
+ state: url.searchParams.get("state") ?? undefined
1148
+ };
1149
+ } catch {
1150
+ return { code: trimmed };
1151
+ }
1152
+ }
1153
+ function buildTokenRedirectUri(callbackPath, loginOption) {
1154
+ const path = callbackPath || "/oauth/callback";
1155
+ const base = `${SOCIAL_REDIRECT_URI}${path}`;
1156
+ if (loginOption) {
1157
+ return `${base}?login_option=${encodeURIComponent(loginOption)}`;
1158
+ }
1159
+ return base;
1160
+ }
1161
+ function oauthCallbackPage(kind, title, message, redirectUrl = "https://app.kiro.dev") {
1162
+ const borderColor = kind === "success" ? "#22c55e" : "#ef4444";
1163
+ 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>`;
1164
+ const ghostSvg = `<svg width="56" height="56" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
1165
+ <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"/>
1166
+ <circle cx="37" cy="45" r="7" fill="#0a0a0a"/>
1167
+ <circle cx="63" cy="45" r="7" fill="#0a0a0a"/>
1168
+ </svg>`;
1169
+ const redirectHost = (() => {
1170
+ try {
1171
+ return new URL(redirectUrl).hostname;
1172
+ } catch {
1173
+ return redirectUrl;
1174
+ }
1175
+ })();
1176
+ const countdownHtml = kind === "success" ? `
1177
+ <div class="countdown" id="countdown">
1178
+ <svg class="ring" viewBox="0 0 60 60">
1179
+ <circle cx="30" cy="30" r="26" stroke="#1a1a1a" stroke-width="3" fill="none"/>
1180
+ <circle id="ring-progress" cx="30" cy="30" r="26" stroke="#22c55e" stroke-width="3" fill="none"
1181
+ stroke-dasharray="163.36" stroke-dashoffset="0" stroke-linecap="round"
1182
+ transform="rotate(-90 30 30)" style="transition:stroke-dashoffset 1s linear"/>
1183
+ </svg>
1184
+ <span class="countdown-num" id="countdown-num">3</span>
1185
+ </div>
1186
+ <p class="subtitle">Redirecting to <strong>${redirectHost}</strong>…</p>
1187
+ <script>
1188
+ (function(){
1189
+ var n=3, el=document.getElementById('countdown-num'),
1190
+ ring=document.getElementById('ring-progress'), circ=163.36;
1191
+ function tick(){
1192
+ if(n<=0){window.location.href=${JSON.stringify(redirectUrl)};return}
1193
+ el.textContent=n;
1194
+ ring.setAttribute('stroke-dashoffset', String(circ*(1-n/3)));
1195
+ n--;
1196
+ setTimeout(tick,1000);
1197
+ }
1198
+ tick();
1199
+ })();
1200
+ </script>` : `<p class="subtitle">Please close this window and try again</p>`;
1201
+ return `<!DOCTYPE html>
1202
+ <html lang="en">
1203
+ <head>
1204
+ <meta charset="utf-8">
1205
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1206
+ <title>PI-KIRO — Authentication</title>
1207
+ <style>
1208
+ *{margin:0;padding:0;box-sizing:border-box}
1209
+ 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}
1210
+ .container{max-width:420px;padding:2rem}
1211
+ .logo{display:flex;align-items:center;justify-content:center;gap:16px;margin-bottom:48px}
1212
+ .logo-text{font-size:42px;font-weight:700;letter-spacing:6px;color:#7c3aed;font-family:'Courier New',monospace}
1213
+ .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)}
1214
+ .status-icon{flex-shrink:0;margin-top:2px}
1215
+ .status-title{font-size:15px;font-weight:600;color:${borderColor};margin-bottom:4px}
1216
+ .status-msg{font-size:13px;color:#a3a3a3;line-height:1.4}
1217
+ .subtitle{font-size:13px;color:#737373;margin-top:4px}
1218
+ .subtitle strong{color:#a3a3a3}
1219
+ .countdown{position:relative;width:60px;height:60px;margin:24px auto 12px}
1220
+ .ring{width:60px;height:60px}
1221
+ .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}
1222
+ </style>
1223
+ </head>
1224
+ <body>
1225
+ <div class="container">
1226
+ <div class="logo">
1227
+ ${ghostSvg}
1228
+ <span class="logo-text">PI-KIRO</span>
1229
+ </div>
1230
+ <div class="status-box">
1231
+ <span class="status-icon">${iconSvg}</span>
1232
+ <div>
1233
+ <div class="status-title">${title}</div>
1234
+ <div class="status-msg">${message}</div>
1235
+ </div>
1236
+ </div>
1237
+ ${countdownHtml}
1238
+ </div>
1239
+ </body>
1240
+ </html>`;
1241
+ }
1242
+ function oauthIdcDelegationPage() {
1243
+ const ghostSvg = `<svg width="56" height="56" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
1244
+ <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"/>
1245
+ <circle cx="37" cy="45" r="7" fill="#0a0a0a"/>
1246
+ <circle cx="63" cy="45" r="7" fill="#0a0a0a"/>
1247
+ </svg>`;
1248
+ return `<!DOCTYPE html>
1249
+ <html lang="en">
1250
+ <head>
1251
+ <meta charset="utf-8">
1252
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1253
+ <title>PI-KIRO — Enterprise Sign-In</title>
1254
+ <style>
1255
+ *{margin:0;padding:0;box-sizing:border-box}
1256
+ 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}
1257
+ .container{max-width:420px;padding:2rem}
1258
+ .logo{display:flex;align-items:center;justify-content:center;gap:16px;margin-bottom:48px}
1259
+ .logo-text{font-size:42px;font-weight:700;letter-spacing:6px;color:#7c3aed;font-family:'Courier New',monospace}
1260
+ .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)}
1261
+ .status-icon{flex-shrink:0;margin-top:2px}
1262
+ .status-title{font-size:15px;font-weight:600;color:#22c55e;margin-bottom:4px}
1263
+ .status-msg{font-size:13px;color:#a3a3a3;line-height:1.4}
1264
+ .subtitle{font-size:13px;color:#737373;margin-top:4px}
1265
+ .subtitle strong{color:#a3a3a3}
1266
+ .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}
1267
+ @keyframes spin{to{transform:rotate(360deg)}}
1268
+ .countdown{position:relative;width:60px;height:60px;margin:24px auto 12px;display:none}
1269
+ .ring{width:60px;height:60px}
1270
+ .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}
1271
+ </style>
1272
+ </head>
1273
+ <body>
1274
+ <div class="container">
1275
+ <div class="logo">
1276
+ ${ghostSvg}
1277
+ <span class="logo-text">PI-KIRO</span>
1278
+ </div>
1279
+ <div class="status-box">
1280
+ <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>
1281
+ <div>
1282
+ <div class="status-title">Enterprise sign-in</div>
1283
+ <div class="status-msg" id="status-msg">Preparing device authorization…</div>
1284
+ </div>
1285
+ </div>
1286
+ <div class="spinner" id="spinner"></div>
1287
+ <div class="countdown" id="countdown">
1288
+ <svg class="ring" viewBox="0 0 60 60">
1289
+ <circle cx="30" cy="30" r="26" stroke="#1a1a1a" stroke-width="3" fill="none"/>
1290
+ <circle id="ring-progress" cx="30" cy="30" r="26" stroke="#22c55e" stroke-width="3" fill="none"
1291
+ stroke-dasharray="163.36" stroke-dashoffset="0" stroke-linecap="round"
1292
+ transform="rotate(-90 30 30)" style="transition:stroke-dashoffset 1s linear"/>
1293
+ </svg>
1294
+ <span class="countdown-num" id="countdown-num">3</span>
1295
+ </div>
1296
+ <p class="subtitle" id="subtitle">Waiting for device authorization…</p>
1297
+ <script>
1298
+ (function(){
1299
+ var msg=document.getElementById('status-msg'),
1300
+ spinner=document.getElementById('spinner'),
1301
+ cd=document.getElementById('countdown'),
1302
+ cdNum=document.getElementById('countdown-num'),
1303
+ ring=document.getElementById('ring-progress'),
1304
+ sub=document.getElementById('subtitle'),
1305
+ circ=163.36;
1306
+
1307
+ function poll(){
1308
+ fetch('/idc-verify').then(function(r){return r.json()}).then(function(d){
1309
+ if(d.url){
1310
+ spinner.style.display='none';
1311
+ cd.style.display='block';
1312
+ msg.textContent='Device authorization ready';
1313
+ try{sub.innerHTML='Redirecting to <strong>'+new URL(d.url).hostname+'</strong>…'}catch(e){}
1314
+ countdown(3,d.url);
1315
+ } else {
1316
+ setTimeout(poll,500);
1317
+ }
1318
+ }).catch(function(){setTimeout(poll,1000)});
1319
+ }
1320
+
1321
+ function countdown(n,url){
1322
+ if(n<=0){window.location.href=url;return}
1323
+ cdNum.textContent=n;
1324
+ ring.setAttribute('stroke-dashoffset',String(circ*(1-n/3)));
1325
+ setTimeout(function(){countdown(n-1,url)},1000);
1326
+ }
1327
+
1328
+ poll();
1329
+ })();
1330
+ </script>
1331
+ </div>
1332
+ </body>
1333
+ </html>`;
1334
+ }
1335
+ function startCallbackServer(expectedState) {
1336
+ return new Promise((resolve2, reject) => {
1337
+ let settleWait;
1338
+ const waitForCodePromise = new Promise((res) => {
1339
+ settleWait = res;
1340
+ });
1341
+ let idcVerifyUrl = null;
1342
+ const server = createServer((req, res) => {
1343
+ try {
1344
+ const url = new URL(req.url ?? "", SOCIAL_REDIRECT_URI);
1345
+ if (url.pathname === "/idc-verify") {
1346
+ res.writeHead(200, {
1347
+ "Content-Type": "application/json",
1348
+ "Access-Control-Allow-Origin": "*",
1349
+ "Cache-Control": "no-store"
1350
+ });
1351
+ res.end(JSON.stringify({ url: idcVerifyUrl }));
1352
+ return;
1353
+ }
1354
+ const code = url.searchParams.get("code");
1355
+ const state = url.searchParams.get("state");
1356
+ const loginOption = url.searchParams.get("login_option");
1357
+ const issuerUrl = url.searchParams.get("issuer_url");
1358
+ const idcRegion = url.searchParams.get("idc_region");
1359
+ const isIdcDelegation = loginOption === "awsidc" && !!issuerUrl && !!state;
1360
+ if (!state || !code && !isIdcDelegation) {
1361
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1362
+ res.end("");
1363
+ return;
1364
+ }
1365
+ if (state !== expectedState) {
1366
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
1367
+ res.end(oauthCallbackPage("error", "State mismatch", "The OAuth state parameter did not match. Please try logging in again."));
1368
+ return;
1369
+ }
1370
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1371
+ if (isIdcDelegation) {
1372
+ res.end(oauthIdcDelegationPage());
1373
+ } else {
1374
+ res.end(oauthCallbackPage("success", "Request approved", "PI-KIRO has been given requested permissions."));
1375
+ }
1376
+ settleWait?.({
1377
+ code,
1378
+ state,
1379
+ callbackPath: url.pathname,
1380
+ loginOption,
1381
+ issuerUrl: issuerUrl ?? undefined,
1382
+ idcRegion: idcRegion ?? undefined
1383
+ });
1384
+ } catch {
1385
+ res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
1386
+ res.end("Internal error");
1387
+ }
1388
+ });
1389
+ server.on("error", (err) => {
1390
+ reject(err);
1391
+ });
1392
+ server.listen(SOCIAL_REDIRECT_PORT, "localhost", () => {
1393
+ resolve2({
1394
+ server,
1395
+ redirectUri: SOCIAL_REDIRECT_URI,
1396
+ cancelWait: () => {
1397
+ settleWait?.(null);
1398
+ },
1399
+ waitForCode: () => waitForCodePromise,
1400
+ setIdcVerifyUrl: (url) => {
1401
+ idcVerifyUrl = url;
1402
+ }
1403
+ });
1404
+ });
1405
+ });
1406
+ }
1407
+ async function runSocialSignInFlow(callbacks) {
1408
+ const verifier = generateCodeVerifier();
1409
+ const challenge = generateCodeChallenge(verifier);
1410
+ const state = generateRandomState();
1411
+ const callbackServer = await startCallbackServer(state);
1412
+ try {
1413
+ const signInUrl = buildSocialSignInURL(callbackServer.redirectUri, challenge, state);
1414
+ callbacks.onAuth({
1415
+ url: signInUrl,
1416
+ instructions: "Complete sign-in in your browser."
1417
+ });
1418
+ callbacks.onProgress?.("Waiting for browser sign-in…");
1419
+ let code;
1420
+ let tokenRedirectUri = SOCIAL_REDIRECT_URI;
1421
+ let callbackResult = null;
1422
+ if (callbacks.onManualCodeInput) {
1423
+ let manualInput;
1424
+ let manualError;
1425
+ const manualPromise = callbacks.onManualCodeInput().then((input) => {
1426
+ manualInput = input;
1427
+ callbackServer.cancelWait();
1428
+ }).catch((err) => {
1429
+ manualError = err instanceof Error ? err : new Error(String(err));
1430
+ callbackServer.cancelWait();
1431
+ });
1432
+ callbackResult = await callbackServer.waitForCode();
1433
+ if (manualError)
1434
+ throw manualError;
1435
+ if (callbackResult?.loginOption !== "awsidc" || !callbackResult.issuerUrl) {
1436
+ if (callbackResult?.code) {
1437
+ code = callbackResult.code;
1438
+ tokenRedirectUri = buildTokenRedirectUri(callbackResult.callbackPath, callbackResult.loginOption);
1439
+ } else if (manualInput) {
1440
+ code = parseAuthRedirectInput(manualInput).code;
1441
+ }
1442
+ if (!code) {
1443
+ await manualPromise;
1444
+ if (manualError)
1445
+ throw manualError;
1446
+ if (manualInput) {
1447
+ code = parseAuthRedirectInput(manualInput).code;
1448
+ }
1449
+ }
1450
+ }
1451
+ } else {
1452
+ callbackResult = await callbackServer.waitForCode();
1453
+ if (callbackResult?.code) {
1454
+ code = callbackResult.code;
1455
+ tokenRedirectUri = buildTokenRedirectUri(callbackResult.callbackPath, callbackResult.loginOption);
1456
+ }
1457
+ }
1458
+ if (callbackResult?.loginOption === "awsidc" && callbackResult.issuerUrl) {
1459
+ callbacks.onProgress?.("Enterprise sign-in detected — starting device authorization…");
1460
+ const idcRegion = callbackResult.idcRegion || BUILDER_ID_REGION;
1461
+ const wrappedCallbacks = {
1462
+ ...callbacks,
1463
+ onAuth: (info) => {
1464
+ callbackServer.setIdcVerifyUrl(info.url);
1465
+ if (info.instructions) {
1466
+ callbacks.onProgress?.(info.instructions);
1467
+ }
1468
+ }
1469
+ };
1470
+ try {
1471
+ return await runDeviceCodeFlow(wrappedCallbacks, callbackResult.issuerUrl, [idcRegion], "idc");
1472
+ } finally {
1473
+ callbackServer.server.close();
1474
+ }
1475
+ }
1476
+ if (!code) {
1477
+ const input = await callbacks.onPrompt({
1478
+ message: "Paste the authorization code or the full redirect URL:",
1479
+ placeholder: SOCIAL_REDIRECT_URI
1480
+ });
1481
+ code = parseAuthRedirectInput(input).code;
1482
+ }
1483
+ if (!code) {
1484
+ throw new Error("Missing authorization code — sign-in was not completed");
1485
+ }
1486
+ callbacks.onProgress?.("Exchanging authorization code…");
1487
+ const resp = await fetch(`${KIRO_SOCIAL_AUTH_ENDPOINT}/oauth/token`, {
1488
+ method: "POST",
1489
+ headers: { "Content-Type": "application/json", "User-Agent": "pi-kiro" },
1490
+ body: JSON.stringify({
1491
+ code,
1492
+ code_verifier: verifier,
1493
+ redirect_uri: tokenRedirectUri
1494
+ })
1495
+ });
1496
+ if (!resp.ok) {
1497
+ const body = await resp.text().catch(() => "");
1498
+ throw new Error(`Token exchange failed: ${resp.status} ${body}`);
1499
+ }
1500
+ const data = await resp.json();
1501
+ if (!data.accessToken || !data.refreshToken) {
1502
+ throw new Error("Token exchange returned no tokens");
1503
+ }
1504
+ if (data.profileArn) {
1505
+ try {
1506
+ const apiRegion = resolveApiRegion(BUILDER_ID_REGION);
1507
+ const apiModels = await fetchAvailableModels(data.accessToken, apiRegion, data.profileArn);
1508
+ setCachedDynamicModels(buildModelsFromApi(apiModels));
1509
+ log.info(`Fetched and cached ${apiModels.length} models after social sign-in`);
1510
+ } catch (err) {
1511
+ log.warn(`Failed to fetch models after social sign-in: ${err}`);
1512
+ }
1513
+ }
1514
+ return {
1515
+ refresh: `${data.refreshToken}|||social`,
1516
+ access: data.accessToken,
1517
+ expires: Date.now() + (data.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS,
1518
+ clientId: "",
1519
+ clientSecret: "",
1520
+ region: BUILDER_ID_REGION,
1521
+ authMethod: "social",
1522
+ profileArn: data.profileArn
1523
+ };
1524
+ } finally {
1525
+ callbackServer.server.close();
1526
+ }
1527
+ }
1095
1528
  async function loginKiro(callbacks) {
1096
1529
  const method = await callbacks.onSelect({
1097
1530
  message: "Select login method:",
@@ -1111,7 +1544,7 @@ async function loginKiro(callbacks) {
1111
1544
  return loginDesktopManual(callbacks);
1112
1545
  }
1113
1546
  if (method === "builder-id") {
1114
- return runDeviceCodeFlow(callbacks, BUILDER_ID_START_URL, [BUILDER_ID_REGION], "builder-id");
1547
+ return runSocialSignInFlow(callbacks);
1115
1548
  }
1116
1549
  const startUrl = (await callbacks.onPrompt({
1117
1550
  message: "Paste your IAM Identity Center start URL:",
@@ -1208,6 +1641,27 @@ Complete authorization within 10 minutes.`
1208
1641
  if (!tok.accessToken || !tok.refreshToken) {
1209
1642
  throw new Error("Authorization completed but no tokens returned");
1210
1643
  }
1644
+ callbacks.onProgress?.("Resolving Kiro profile…");
1645
+ let profileArn;
1646
+ try {
1647
+ const apiRegion = resolveApiRegion(detectedRegion);
1648
+ const resolved = await resolveProfileArn(tok.accessToken, apiRegion);
1649
+ if (resolved) {
1650
+ profileArn = resolved;
1651
+ log.info(`Resolved profileArn during login: ${profileArn}`);
1652
+ try {
1653
+ const apiModels = await fetchAvailableModels(tok.accessToken, apiRegion, profileArn);
1654
+ setCachedDynamicModels(buildModelsFromApi(apiModels));
1655
+ log.info(`Fetched and cached ${apiModels.length} models after login`);
1656
+ } catch (err) {
1657
+ log.warn(`Failed to fetch models during login: ${err}`);
1658
+ }
1659
+ } else {
1660
+ log.warn("Could not resolve profileArn during login");
1661
+ }
1662
+ } catch (err) {
1663
+ log.warn(`Failed to resolve profileArn during login: ${err}`);
1664
+ }
1211
1665
  return {
1212
1666
  refresh: `${tok.refreshToken}|${result.clientId}|${result.clientSecret}|${authMethod}`,
1213
1667
  access: tok.accessToken,
@@ -1215,7 +1669,8 @@ Complete authorization within 10 minutes.`
1215
1669
  clientId: result.clientId,
1216
1670
  clientSecret: result.clientSecret,
1217
1671
  region: detectedRegion,
1218
- authMethod
1672
+ authMethod,
1673
+ ...profileArn ? { profileArn } : {}
1219
1674
  };
1220
1675
  }
1221
1676
  async function syncBackToKiroCli(result) {
@@ -1229,7 +1684,7 @@ async function syncBackToKiroCli(result) {
1229
1684
  accessToken: result.access,
1230
1685
  refreshToken: result.refresh.split("|")[0] ?? "",
1231
1686
  region: result.region,
1232
- authMethod: result.authMethod === "builder-id" ? "idc" : result.authMethod,
1687
+ authMethod: result.authMethod === "builder-id" || result.authMethod === "social" ? "desktop" : result.authMethod,
1233
1688
  source: result.kiroSyncSource,
1234
1689
  tokenKey: result.kiroSyncTokenKey
1235
1690
  });
@@ -1266,10 +1721,10 @@ async function refreshTokenInner(credentials) {
1266
1721
  if (!refreshToken || !region) {
1267
1722
  throw new Error("Refresh token is missing region — re-login required");
1268
1723
  }
1269
- if (authMethod !== "desktop" && (!clientId || !clientSecret)) {
1724
+ if (authMethod !== "desktop" && authMethod !== "social" && (!clientId || !clientSecret)) {
1270
1725
  throw new Error("Refresh token is missing clientId/clientSecret — re-login required");
1271
1726
  }
1272
- if (authMethod === "desktop") {
1727
+ if (authMethod === "desktop" || authMethod === "social") {
1273
1728
  const desktopEndpoint = `https://prod.${region}.auth.desktop.kiro.dev/refreshToken`;
1274
1729
  const resp2 = await fetch(desktopEndpoint, {
1275
1730
  method: "POST",
@@ -1281,10 +1736,23 @@ async function refreshTokenInner(credentials) {
1281
1736
  throw new Error(`Desktop token refresh failed: ${resp2.status} ${body}`);
1282
1737
  }
1283
1738
  const data2 = await resp2.json();
1284
- if (credentials.profileArn) {
1739
+ let profileArn2 = credentials.profileArn;
1740
+ if (!profileArn2) {
1285
1741
  try {
1286
1742
  const apiRegion = resolveApiRegion(region);
1287
- const apiModels = await fetchAvailableModels(data2.accessToken, apiRegion, credentials.profileArn);
1743
+ const resolved = await resolveProfileArn(data2.accessToken, apiRegion);
1744
+ if (resolved) {
1745
+ profileArn2 = resolved;
1746
+ log.info(`Resolved profileArn during desktop refresh: ${profileArn2}`);
1747
+ }
1748
+ } catch (err) {
1749
+ log.warn(`Failed to resolve profileArn during desktop refresh: ${err}`);
1750
+ }
1751
+ }
1752
+ if (profileArn2) {
1753
+ try {
1754
+ const apiRegion = resolveApiRegion(region);
1755
+ const apiModels = await fetchAvailableModels(data2.accessToken, apiRegion, profileArn2);
1288
1756
  setCachedDynamicModels(buildModelsFromApi(apiModels));
1289
1757
  log.info(`Fetched and cached ${apiModels.length} models after desktop token refresh`);
1290
1758
  } catch (err) {
@@ -1292,14 +1760,14 @@ async function refreshTokenInner(credentials) {
1292
1760
  }
1293
1761
  }
1294
1762
  return {
1295
- refresh: `${data2.refreshToken}|||desktop`,
1763
+ refresh: `${data2.refreshToken}|||${authMethod}`,
1296
1764
  access: data2.accessToken,
1297
1765
  expires: Date.now() + (data2.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS,
1298
1766
  clientId: "",
1299
1767
  clientSecret: "",
1300
1768
  region,
1301
- authMethod: "desktop",
1302
- profileArn: credentials.profileArn,
1769
+ authMethod,
1770
+ profileArn: profileArn2,
1303
1771
  kiroSyncSource: credentials.kiroSyncSource,
1304
1772
  kiroSyncTokenKey: credentials.kiroSyncTokenKey
1305
1773
  };
@@ -1315,10 +1783,23 @@ async function refreshTokenInner(credentials) {
1315
1783
  throw new Error(`Token refresh failed: ${resp.status} ${body}`);
1316
1784
  }
1317
1785
  const data = await resp.json();
1318
- if (credentials.profileArn) {
1786
+ let profileArn = credentials.profileArn;
1787
+ if (!profileArn) {
1319
1788
  try {
1320
1789
  const apiRegion = resolveApiRegion(region);
1321
- const apiModels = await fetchAvailableModels(data.accessToken, apiRegion, credentials.profileArn);
1790
+ const resolved = await resolveProfileArn(data.accessToken, apiRegion);
1791
+ if (resolved) {
1792
+ profileArn = resolved;
1793
+ log.info(`Resolved profileArn during OIDC refresh: ${profileArn}`);
1794
+ }
1795
+ } catch (err) {
1796
+ log.warn(`Failed to resolve profileArn during OIDC refresh: ${err}`);
1797
+ }
1798
+ }
1799
+ if (profileArn) {
1800
+ try {
1801
+ const apiRegion = resolveApiRegion(region);
1802
+ const apiModels = await fetchAvailableModels(data.accessToken, apiRegion, profileArn);
1322
1803
  setCachedDynamicModels(buildModelsFromApi(apiModels));
1323
1804
  log.info(`Fetched and cached ${apiModels.length} models after token refresh`);
1324
1805
  } catch (err) {
@@ -1333,15 +1814,15 @@ async function refreshTokenInner(credentials) {
1333
1814
  clientSecret,
1334
1815
  region,
1335
1816
  authMethod,
1336
- profileArn: credentials.profileArn,
1817
+ profileArn,
1337
1818
  kiroSyncSource: credentials.kiroSyncSource,
1338
1819
  kiroSyncTokenKey: credentials.kiroSyncTokenKey
1339
1820
  };
1340
1821
  }
1341
1822
  async function refreshKiroToken(credentials) {
1342
1823
  const inputMethod = credentials.authMethod;
1343
- const authMethod = inputMethod === "builder-id" || inputMethod === "idc" || inputMethod === "desktop" ? inputMethod : "idc";
1344
- if (inputMethod !== undefined && inputMethod !== "builder-id" && inputMethod !== "idc" && inputMethod !== "desktop") {
1824
+ const authMethod = inputMethod === "builder-id" || inputMethod === "idc" || inputMethod === "desktop" || inputMethod === "social" ? inputMethod : "idc";
1825
+ if (inputMethod !== undefined && inputMethod !== "builder-id" && inputMethod !== "idc" && inputMethod !== "desktop" && inputMethod !== "social") {
1345
1826
  log.warn(`refreshKiroToken: unrecognized authMethod "${String(inputMethod)}" — defaulting to "idc"`);
1346
1827
  }
1347
1828
  const baseCreds = {
@@ -1872,7 +2353,7 @@ function countTokens(text) {
1872
2353
  }
1873
2354
 
1874
2355
  // src/transform.ts
1875
- import { createHash } from "node:crypto";
2356
+ import { createHash as createHash2 } from "node:crypto";
1876
2357
  function normalizeMessages(messages) {
1877
2358
  return messages.filter((msg) => msg.role !== "assistant" || msg.stopReason !== "error" && msg.stopReason !== "aborted");
1878
2359
  }
@@ -1925,7 +2406,7 @@ var KIRO_TOOL_USE_ID_RE = /^tooluse_[A-Za-z0-9]+$/;
1925
2406
  function toKiroToolUseId(id) {
1926
2407
  if (KIRO_TOOL_USE_ID_RE.test(id))
1927
2408
  return id;
1928
- const digest = createHash("sha256").update(id).digest("hex").slice(0, 22);
2409
+ const digest = createHash2("sha256").update(id).digest("hex").slice(0, 22);
1929
2410
  return `tooluse_${digest}`;
1930
2411
  }
1931
2412
  function convertImagesToKiro(images) {
@@ -3012,10 +3493,22 @@ function writeKiroCredentials(refreshed) {
3012
3493
  log.warn(`Failed to persist refreshed credentials: ${err}`);
3013
3494
  }
3014
3495
  }
3496
+ function writeKiroCredentialsPartial(fields) {
3497
+ try {
3498
+ const authPath = join2(homedir2(), ".pi", "agent", "auth.json");
3499
+ const raw = existsSync2(authPath) ? readFileSync2(authPath, "utf-8") : "{}";
3500
+ const data = JSON.parse(raw);
3501
+ const existing = data["kiro"] ?? {};
3502
+ data["kiro"] = { ...existing, ...fields };
3503
+ writeFileSync(authPath, JSON.stringify(data, null, 2), { mode: 384 });
3504
+ } catch (err) {
3505
+ log.warn(`Failed to persist partial credentials: ${err}`);
3506
+ }
3507
+ }
3015
3508
  async function extension_default(pi) {
3016
3509
  let modelDefs = toProviderModels(kiroModels);
3017
3510
  const creds = readKiroCredentials();
3018
- if (creds?.profileArn) {
3511
+ if (creds?.access || creds?.refresh) {
3019
3512
  let accessToken = creds.access;
3020
3513
  if (creds.refresh) {
3021
3514
  try {
@@ -3027,16 +3520,34 @@ async function extension_default(pi) {
3027
3520
  log.warn(`Startup token refresh failed, trying with existing token: ${err}`);
3028
3521
  }
3029
3522
  }
3030
- try {
3031
- const apiRegion = resolveApiRegion(creds.region);
3032
- seedProfileArn(creds.profileArn);
3033
- const apiModels = await fetchAvailableModels(accessToken, apiRegion, creds.profileArn);
3034
- const dynamicDefs = buildModelsFromApi(apiModels);
3035
- setCachedDynamicModels(dynamicDefs);
3036
- modelDefs = toProviderModels(dynamicDefs);
3037
- log.info(`Loaded ${modelDefs.length} models dynamically from Kiro API`);
3038
- } catch (err) {
3039
- log.warn(`Failed to fetch models at startup, using hardcoded fallback: ${err}`);
3523
+ let profileArn = creds.profileArn;
3524
+ if (!profileArn && accessToken) {
3525
+ try {
3526
+ const apiRegion = resolveApiRegion(creds.region);
3527
+ log.info("profileArn missing, resolving via ListAvailableProfiles…");
3528
+ profileArn = await resolveProfileArn(accessToken, apiRegion) ?? undefined;
3529
+ if (profileArn) {
3530
+ log.info(`Resolved profileArn: ${profileArn}`);
3531
+ writeKiroCredentialsPartial({ profileArn });
3532
+ } else {
3533
+ log.warn("Could not resolve profileArn — model fetch and streaming will fail");
3534
+ }
3535
+ } catch (err) {
3536
+ log.warn(`profileArn resolution failed: ${err}`);
3537
+ }
3538
+ }
3539
+ if (profileArn) {
3540
+ seedProfileArn(profileArn);
3541
+ try {
3542
+ const apiRegion = resolveApiRegion(creds.region);
3543
+ const apiModels = await fetchAvailableModels(accessToken, apiRegion, profileArn);
3544
+ const dynamicDefs = buildModelsFromApi(apiModels);
3545
+ setCachedDynamicModels(dynamicDefs);
3546
+ modelDefs = toProviderModels(dynamicDefs);
3547
+ log.info(`Loaded ${modelDefs.length} models dynamically from Kiro API`);
3548
+ } catch (err) {
3549
+ log.warn(`Failed to fetch models at startup, using hardcoded fallback: ${err}`);
3550
+ }
3040
3551
  }
3041
3552
  } else {
3042
3553
  log.warn("Run 'kiro login' to authenticate and fetch models dynamically. Note: This extension does not have the same authentication mechanism as other Kiro tools.");