@javargasm/opencode-kiro-auth 0.1.0 → 0.2.0

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/index.js CHANGED
@@ -1,12 +1,16 @@
1
1
  import { createRequire } from "node:module";
2
2
  var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
3
7
  var __export = (target, all) => {
4
8
  for (var name in all)
5
9
  __defProp(target, name, {
6
10
  get: all[name],
7
11
  enumerable: true,
8
12
  configurable: true,
9
- set: (newValue) => all[name] = () => newValue
13
+ set: __exportSetter.bind(all, name)
10
14
  });
11
15
  };
12
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -113,13 +117,430 @@ var init_debug = __esm(() => {
113
117
  };
114
118
  });
115
119
 
120
+ // src/models.ts
121
+ var exports_models = {};
122
+ __export(exports_models, {
123
+ setCachedDynamicModels: () => setCachedDynamicModels,
124
+ seedProfileArn: () => seedProfileArn,
125
+ resolveProfileArn: () => resolveProfileArn,
126
+ resolveKiroModel: () => resolveKiroModel,
127
+ resolveApiRegion: () => resolveApiRegion,
128
+ resetProfileArnCache: () => resetProfileArnCache,
129
+ kiroModels: () => kiroModels,
130
+ getCachedDynamicModels: () => getCachedDynamicModels,
131
+ fetchAvailableModels: () => fetchAvailableModels,
132
+ dotToDash: () => dotToDash,
133
+ buildModelsFromApi: () => buildModelsFromApi,
134
+ KIRO_MODEL_IDS: () => KIRO_MODEL_IDS
135
+ });
136
+ function dotToDash(modelId) {
137
+ return modelId.replace(/(\d)\.(\d)/g, "$1-$2");
138
+ }
139
+ function resolveKiroModel(modelId) {
140
+ const kiroId = modelId.replace(/(\d)-(\d)/g, "$1.$2");
141
+ if (!KIRO_MODEL_IDS.has(kiroId)) {
142
+ throw new Error(`Unknown Kiro model ID: ${modelId}`);
143
+ }
144
+ return kiroId;
145
+ }
146
+ function resolveApiRegion(ssoRegion) {
147
+ if (!ssoRegion)
148
+ return "us-east-1";
149
+ return API_REGION_MAP[ssoRegion] ?? ssoRegion;
150
+ }
151
+ function resetProfileArnCache(skipResolution = false) {
152
+ cachedProfileArn = null;
153
+ profileArnSkipResolution = skipResolution;
154
+ }
155
+ function seedProfileArn(arn) {
156
+ cachedProfileArn = arn;
157
+ }
158
+ async function resolveProfileArn(accessToken, apiRegion) {
159
+ if (profileArnSkipResolution)
160
+ return null;
161
+ if (cachedProfileArn !== null)
162
+ return cachedProfileArn;
163
+ const endpoint = `https://management.${apiRegion}.kiro.dev/`;
164
+ const resp = await fetch(endpoint, {
165
+ method: "POST",
166
+ headers: {
167
+ Authorization: `Bearer ${accessToken}`,
168
+ "Content-Type": "application/x-amz-json-1.0",
169
+ "X-Amz-Target": "AmazonCodeWhispererService.ListAvailableProfiles",
170
+ "user-agent": "aws-sdk-rust/1.3.15 ua/2.1 api/codewhispererruntime/0.1.16551 os/macos lang/rust/1.92.0 md/appVersion-2.7.1 app/AmazonQ-For-CLI"
171
+ },
172
+ body: "{}"
173
+ });
174
+ if (!resp || !resp.ok)
175
+ return null;
176
+ const data = await resp.json();
177
+ const profiles = data.profiles ?? [];
178
+ const kiroProfile = profiles.find((p) => p.profileType === "KIRO" && p.status === "ACTIVE");
179
+ const arn = kiroProfile?.arn ?? profiles[0]?.arn ?? null;
180
+ if (arn) {
181
+ cachedProfileArn = arn;
182
+ }
183
+ return arn;
184
+ }
185
+ async function fetchAvailableModels(accessToken, apiRegion, profileArn) {
186
+ const url = `https://management.${apiRegion}.kiro.dev/?origin=KIRO_CLI&profileArn=${encodeURIComponent(profileArn)}`;
187
+ const resp = await fetch(url, {
188
+ method: "POST",
189
+ headers: {
190
+ Authorization: `Bearer ${accessToken}`,
191
+ "Content-Type": "application/x-amz-json-1.0",
192
+ "X-Amz-Target": "AmazonCodeWhispererService.ListAvailableModels",
193
+ "user-agent": "aws-sdk-rust/1.3.15 ua/2.1 api/codewhispererruntime/0.1.16551 os/macos lang/rust/1.92.0 md/appVersion-2.7.1 app/AmazonQ-For-CLI"
194
+ },
195
+ body: "{}"
196
+ });
197
+ if (!resp.ok) {
198
+ throw new Error(`ListAvailableModels failed: HTTP ${resp.status}`);
199
+ }
200
+ const data = await resp.json();
201
+ return (data.models ?? []).filter((m) => m.modelId !== "auto");
202
+ }
203
+ function isReasoningModel(dotId) {
204
+ for (const family of REASONING_FAMILIES) {
205
+ if (dotId.startsWith(family))
206
+ return true;
207
+ }
208
+ return false;
209
+ }
210
+ function firstTokenTimeout(dotId) {
211
+ if (dotId.startsWith("claude-fable") || dotId.startsWith("claude-opus"))
212
+ return 180000;
213
+ return 90000;
214
+ }
215
+ function buildModelsFromApi(apiModels) {
216
+ return apiModels.map((m) => {
217
+ KIRO_MODEL_IDS.add(m.modelId);
218
+ const dashId = dotToDash(m.modelId);
219
+ const supportedTypes = m.supportedInputTypes ?? ["TEXT"];
220
+ const input = supportedTypes.includes("IMAGE") ? ["text", "image"] : ["text"];
221
+ const effortEnum = m.additionalModelRequestFieldsSchema?.properties?.output_config?.properties?.effort?.enum;
222
+ const supportedEfforts = Array.isArray(effortEnum) && effortEnum.length > 0 ? effortEnum : undefined;
223
+ const supportsThinkingConfig = !!m.additionalModelRequestFieldsSchema?.properties?.thinking;
224
+ return {
225
+ ...KIRO_DEFAULTS,
226
+ id: dashId,
227
+ name: m.modelName,
228
+ reasoning: isReasoningModel(m.modelId),
229
+ input,
230
+ contextWindow: m.tokenLimits?.maxInputTokens ?? 200000,
231
+ maxTokens: m.tokenLimits?.maxOutputTokens ?? 8192,
232
+ firstTokenTimeout: firstTokenTimeout(m.modelId),
233
+ ...supportedEfforts ? { supportedEfforts } : {},
234
+ ...supportsThinkingConfig ? { supportsThinkingConfig } : {}
235
+ };
236
+ });
237
+ }
238
+ function getCachedDynamicModels() {
239
+ return cachedDynamicModels;
240
+ }
241
+ function setCachedDynamicModels(models) {
242
+ cachedDynamicModels = models;
243
+ }
244
+ var KIRO_MODEL_IDS, API_REGION_MAP, BASE_URL = "https://runtime.us-east-1.kiro.dev", ZERO_COST, KIRO_DEFAULTS, MULTIMODAL, TEXT_ONLY, kiroModels, cachedProfileArn = null, profileArnSkipResolution = false, REASONING_FAMILIES, cachedDynamicModels = null;
245
+ var init_models = __esm(() => {
246
+ KIRO_MODEL_IDS = new Set([
247
+ "claude-fable-5",
248
+ "claude-opus-4.8",
249
+ "claude-opus-4.7",
250
+ "claude-opus-4.6",
251
+ "claude-opus-4.6-1m",
252
+ "claude-sonnet-4.6",
253
+ "claude-sonnet-4.6-1m",
254
+ "claude-opus-4.5",
255
+ "claude-sonnet-4.5",
256
+ "claude-sonnet-4.5-1m",
257
+ "claude-sonnet-4",
258
+ "claude-haiku-4.5",
259
+ "deepseek-3.2",
260
+ "kimi-k2.5",
261
+ "minimax-m2.1",
262
+ "minimax-m2.5",
263
+ "glm-4.7",
264
+ "glm-4.7-flash",
265
+ "qwen3-coder-next",
266
+ "agi-nova-beta-1m",
267
+ "qwen3-coder-480b",
268
+ "auto"
269
+ ]);
270
+ API_REGION_MAP = {
271
+ "us-west-1": "us-east-1",
272
+ "us-west-2": "us-east-1",
273
+ "us-east-2": "us-east-1",
274
+ "eu-west-1": "eu-central-1",
275
+ "eu-west-2": "eu-central-1",
276
+ "eu-west-3": "eu-central-1",
277
+ "eu-north-1": "eu-central-1",
278
+ "eu-south-1": "eu-central-1",
279
+ "eu-south-2": "eu-central-1",
280
+ "eu-central-2": "eu-central-1",
281
+ "ap-northeast-1": "us-east-1",
282
+ "ap-northeast-2": "us-east-1",
283
+ "ap-northeast-3": "us-east-1",
284
+ "ap-southeast-1": "us-east-1",
285
+ "ap-southeast-2": "us-east-1",
286
+ "ap-south-1": "us-east-1",
287
+ "ap-east-1": "us-east-1",
288
+ "ap-south-2": "us-east-1",
289
+ "ap-southeast-3": "us-east-1",
290
+ "ap-southeast-4": "us-east-1"
291
+ };
292
+ ZERO_COST = Object.freeze({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 });
293
+ KIRO_DEFAULTS = {
294
+ api: "kiro-api",
295
+ provider: "kiro",
296
+ baseUrl: BASE_URL,
297
+ cost: ZERO_COST
298
+ };
299
+ MULTIMODAL = ["text", "image"];
300
+ TEXT_ONLY = ["text"];
301
+ kiroModels = [
302
+ {
303
+ ...KIRO_DEFAULTS,
304
+ id: "claude-fable-5",
305
+ name: "Claude Fable 5",
306
+ reasoning: true,
307
+ input: MULTIMODAL,
308
+ contextWindow: 1e6,
309
+ maxTokens: 128000,
310
+ firstTokenTimeout: 180000,
311
+ supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
312
+ supportsThinkingConfig: true
313
+ },
314
+ {
315
+ ...KIRO_DEFAULTS,
316
+ id: "claude-opus-4-8",
317
+ name: "Claude Opus 4.8",
318
+ reasoning: true,
319
+ input: MULTIMODAL,
320
+ contextWindow: 1e6,
321
+ maxTokens: 128000,
322
+ firstTokenTimeout: 180000,
323
+ supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
324
+ supportsThinkingConfig: true
325
+ },
326
+ {
327
+ ...KIRO_DEFAULTS,
328
+ id: "claude-opus-4-7",
329
+ name: "Claude Opus 4.7",
330
+ reasoning: true,
331
+ input: MULTIMODAL,
332
+ contextWindow: 1e6,
333
+ maxTokens: 128000,
334
+ firstTokenTimeout: 180000,
335
+ supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
336
+ supportsThinkingConfig: true
337
+ },
338
+ {
339
+ ...KIRO_DEFAULTS,
340
+ id: "claude-opus-4-6",
341
+ name: "Claude Opus 4.6",
342
+ reasoning: true,
343
+ input: MULTIMODAL,
344
+ contextWindow: 1e6,
345
+ maxTokens: 64000,
346
+ supportedEfforts: ["low", "medium", "high", "max"],
347
+ supportsThinkingConfig: true
348
+ },
349
+ {
350
+ ...KIRO_DEFAULTS,
351
+ id: "claude-opus-4-6-1m",
352
+ name: "Claude Opus 4.6 (1M)",
353
+ reasoning: true,
354
+ input: MULTIMODAL,
355
+ contextWindow: 1e6,
356
+ maxTokens: 64000,
357
+ supportedEfforts: ["low", "medium", "high", "max"],
358
+ supportsThinkingConfig: true
359
+ },
360
+ {
361
+ ...KIRO_DEFAULTS,
362
+ id: "claude-sonnet-4-6",
363
+ name: "Claude Sonnet 4.6",
364
+ reasoning: true,
365
+ input: MULTIMODAL,
366
+ contextWindow: 1e6,
367
+ maxTokens: 64000,
368
+ supportedEfforts: ["low", "medium", "high", "max"],
369
+ supportsThinkingConfig: true
370
+ },
371
+ {
372
+ ...KIRO_DEFAULTS,
373
+ id: "claude-sonnet-4-6-1m",
374
+ name: "Claude Sonnet 4.6 (1M)",
375
+ reasoning: true,
376
+ input: MULTIMODAL,
377
+ contextWindow: 1e6,
378
+ maxTokens: 64000,
379
+ supportedEfforts: ["low", "medium", "high", "max"],
380
+ supportsThinkingConfig: true
381
+ },
382
+ {
383
+ ...KIRO_DEFAULTS,
384
+ id: "claude-opus-4-5",
385
+ name: "Claude Opus 4.5",
386
+ reasoning: true,
387
+ input: MULTIMODAL,
388
+ contextWindow: 200000,
389
+ maxTokens: 64000
390
+ },
391
+ {
392
+ ...KIRO_DEFAULTS,
393
+ id: "claude-sonnet-4-5",
394
+ name: "Claude Sonnet 4.5",
395
+ reasoning: true,
396
+ input: MULTIMODAL,
397
+ contextWindow: 200000,
398
+ maxTokens: 65536
399
+ },
400
+ {
401
+ ...KIRO_DEFAULTS,
402
+ id: "claude-sonnet-4-5-1m",
403
+ name: "Claude Sonnet 4.5 (1M)",
404
+ reasoning: true,
405
+ input: MULTIMODAL,
406
+ contextWindow: 1e6,
407
+ maxTokens: 65536
408
+ },
409
+ {
410
+ ...KIRO_DEFAULTS,
411
+ id: "claude-sonnet-4",
412
+ name: "Claude Sonnet 4",
413
+ reasoning: true,
414
+ input: MULTIMODAL,
415
+ contextWindow: 200000,
416
+ maxTokens: 65536
417
+ },
418
+ {
419
+ ...KIRO_DEFAULTS,
420
+ id: "claude-haiku-4-5",
421
+ name: "Claude Haiku 4.5",
422
+ reasoning: false,
423
+ input: MULTIMODAL,
424
+ contextWindow: 200000,
425
+ maxTokens: 65536
426
+ },
427
+ {
428
+ ...KIRO_DEFAULTS,
429
+ id: "deepseek-3-2",
430
+ name: "DeepSeek 3.2",
431
+ reasoning: true,
432
+ input: TEXT_ONLY,
433
+ contextWindow: 128000,
434
+ maxTokens: 8192
435
+ },
436
+ {
437
+ ...KIRO_DEFAULTS,
438
+ id: "kimi-k2-5",
439
+ name: "Kimi K2.5",
440
+ reasoning: true,
441
+ input: TEXT_ONLY,
442
+ contextWindow: 200000,
443
+ maxTokens: 8192
444
+ },
445
+ {
446
+ ...KIRO_DEFAULTS,
447
+ id: "minimax-m2-5",
448
+ name: "MiniMax M2.5",
449
+ reasoning: false,
450
+ input: TEXT_ONLY,
451
+ contextWindow: 196000,
452
+ maxTokens: 64000
453
+ },
454
+ {
455
+ ...KIRO_DEFAULTS,
456
+ id: "minimax-m2-1",
457
+ name: "MiniMax M2.1",
458
+ reasoning: false,
459
+ input: MULTIMODAL,
460
+ contextWindow: 196000,
461
+ maxTokens: 64000
462
+ },
463
+ {
464
+ ...KIRO_DEFAULTS,
465
+ id: "glm-4-7",
466
+ name: "GLM 4.7",
467
+ reasoning: true,
468
+ input: TEXT_ONLY,
469
+ contextWindow: 128000,
470
+ maxTokens: 8192
471
+ },
472
+ {
473
+ ...KIRO_DEFAULTS,
474
+ id: "glm-4-7-flash",
475
+ name: "GLM 4.7 Flash",
476
+ reasoning: false,
477
+ input: TEXT_ONLY,
478
+ contextWindow: 128000,
479
+ maxTokens: 8192
480
+ },
481
+ {
482
+ ...KIRO_DEFAULTS,
483
+ id: "qwen3-coder-next",
484
+ name: "Qwen3 Coder Next",
485
+ reasoning: true,
486
+ input: MULTIMODAL,
487
+ contextWindow: 256000,
488
+ maxTokens: 64000
489
+ },
490
+ {
491
+ ...KIRO_DEFAULTS,
492
+ id: "qwen3-coder-480b",
493
+ name: "Qwen3 Coder 480B",
494
+ reasoning: true,
495
+ input: TEXT_ONLY,
496
+ contextWindow: 128000,
497
+ maxTokens: 8192
498
+ },
499
+ {
500
+ ...KIRO_DEFAULTS,
501
+ id: "agi-nova-beta-1m",
502
+ name: "AGI Nova Beta (1M)",
503
+ reasoning: true,
504
+ input: MULTIMODAL,
505
+ contextWindow: 1e6,
506
+ maxTokens: 65536
507
+ },
508
+ {
509
+ ...KIRO_DEFAULTS,
510
+ id: "auto",
511
+ name: "Auto",
512
+ reasoning: true,
513
+ input: MULTIMODAL,
514
+ contextWindow: 200000,
515
+ maxTokens: 65536
516
+ }
517
+ ];
518
+ REASONING_FAMILIES = new Set([
519
+ "claude-fable",
520
+ "claude-sonnet",
521
+ "claude-opus",
522
+ "deepseek",
523
+ "kimi",
524
+ "glm",
525
+ "qwen",
526
+ "agi-nova",
527
+ "minimax"
528
+ ]);
529
+ });
530
+
116
531
  // src/kiro-cli-sync.ts
117
532
  var exports_kiro_cli_sync = {};
118
533
  __export(exports_kiro_cli_sync, {
119
- importFromKiroCli: () => importFromKiroCli
534
+ selectKiroTokenRowForWrite: () => selectKiroTokenRowForWrite,
535
+ saveKiroCliCredentials: () => saveKiroCliCredentials,
536
+ sameKiroCliCredential: () => sameKiroCliCredential,
537
+ importFromKiroSsoCache: () => importFromKiroSsoCache,
538
+ importFromKiroCli: () => importFromKiroCli,
539
+ getKiroCliCredentialsAllowExpired: () => getKiroCliCredentialsAllowExpired
120
540
  });
121
541
  import { homedir } from "node:os";
122
542
  import { join } from "node:path";
543
+ import { spawn } from "node:child_process";
123
544
  import { existsSync, readFileSync } from "node:fs";
124
545
  function getKiroDbPath() {
125
546
  const home = homedir();
@@ -151,6 +572,105 @@ function safeJsonParse(value) {
151
572
  return null;
152
573
  }
153
574
  }
575
+ function sqliteQuote(value) {
576
+ return `'${value.replaceAll("'", "''")}'`;
577
+ }
578
+ function runSqliteCli(dbPath, sql, options) {
579
+ return new Promise((resolve2) => {
580
+ const args = [
581
+ ...options.readonly ? ["-readonly"] : [],
582
+ "-cmd",
583
+ `.timeout ${SQLITE_CLI_TIMEOUT_MS}`,
584
+ ...options.json ? ["-json"] : [],
585
+ dbPath
586
+ ];
587
+ const child = spawn("sqlite3", args, { stdio: "pipe" });
588
+ let stdout = "";
589
+ let settled = false;
590
+ const finish = (result) => {
591
+ if (settled)
592
+ return;
593
+ settled = true;
594
+ clearTimeout(timeout);
595
+ resolve2(result);
596
+ };
597
+ const timeout = setTimeout(() => {
598
+ child.kill();
599
+ log.debug("sqlite3 CLI timed out while reading Kiro DB");
600
+ finish(null);
601
+ }, SQLITE_CLI_TIMEOUT_MS);
602
+ child.stdout?.setEncoding("utf8");
603
+ child.stdout?.on("data", (chunk) => {
604
+ stdout += chunk;
605
+ });
606
+ child.on("error", () => {
607
+ log.debug("sqlite3 CLI unavailable for Kiro DB access");
608
+ finish(null);
609
+ });
610
+ child.on("close", (code) => {
611
+ if (code === 0)
612
+ finish(stdout);
613
+ else {
614
+ log.debug("sqlite3 CLI failed while accessing Kiro DB");
615
+ finish(null);
616
+ }
617
+ });
618
+ child.stdin?.end(`${sql}
619
+ `);
620
+ });
621
+ }
622
+ function parseAuthKvRows(value) {
623
+ if (!Array.isArray(value))
624
+ return null;
625
+ const rows = [];
626
+ for (const item of value) {
627
+ if (!item || typeof item !== "object")
628
+ continue;
629
+ const record = item;
630
+ if (typeof record.key === "string" && typeof record.value === "string") {
631
+ rows.push({ key: record.key, value: record.value });
632
+ }
633
+ }
634
+ return rows;
635
+ }
636
+ function extractActiveProfileArnFromStateRows(value) {
637
+ if (!Array.isArray(value))
638
+ return;
639
+ const first = value[0];
640
+ if (!first || typeof first !== "object")
641
+ return;
642
+ const record = first;
643
+ const parsed = safeJsonParse(record.value);
644
+ const arn = parsed?.arn || parsed?.profileArn || parsed?.profile_arn;
645
+ return typeof arn === "string" && arn.trim() ? arn.trim() : undefined;
646
+ }
647
+ function parseSqliteChanges(value) {
648
+ if (!Array.isArray(value))
649
+ return 0;
650
+ const first = value[0];
651
+ if (!first || typeof first !== "object")
652
+ return 0;
653
+ const changes = first.changes;
654
+ return typeof changes === "number" ? changes : 0;
655
+ }
656
+ async function readKiroAuthKvRowsWithSqliteCli(dbPath) {
657
+ const rowsRaw = await runSqliteCli(dbPath, "SELECT key, value FROM auth_kv", { readonly: true, json: true });
658
+ if (!rowsRaw)
659
+ return null;
660
+ return parseAuthKvRows(safeJsonParse(rowsRaw));
661
+ }
662
+ async function readKiroDbWithSqliteCli(dbPath) {
663
+ const rows = await readKiroAuthKvRowsWithSqliteCli(dbPath);
664
+ if (!rows)
665
+ return null;
666
+ const stateRaw = await runSqliteCli(dbPath, "SELECT value FROM state WHERE key = 'api.codewhisperer.profile'", { readonly: true, json: true });
667
+ const activeProfileArn = stateRaw ? extractActiveProfileArnFromStateRows(safeJsonParse(stateRaw)) : undefined;
668
+ return { rows, activeProfileArn };
669
+ }
670
+ async function writeKiroDbWithSqliteCli(dbPath, tokenKey, updatedValue) {
671
+ const resultRaw = await runSqliteCli(dbPath, `UPDATE auth_kv SET value = ${sqliteQuote(updatedValue)} WHERE key = ${sqliteQuote(tokenKey)}; ` + "SELECT changes() AS changes", { readonly: false, json: true });
672
+ return parseSqliteChanges(safeJsonParse(resultRaw)) > 0;
673
+ }
154
674
  function findClientCreds(obj) {
155
675
  if (!obj || typeof obj !== "object")
156
676
  return {};
@@ -169,6 +689,23 @@ function findClientCreds(obj) {
169
689
  function isIdcTokenKey(key) {
170
690
  return key.includes("odic") || key.includes("oidc") || key.includes("idc");
171
691
  }
692
+ function isTokenRow(row) {
693
+ return row.key.includes(":token");
694
+ }
695
+ function tokenReadRank(row) {
696
+ return isIdcTokenKey(row.key) ? 0 : 1;
697
+ }
698
+ function selectKiroTokenRowForWrite(rows, creds) {
699
+ const tokenRows = rows.filter(isTokenRow);
700
+ if (!creds.tokenKey)
701
+ return;
702
+ return tokenRows.find((row) => row.key === creds.tokenKey);
703
+ }
704
+ function sameKiroCliCredential(left, right) {
705
+ if (!left || !right)
706
+ return false;
707
+ return left.source === right.source && left.tokenKey === right.tokenKey && left.accessToken === right.accessToken && left.refreshToken === right.refreshToken && left.region === right.region && left.authMethod === right.authMethod;
708
+ }
172
709
  function extractRegionFromArn(arn) {
173
710
  if (!arn)
174
711
  return;
@@ -185,37 +722,56 @@ async function importFromKiroDb() {
185
722
  return null;
186
723
  }
187
724
  try {
188
- const { Database } = await import("bun:sqlite");
189
- const db = new Database(dbPath, { readonly: true });
190
- try {
191
- db.exec("PRAGMA busy_timeout = 5000");
192
- } catch {}
193
725
  let rows;
726
+ let activeProfileArn;
727
+ let Database = null;
194
728
  try {
195
- rows = db.prepare("SELECT key, value FROM auth_kv").all();
729
+ Database = (await import("bun:sqlite")).Database;
196
730
  } catch {
197
- log.debug("Failed to read auth_kv table from Kiro DB");
731
+ try {
732
+ Database = (await import("better-sqlite3")).default;
733
+ } catch {
734
+ log.debug("No SQLite driver available (need bun:sqlite or better-sqlite3); trying sqlite3 CLI");
735
+ }
736
+ }
737
+ if (Database) {
738
+ const db = new Database(dbPath, { readonly: true });
739
+ try {
740
+ db.run?.("PRAGMA busy_timeout = 5000") ?? db.exec?.("PRAGMA busy_timeout = 5000");
741
+ } catch {}
742
+ try {
743
+ const stmt = db.prepare("SELECT key, value FROM auth_kv");
744
+ rows = stmt.all();
745
+ } catch {
746
+ log.debug("Failed to read auth_kv table from Kiro DB");
747
+ try {
748
+ db.close();
749
+ } catch {}
750
+ return null;
751
+ }
752
+ try {
753
+ const stateStmt = db.prepare("SELECT value FROM state WHERE key = ?");
754
+ const stateRow = stateStmt.get("api.codewhisperer.profile");
755
+ const parsed = safeJsonParse(stateRow?.value);
756
+ const arn = parsed?.arn || parsed?.profileArn || parsed?.profile_arn;
757
+ if (typeof arn === "string" && arn.trim()) {
758
+ activeProfileArn = arn.trim();
759
+ }
760
+ } catch {}
198
761
  try {
199
762
  db.close();
200
763
  } catch {}
201
- return null;
764
+ } else {
765
+ const snapshot = await readKiroDbWithSqliteCli(dbPath);
766
+ if (!snapshot)
767
+ return null;
768
+ rows = snapshot.rows;
769
+ activeProfileArn = snapshot.activeProfileArn;
202
770
  }
203
- let activeProfileArn;
204
- try {
205
- const stateRow = db.prepare("SELECT value FROM state WHERE key = ?").get("api.codewhisperer.profile");
206
- const parsed = safeJsonParse(stateRow?.value);
207
- const arn = parsed?.arn || parsed?.profileArn || parsed?.profile_arn;
208
- if (typeof arn === "string" && arn.trim()) {
209
- activeProfileArn = arn.trim();
210
- }
211
- } catch {}
212
- try {
213
- db.close();
214
- } catch {}
215
771
  const deviceRegRow = rows.find((r) => typeof r?.key === "string" && r.key.includes("device-registration"));
216
772
  const deviceReg = safeJsonParse(deviceRegRow?.value);
217
773
  const regCreds = deviceReg ? findClientCreds(deviceReg) : {};
218
- const tokenRows = rows.filter((r) => r.key.includes(":token")).sort((a, b) => (isIdcTokenKey(a.key) ? 0 : 1) - (isIdcTokenKey(b.key) ? 0 : 1));
774
+ const tokenRows = rows.filter(isTokenRow).sort((a, b) => tokenReadRank(a) - tokenReadRank(b));
219
775
  for (const row of tokenRows) {
220
776
  const data = safeJsonParse(row.value);
221
777
  if (!data)
@@ -228,16 +784,19 @@ async function importFromKiroDb() {
228
784
  const authMethod = isIdc ? "idc" : "desktop";
229
785
  const oidcRegion = data.region || "us-east-1";
230
786
  let profileArn = data.profile_arn || data.profileArn;
231
- if (!profileArn)
787
+ if (!profileArn && isIdc) {
232
788
  profileArn = activeProfileArn;
789
+ }
233
790
  const serviceRegion = extractRegionFromArn(profileArn) || oidcRegion;
234
791
  const result = {
235
792
  accessToken: accessToken || "",
236
793
  refreshToken: refreshToken || "",
237
794
  region: serviceRegion,
238
795
  authMethod,
796
+ profileArn,
239
797
  email: data.email || data.emailAddress,
240
- profileArn
798
+ source: "kiro-cli-db",
799
+ tokenKey: row.key
241
800
  };
242
801
  if (isIdc && regCreds.clientId) {
243
802
  result.clientId = regCreds.clientId;
@@ -253,30 +812,48 @@ async function importFromKiroDb() {
253
812
  return null;
254
813
  }
255
814
  }
815
+ function mapSsoCacheAuthMethod(value) {
816
+ if (typeof value !== "string")
817
+ return "idc";
818
+ const v = value.toLowerCase();
819
+ if (v === "builderid" || v === "builder-id")
820
+ return "desktop";
821
+ return "idc";
822
+ }
256
823
  async function importFromKiroSsoCache() {
257
- const cachePath = getKiroSsoCachePath();
258
- if (!existsSync(cachePath)) {
259
- log.debug(`Kiro SSO cache not found at ${cachePath}`);
824
+ const path = getKiroSsoCachePath();
825
+ if (!existsSync(path)) {
826
+ log.debug(`Kiro SSO cache not found at ${path}`);
260
827
  return null;
261
828
  }
262
829
  let raw;
263
830
  try {
264
- raw = readFileSync(cachePath, "utf8");
831
+ raw = readFileSync(path, "utf8");
265
832
  } catch (err) {
266
- log.warn(`Failed to read Kiro SSO cache at ${cachePath}: ${err}`);
833
+ log.warn(`Failed to read Kiro SSO cache at ${path}: ${err}`);
267
834
  return null;
268
835
  }
269
836
  const token = safeJsonParse(raw);
270
- if (!token || typeof token !== "object")
837
+ if (!token || typeof token !== "object") {
838
+ log.debug(`Kiro SSO cache at ${path} is not valid JSON`);
271
839
  return null;
840
+ }
272
841
  const accessToken = typeof token.accessToken === "string" ? token.accessToken : "";
273
842
  const refreshToken = typeof token.refreshToken === "string" ? token.refreshToken : "";
274
- if (!accessToken && !refreshToken)
843
+ if (!accessToken && !refreshToken) {
844
+ log.debug(`Kiro SSO cache at ${path} has no tokens`);
275
845
  return null;
846
+ }
276
847
  const region = typeof token.region === "string" && token.region.length > 0 ? token.region : "us-east-1";
277
- const authMethod = "desktop";
278
- log.info(`Imported Kiro SSO cache credentials (region=${region})`);
279
- return { accessToken, refreshToken, region, authMethod };
848
+ const authMethod = mapSsoCacheAuthMethod(token.authMethod);
849
+ log.info(`Imported Kiro SSO cache credentials (method=${authMethod}, region=${region})`);
850
+ return {
851
+ accessToken,
852
+ refreshToken,
853
+ region,
854
+ authMethod,
855
+ source: "kiro-sso-cache"
856
+ };
280
857
  }
281
858
  async function importFromKiroCli() {
282
859
  const dbResult = await importFromKiroDb();
@@ -284,6 +861,98 @@ async function importFromKiroCli() {
284
861
  return dbResult;
285
862
  return importFromKiroSsoCache();
286
863
  }
864
+ async function getKiroCliCredentialsAllowExpired(exclude) {
865
+ const imported = await importFromKiroCli();
866
+ return sameKiroCliCredential(imported, exclude ?? null) ? null : imported;
867
+ }
868
+ async function saveKiroCliCredentials(creds) {
869
+ if (creds.source !== "kiro-cli-db" || !creds.tokenKey) {
870
+ log.debug("Credential write-back skipped: credential did not originate from kiro-cli DB");
871
+ return false;
872
+ }
873
+ const dbPath = getKiroDbPath();
874
+ if (!existsSync(dbPath)) {
875
+ log.debug(`Kiro CLI DB not found at ${dbPath} — cannot save credentials`);
876
+ return false;
877
+ }
878
+ try {
879
+ let Database = null;
880
+ try {
881
+ Database = (await import("bun:sqlite")).Database;
882
+ } catch {
883
+ try {
884
+ Database = (await import("better-sqlite3")).default;
885
+ } catch {
886
+ log.debug("No SQLite driver available for credential write-back; trying sqlite3 CLI");
887
+ }
888
+ }
889
+ let rows;
890
+ let db = null;
891
+ if (Database) {
892
+ db = new Database(dbPath);
893
+ try {
894
+ db.run?.("PRAGMA busy_timeout = 5000") ?? db.exec?.("PRAGMA busy_timeout = 5000");
895
+ } catch {}
896
+ try {
897
+ const stmt = db.prepare("SELECT key, value FROM auth_kv");
898
+ rows = stmt.all();
899
+ } catch {
900
+ log.debug("Failed to read auth_kv table for credential write-back");
901
+ try {
902
+ db.close();
903
+ } catch {}
904
+ return false;
905
+ }
906
+ } else {
907
+ const cliRows = await readKiroAuthKvRowsWithSqliteCli(dbPath);
908
+ if (!cliRows)
909
+ return false;
910
+ rows = cliRows;
911
+ }
912
+ const tokenRow = selectKiroTokenRowForWrite(rows, creds);
913
+ if (!tokenRow) {
914
+ log.debug("No matching token entry found in auth_kv — cannot write back");
915
+ try {
916
+ db?.close();
917
+ } catch {}
918
+ return false;
919
+ }
920
+ const existing = safeJsonParse(tokenRow.value) ?? {};
921
+ const updated = {
922
+ ...existing,
923
+ accessToken: creds.accessToken,
924
+ access_token: creds.accessToken,
925
+ refreshToken: creds.refreshToken,
926
+ refresh_token: creds.refreshToken
927
+ };
928
+ const updatedValue = JSON.stringify(updated);
929
+ if (db) {
930
+ try {
931
+ const updateStmt = db.prepare("UPDATE auth_kv SET value = ? WHERE key = ?");
932
+ updateStmt.run(updatedValue, tokenRow.key);
933
+ } catch (err) {
934
+ log.warn(`Failed to write credentials back to Kiro CLI DB: ${err}`);
935
+ try {
936
+ db.close();
937
+ } catch {}
938
+ return false;
939
+ }
940
+ try {
941
+ db.close();
942
+ } catch {}
943
+ } else {
944
+ const wrote = await writeKiroDbWithSqliteCli(dbPath, tokenRow.key, updatedValue);
945
+ if (!wrote)
946
+ return false;
947
+ }
948
+ log.info("Wrote refreshed credentials back to Kiro CLI DB");
949
+ return true;
950
+ } catch (err) {
951
+ log.warn(`Failed to save credentials to Kiro CLI: ${err}`);
952
+ return false;
953
+ }
954
+ }
955
+ var SQLITE_CLI_TIMEOUT_MS = 5000;
287
956
  var init_kiro_cli_sync = __esm(() => {
288
957
  init_debug();
289
958
  });
@@ -612,372 +1281,8 @@ function isPermanentError(reason) {
612
1281
  return PERMANENT_PATTERNS.some((p) => reason.includes(p));
613
1282
  }
614
1283
 
615
- // src/models.ts
616
- var KIRO_MODEL_IDS = new Set([
617
- "claude-fable-5",
618
- "claude-opus-4.8",
619
- "claude-opus-4.7",
620
- "claude-opus-4.6",
621
- "claude-opus-4.6-1m",
622
- "claude-sonnet-4.6",
623
- "claude-sonnet-4.6-1m",
624
- "claude-opus-4.5",
625
- "claude-sonnet-4.5",
626
- "claude-sonnet-4.5-1m",
627
- "claude-sonnet-4",
628
- "claude-haiku-4.5",
629
- "deepseek-3.2",
630
- "kimi-k2.5",
631
- "minimax-m2.1",
632
- "minimax-m2.5",
633
- "glm-4.7",
634
- "glm-4.7-flash",
635
- "qwen3-coder-next",
636
- "agi-nova-beta-1m",
637
- "qwen3-coder-480b",
638
- "auto"
639
- ]);
640
- function dotToDash(modelId) {
641
- return modelId.replace(/(\d)\.(\d)/g, "$1-$2");
642
- }
643
- function resolveKiroModel(modelId) {
644
- const kiroId = modelId.replace(/(\d)-(\d)/g, "$1.$2");
645
- if (!KIRO_MODEL_IDS.has(kiroId)) {
646
- throw new Error(`Unknown Kiro model ID: ${modelId}`);
647
- }
648
- return kiroId;
649
- }
650
- var API_REGION_MAP = {
651
- "us-west-1": "us-east-1",
652
- "us-west-2": "us-east-1",
653
- "us-east-2": "us-east-1",
654
- "eu-west-1": "eu-central-1",
655
- "eu-west-2": "eu-central-1",
656
- "eu-west-3": "eu-central-1",
657
- "eu-north-1": "eu-central-1",
658
- "eu-south-1": "eu-central-1",
659
- "eu-south-2": "eu-central-1",
660
- "eu-central-2": "eu-central-1",
661
- "ap-northeast-1": "us-east-1",
662
- "ap-northeast-2": "us-east-1",
663
- "ap-northeast-3": "us-east-1",
664
- "ap-southeast-1": "us-east-1",
665
- "ap-southeast-2": "us-east-1",
666
- "ap-south-1": "us-east-1",
667
- "ap-east-1": "us-east-1",
668
- "ap-south-2": "us-east-1",
669
- "ap-southeast-3": "us-east-1",
670
- "ap-southeast-4": "us-east-1"
671
- };
672
- function resolveApiRegion(ssoRegion) {
673
- if (!ssoRegion)
674
- return "us-east-1";
675
- return API_REGION_MAP[ssoRegion] ?? ssoRegion;
676
- }
677
- var BASE_URL = "https://runtime.us-east-1.kiro.dev";
678
- var ZERO_COST = Object.freeze({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 });
679
- var KIRO_DEFAULTS = {
680
- api: "kiro-api",
681
- provider: "kiro",
682
- baseUrl: BASE_URL,
683
- cost: ZERO_COST
684
- };
685
- var MULTIMODAL = ["text", "image"];
686
- var TEXT_ONLY = ["text"];
687
- var kiroModels = [
688
- {
689
- ...KIRO_DEFAULTS,
690
- id: "claude-fable-5",
691
- name: "Claude Fable 5",
692
- reasoning: true,
693
- input: MULTIMODAL,
694
- contextWindow: 1e6,
695
- maxTokens: 128000,
696
- firstTokenTimeout: 180000,
697
- supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
698
- supportsThinkingConfig: true
699
- },
700
- {
701
- ...KIRO_DEFAULTS,
702
- id: "claude-opus-4-8",
703
- name: "Claude Opus 4.8",
704
- reasoning: true,
705
- input: MULTIMODAL,
706
- contextWindow: 1e6,
707
- maxTokens: 128000,
708
- firstTokenTimeout: 180000,
709
- supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
710
- supportsThinkingConfig: true
711
- },
712
- {
713
- ...KIRO_DEFAULTS,
714
- id: "claude-opus-4-7",
715
- name: "Claude Opus 4.7",
716
- reasoning: true,
717
- input: MULTIMODAL,
718
- contextWindow: 1e6,
719
- maxTokens: 128000,
720
- firstTokenTimeout: 180000,
721
- supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
722
- supportsThinkingConfig: true
723
- },
724
- {
725
- ...KIRO_DEFAULTS,
726
- id: "claude-opus-4-6",
727
- name: "Claude Opus 4.6",
728
- reasoning: true,
729
- input: MULTIMODAL,
730
- contextWindow: 1e6,
731
- maxTokens: 64000,
732
- supportedEfforts: ["low", "medium", "high", "max"],
733
- supportsThinkingConfig: true
734
- },
735
- {
736
- ...KIRO_DEFAULTS,
737
- id: "claude-opus-4-6-1m",
738
- name: "Claude Opus 4.6 (1M)",
739
- reasoning: true,
740
- input: MULTIMODAL,
741
- contextWindow: 1e6,
742
- maxTokens: 64000,
743
- supportedEfforts: ["low", "medium", "high", "max"],
744
- supportsThinkingConfig: true
745
- },
746
- {
747
- ...KIRO_DEFAULTS,
748
- id: "claude-sonnet-4-6",
749
- name: "Claude Sonnet 4.6",
750
- reasoning: true,
751
- input: MULTIMODAL,
752
- contextWindow: 1e6,
753
- maxTokens: 64000,
754
- supportedEfforts: ["low", "medium", "high", "max"],
755
- supportsThinkingConfig: true
756
- },
757
- {
758
- ...KIRO_DEFAULTS,
759
- id: "claude-sonnet-4-6-1m",
760
- name: "Claude Sonnet 4.6 (1M)",
761
- reasoning: true,
762
- input: MULTIMODAL,
763
- contextWindow: 1e6,
764
- maxTokens: 64000,
765
- supportedEfforts: ["low", "medium", "high", "max"],
766
- supportsThinkingConfig: true
767
- },
768
- {
769
- ...KIRO_DEFAULTS,
770
- id: "claude-opus-4-5",
771
- name: "Claude Opus 4.5",
772
- reasoning: true,
773
- input: MULTIMODAL,
774
- contextWindow: 200000,
775
- maxTokens: 64000
776
- },
777
- {
778
- ...KIRO_DEFAULTS,
779
- id: "claude-sonnet-4-5",
780
- name: "Claude Sonnet 4.5",
781
- reasoning: true,
782
- input: MULTIMODAL,
783
- contextWindow: 200000,
784
- maxTokens: 65536
785
- },
786
- {
787
- ...KIRO_DEFAULTS,
788
- id: "claude-sonnet-4-5-1m",
789
- name: "Claude Sonnet 4.5 (1M)",
790
- reasoning: true,
791
- input: MULTIMODAL,
792
- contextWindow: 1e6,
793
- maxTokens: 65536
794
- },
795
- {
796
- ...KIRO_DEFAULTS,
797
- id: "claude-sonnet-4",
798
- name: "Claude Sonnet 4",
799
- reasoning: true,
800
- input: MULTIMODAL,
801
- contextWindow: 200000,
802
- maxTokens: 65536
803
- },
804
- {
805
- ...KIRO_DEFAULTS,
806
- id: "claude-haiku-4-5",
807
- name: "Claude Haiku 4.5",
808
- reasoning: false,
809
- input: MULTIMODAL,
810
- contextWindow: 200000,
811
- maxTokens: 65536
812
- },
813
- {
814
- ...KIRO_DEFAULTS,
815
- id: "deepseek-3-2",
816
- name: "DeepSeek 3.2",
817
- reasoning: true,
818
- input: TEXT_ONLY,
819
- contextWindow: 128000,
820
- maxTokens: 8192
821
- },
822
- {
823
- ...KIRO_DEFAULTS,
824
- id: "kimi-k2-5",
825
- name: "Kimi K2.5",
826
- reasoning: true,
827
- input: TEXT_ONLY,
828
- contextWindow: 200000,
829
- maxTokens: 8192
830
- },
831
- {
832
- ...KIRO_DEFAULTS,
833
- id: "minimax-m2-5",
834
- name: "MiniMax M2.5",
835
- reasoning: false,
836
- input: TEXT_ONLY,
837
- contextWindow: 196000,
838
- maxTokens: 64000
839
- },
840
- {
841
- ...KIRO_DEFAULTS,
842
- id: "minimax-m2-1",
843
- name: "MiniMax M2.1",
844
- reasoning: false,
845
- input: MULTIMODAL,
846
- contextWindow: 196000,
847
- maxTokens: 64000
848
- },
849
- {
850
- ...KIRO_DEFAULTS,
851
- id: "glm-4-7",
852
- name: "GLM 4.7",
853
- reasoning: true,
854
- input: TEXT_ONLY,
855
- contextWindow: 128000,
856
- maxTokens: 8192
857
- },
858
- {
859
- ...KIRO_DEFAULTS,
860
- id: "glm-4-7-flash",
861
- name: "GLM 4.7 Flash",
862
- reasoning: false,
863
- input: TEXT_ONLY,
864
- contextWindow: 128000,
865
- maxTokens: 8192
866
- },
867
- {
868
- ...KIRO_DEFAULTS,
869
- id: "qwen3-coder-next",
870
- name: "Qwen3 Coder Next",
871
- reasoning: true,
872
- input: MULTIMODAL,
873
- contextWindow: 256000,
874
- maxTokens: 64000
875
- },
876
- {
877
- ...KIRO_DEFAULTS,
878
- id: "qwen3-coder-480b",
879
- name: "Qwen3 Coder 480B",
880
- reasoning: true,
881
- input: TEXT_ONLY,
882
- contextWindow: 128000,
883
- maxTokens: 8192
884
- },
885
- {
886
- ...KIRO_DEFAULTS,
887
- id: "agi-nova-beta-1m",
888
- name: "AGI Nova Beta (1M)",
889
- reasoning: true,
890
- input: MULTIMODAL,
891
- contextWindow: 1e6,
892
- maxTokens: 65536
893
- },
894
- {
895
- ...KIRO_DEFAULTS,
896
- id: "auto",
897
- name: "Auto",
898
- reasoning: true,
899
- input: MULTIMODAL,
900
- contextWindow: 200000,
901
- maxTokens: 65536
902
- }
903
- ];
904
- async function fetchAvailableModels(accessToken, apiRegion, fallbackProfileArn) {
905
- const runtimeUrl = `https://runtime.${apiRegion}.kiro.dev/`;
906
- let profileArn = await resolveProfileArn(accessToken, runtimeUrl);
907
- if (!profileArn && fallbackProfileArn) {
908
- profileArn = fallbackProfileArn;
909
- }
910
- if (!profileArn) {
911
- throw new Error("Missing profileArn: cannot fetch available models.");
912
- }
913
- const url = `https://management.${apiRegion}.kiro.dev/ListAvailableModels?origin=KIRO_CLI&profileArn=${encodeURIComponent(profileArn)}`;
914
- const resp = await fetch(url, {
915
- method: "GET",
916
- headers: {
917
- Authorization: `Bearer ${accessToken}`,
918
- Accept: "application/json",
919
- "User-Agent": "opencode-kiro"
920
- }
921
- });
922
- if (!resp.ok) {
923
- throw new Error(`ListAvailableModels failed: HTTP ${resp.status}`);
924
- }
925
- const data = await resp.json();
926
- return (data.models ?? []).filter((m) => m.modelId !== "auto");
927
- }
928
- var REASONING_FAMILIES = new Set([
929
- "claude-fable",
930
- "claude-sonnet",
931
- "claude-opus",
932
- "deepseek",
933
- "kimi",
934
- "glm",
935
- "qwen",
936
- "agi-nova",
937
- "minimax"
938
- ]);
939
- function isReasoningModel(dotId) {
940
- for (const family of REASONING_FAMILIES) {
941
- if (dotId.startsWith(family))
942
- return true;
943
- }
944
- return false;
945
- }
946
- function firstTokenTimeout(dotId) {
947
- if (dotId.startsWith("claude-fable") || dotId.startsWith("claude-opus"))
948
- return 180000;
949
- return 90000;
950
- }
951
- function buildModelsFromApi(apiModels) {
952
- return apiModels.map((m) => {
953
- KIRO_MODEL_IDS.add(m.modelId);
954
- const dashId = dotToDash(m.modelId);
955
- const supportedTypes = m.supportedInputTypes ?? ["TEXT"];
956
- const input = supportedTypes.includes("IMAGE") ? ["text", "image"] : ["text"];
957
- const effortEnum = m.additionalModelRequestFieldsSchema?.properties?.output_config?.properties?.effort?.enum;
958
- const supportedEfforts = Array.isArray(effortEnum) && effortEnum.length > 0 ? effortEnum : undefined;
959
- const supportsThinkingConfig = !!m.additionalModelRequestFieldsSchema?.properties?.thinking;
960
- return {
961
- ...KIRO_DEFAULTS,
962
- id: dashId,
963
- name: m.modelName,
964
- reasoning: isReasoningModel(m.modelId),
965
- input,
966
- contextWindow: m.tokenLimits?.maxInputTokens ?? 200000,
967
- maxTokens: m.tokenLimits?.maxOutputTokens ?? 8192,
968
- firstTokenTimeout: firstTokenTimeout(m.modelId),
969
- ...supportedEfforts ? { supportedEfforts } : {},
970
- ...supportsThinkingConfig ? { supportsThinkingConfig } : {}
971
- };
972
- });
973
- }
974
- var cachedDynamicModels = null;
975
- function getCachedDynamicModels() {
976
- return cachedDynamicModels;
977
- }
978
- function setCachedDynamicModels(models) {
979
- cachedDynamicModels = models;
980
- }
1284
+ // src/stream.ts
1285
+ init_models();
981
1286
 
982
1287
  // src/thinking-parser.ts
983
1288
  init_debug();
@@ -1214,7 +1519,9 @@ function countTokens(text) {
1214
1519
 
1215
1520
  // src/oauth.ts
1216
1521
  init_debug();
1217
- var BUILDER_ID_START_URL = "https://view.awsapps.com/start";
1522
+ init_models();
1523
+ import { createServer } from "node:http";
1524
+ import { randomBytes, createHash } from "node:crypto";
1218
1525
  var BUILDER_ID_REGION = "us-east-1";
1219
1526
  var SSO_SCOPES = [
1220
1527
  "codewhisperer:completions",
@@ -1333,10 +1640,12 @@ async function refreshKiroToken(refreshTokenPacked, region, authMethod) {
1333
1640
  const refreshToken = parts[0] ?? "";
1334
1641
  const clientId = parts[1] ?? "";
1335
1642
  const clientSecret = parts[2] ?? "";
1643
+ const source = parts[4] ?? "";
1644
+ const tokenKey = parts[5] ?? "";
1336
1645
  if (!refreshToken || !region) {
1337
1646
  throw new Error("Refresh token or region is missing — re-login required");
1338
1647
  }
1339
- if (authMethod === "desktop") {
1648
+ if (authMethod === "desktop" || authMethod === "social") {
1340
1649
  const desktopEndpoint = `https://prod.${region}.auth.desktop.kiro.dev/refreshToken`;
1341
1650
  const resp2 = await fetch(desktopEndpoint, {
1342
1651
  method: "POST",
@@ -1348,9 +1657,23 @@ async function refreshKiroToken(refreshTokenPacked, region, authMethod) {
1348
1657
  throw new Error(`Desktop token refresh failed: ${resp2.status} ${body}`);
1349
1658
  }
1350
1659
  const data2 = await resp2.json();
1660
+ const newPacked2 = `${data2.refreshToken}|||${authMethod}|${source}|${tokenKey}`;
1661
+ if (source === "kiro-cli-db" && tokenKey) {
1662
+ const { saveKiroCliCredentials: saveKiroCliCredentials2 } = await Promise.resolve().then(() => (init_kiro_cli_sync(), exports_kiro_cli_sync));
1663
+ await saveKiroCliCredentials2({
1664
+ accessToken: data2.accessToken,
1665
+ refreshToken: data2.refreshToken,
1666
+ clientId: "",
1667
+ clientSecret: "",
1668
+ region,
1669
+ authMethod,
1670
+ source: "kiro-cli-db",
1671
+ tokenKey
1672
+ });
1673
+ }
1351
1674
  return {
1352
1675
  access: data2.accessToken,
1353
- refresh: `${data2.refreshToken}|||desktop`,
1676
+ refresh: newPacked2,
1354
1677
  expires: Date.now() + (data2.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS
1355
1678
  };
1356
1679
  }
@@ -1368,15 +1691,411 @@ async function refreshKiroToken(refreshTokenPacked, region, authMethod) {
1368
1691
  throw new Error(`Token refresh failed: ${resp.status} ${body}`);
1369
1692
  }
1370
1693
  const data = await resp.json();
1694
+ const newPacked = `${data.refreshToken}|${clientId}|${clientSecret}|${authMethod}|${source}|${tokenKey}`;
1695
+ if (source === "kiro-cli-db" && tokenKey) {
1696
+ const { saveKiroCliCredentials: saveKiroCliCredentials2 } = await Promise.resolve().then(() => (init_kiro_cli_sync(), exports_kiro_cli_sync));
1697
+ await saveKiroCliCredentials2({
1698
+ accessToken: data.accessToken,
1699
+ refreshToken: data.refreshToken,
1700
+ clientId,
1701
+ clientSecret,
1702
+ region,
1703
+ authMethod,
1704
+ source: "kiro-cli-db",
1705
+ tokenKey
1706
+ });
1707
+ }
1371
1708
  return {
1372
1709
  access: data.accessToken,
1373
- refresh: `${data.refreshToken}|${clientId}|${clientSecret}|${authMethod}`,
1710
+ refresh: newPacked,
1374
1711
  expires: Date.now() + (data.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS
1375
1712
  };
1376
1713
  }
1714
+ var KIRO_SOCIAL_PORTAL = "https://app.kiro.dev";
1715
+ var KIRO_SOCIAL_AUTH_ENDPOINT = `https://prod.${BUILDER_ID_REGION}.auth.desktop.kiro.dev`;
1716
+ var SOCIAL_REDIRECT_PORT = 49153;
1717
+ var SOCIAL_REDIRECT_URI = `http://localhost:${SOCIAL_REDIRECT_PORT}`;
1718
+ function generateRandomState() {
1719
+ return randomBytes(16).toString("base64url");
1720
+ }
1721
+ function generateCodeVerifier() {
1722
+ return randomBytes(32).toString("base64url");
1723
+ }
1724
+ function generateCodeChallenge(verifier) {
1725
+ return createHash("sha256").update(verifier).digest("base64url");
1726
+ }
1727
+ function buildSocialSignInURL(redirectUri, codeChallenge, state) {
1728
+ const params = new URLSearchParams;
1729
+ params.set("code_challenge", codeChallenge);
1730
+ params.set("code_challenge_method", "S256");
1731
+ params.set("redirect_from", "kirocli");
1732
+ params.set("redirect_uri", redirectUri);
1733
+ params.set("state", state);
1734
+ return `${KIRO_SOCIAL_PORTAL}/signin?${params.toString()}`;
1735
+ }
1736
+ function buildTokenRedirectUri(callbackPath, loginOption) {
1737
+ const path = callbackPath || "/oauth/callback";
1738
+ const base = `${SOCIAL_REDIRECT_URI}${path}`;
1739
+ if (loginOption) {
1740
+ return `${base}?login_option=${encodeURIComponent(loginOption)}`;
1741
+ }
1742
+ return base;
1743
+ }
1744
+ function oauthCallbackPage(kind, title, message, redirectUrl = "https://app.kiro.dev") {
1745
+ const borderColor = kind === "success" ? "#22c55e" : "#ef4444";
1746
+ 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>`;
1747
+ const ghostSvg = `<svg width="56" height="56" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
1748
+ <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"/>
1749
+ <circle cx="37" cy="45" r="7" fill="#0a0a0a"/>
1750
+ <circle cx="63" cy="45" r="7" fill="#0a0a0a"/>
1751
+ </svg>`;
1752
+ const redirectHost = (() => {
1753
+ try {
1754
+ return new URL(redirectUrl).hostname;
1755
+ } catch {
1756
+ return redirectUrl;
1757
+ }
1758
+ })();
1759
+ const countdownHtml = kind === "success" ? `
1760
+ <div class="countdown" id="countdown">
1761
+ <svg class="ring" viewBox="0 0 60 60">
1762
+ <circle cx="30" cy="30" r="26" stroke="#1a1a1a" stroke-width="3" fill="none"/>
1763
+ <circle id="ring-progress" cx="30" cy="30" r="26" stroke="#22c55e" stroke-width="3" fill="none"
1764
+ stroke-dasharray="163.36" stroke-dashoffset="0" stroke-linecap="round"
1765
+ transform="rotate(-90 30 30)" style="transition:stroke-dashoffset 1s linear"/>
1766
+ </svg>
1767
+ <span class="countdown-num" id="countdown-num">3</span>
1768
+ </div>
1769
+ <p class="subtitle">Redirecting to <strong>${redirectHost}</strong>…</p>
1770
+ <script>
1771
+ (function(){
1772
+ var n=3, el=document.getElementById('countdown-num'),
1773
+ ring=document.getElementById('ring-progress'), circ=163.36;
1774
+ function tick(){
1775
+ if(n<=0){window.location.href=${JSON.stringify(redirectUrl)};return}
1776
+ el.textContent=n;
1777
+ ring.setAttribute('stroke-dashoffset', String(circ*(1-n/3)));
1778
+ n--;
1779
+ setTimeout(tick,1000);
1780
+ }
1781
+ tick();
1782
+ })();
1783
+ </script>` : `<p class="subtitle">Please close this window and try again</p>`;
1784
+ return `<!DOCTYPE html>
1785
+ <html lang="en">
1786
+ <head>
1787
+ <meta charset="utf-8">
1788
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1789
+ <title>OPENCODE-KIRO — Authentication</title>
1790
+ <style>
1791
+ *{margin:0;padding:0;box-sizing:border-box}
1792
+ 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}
1793
+ .container{max-width:420px;padding:2rem}
1794
+ .logo{display:flex;align-items:center;justify-content:center;gap:16px;margin-bottom:48px}
1795
+ .logo-text{font-size:32px;font-weight:700;letter-spacing:4px;color:#7c3aed;font-family:'Courier New',monospace}
1796
+ .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)}
1797
+ .status-icon{flex-shrink:0;margin-top:2px}
1798
+ .status-title{font-size:15px;font-weight:600;color:${borderColor};margin-bottom:4px}
1799
+ .status-msg{font-size:13px;color:#a3a3a3;line-height:1.4}
1800
+ .subtitle{font-size:13px;color:#737373;margin-top:4px}
1801
+ .subtitle strong{color:#a3a3a3}
1802
+ .countdown{position:relative;width:60px;height:60px;margin:24px auto 12px}
1803
+ .ring{width:60px;height:60px}
1804
+ .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}
1805
+ </style>
1806
+ </head>
1807
+ <body>
1808
+ <div class="container">
1809
+ <div class="logo">
1810
+ ${ghostSvg}
1811
+ <span class="logo-text">OPENCODE-KIRO</span>
1812
+ </div>
1813
+ <div class="status-box">
1814
+ <span class="status-icon">${iconSvg}</span>
1815
+ <div>
1816
+ <div class="status-title">${title}</div>
1817
+ <div class="status-msg">${message}</div>
1818
+ </div>
1819
+ </div>
1820
+ ${countdownHtml}
1821
+ </div>
1822
+ </body>
1823
+ </html>`;
1824
+ }
1825
+ function oauthIdcDelegationPage() {
1826
+ const ghostSvg = `<svg width="56" height="56" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
1827
+ <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"/>
1828
+ <circle cx="37" cy="45" r="7" fill="#0a0a0a"/>
1829
+ <circle cx="63" cy="45" r="7" fill="#0a0a0a"/>
1830
+ </svg>`;
1831
+ return `<!DOCTYPE html>
1832
+ <html lang="en">
1833
+ <head>
1834
+ <meta charset="utf-8">
1835
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1836
+ <title>OPENCODE-KIRO — Enterprise Sign-In</title>
1837
+ <style>
1838
+ *{margin:0;padding:0;box-sizing:border-box}
1839
+ 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}
1840
+ .container{max-width:420px;padding:2rem}
1841
+ .logo{display:flex;align-items:center;justify-content:center;gap:16px;margin-bottom:48px}
1842
+ .logo-text{font-size:32px;font-weight:700;letter-spacing:4px;color:#7c3aed;font-family:'Courier New',monospace}
1843
+ .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)}
1844
+ .status-icon{flex-shrink:0;margin-top:2px}
1845
+ .status-title{font-size:15px;font-weight:600;color:#22c55e;margin-bottom:4px}
1846
+ .status-msg{font-size:13px;color:#a3a3a3;line-height:1.4}
1847
+ .subtitle{font-size:13px;color:#737373;margin-top:4px}
1848
+ .subtitle strong{color:#a3a3a3}
1849
+ .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}
1850
+ @keyframes spin{to{transform:rotate(360deg)}}
1851
+ .countdown{position:relative;width:60px;height:60px;margin:24px auto 12px;display:none}
1852
+ .ring{width:60px;height:60px}
1853
+ .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}
1854
+ </style>
1855
+ </head>
1856
+ <body>
1857
+ <div class="container">
1858
+ <div class="logo">
1859
+ ${ghostSvg}
1860
+ <span class="logo-text">OPENCODE-KIRO</span>
1861
+ </div>
1862
+ <div class="status-box">
1863
+ <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>
1864
+ <div>
1865
+ <div class="status-title">Enterprise sign-in</div>
1866
+ <div class="status-msg" id="status-msg">Preparing device authorization…</div>
1867
+ </div>
1868
+ </div>
1869
+ <div class="spinner" id="spinner"></div>
1870
+ <div class="countdown" id="countdown">
1871
+ <svg class="ring" viewBox="0 0 60 60">
1872
+ <circle cx="30" cy="30" r="26" stroke="#1a1a1a" stroke-width="3" fill="none"/>
1873
+ <circle id="ring-progress" cx="30" cy="30" r="26" stroke="#22c55e" stroke-width="3" fill="none"
1874
+ stroke-dasharray="163.36" stroke-dashoffset="0" stroke-linecap="round"
1875
+ transform="rotate(-90 30 30)" style="transition:stroke-dashoffset 1s linear"/>
1876
+ </svg>
1877
+ <span class="countdown-num" id="countdown-num">3</span>
1878
+ </div>
1879
+ <p class="subtitle" id="subtitle">Waiting for device authorization…</p>
1880
+ <script>
1881
+ (function(){
1882
+ var msg=document.getElementById('status-msg'),
1883
+ spinner=document.getElementById('spinner'),
1884
+ cd=document.getElementById('countdown'),
1885
+ cdNum=document.getElementById('countdown-num'),
1886
+ ring=document.getElementById('ring-progress'),
1887
+ sub=document.getElementById('subtitle'),
1888
+ circ=163.36;
1889
+
1890
+ function poll(){
1891
+ fetch('/idc-verify').then(function(r){return r.json()}).then(function(d){
1892
+ if(d.url){
1893
+ spinner.style.display='none';
1894
+ cd.style.display='block';
1895
+ msg.textContent='Device authorization ready';
1896
+ try{sub.innerHTML='Redirecting to <strong>'+new URL(d.url).hostname+'</strong>…'}catch(e){}
1897
+ countdown(3,d.url);
1898
+ } else {
1899
+ setTimeout(poll,500);
1900
+ }
1901
+ }).catch(function(){setTimeout(poll,1000)});
1902
+ }
1903
+
1904
+ function countdown(n,url){
1905
+ if(n<=0){window.location.href=url;return}
1906
+ cdNum.textContent=n;
1907
+ ring.setAttribute('stroke-dashoffset',String(circ*(1-n/3)));
1908
+ setTimeout(function(){countdown(n-1,url)},1000);
1909
+ }
1910
+
1911
+ poll();
1912
+ })();
1913
+ </script>
1914
+ </div>
1915
+ </body>
1916
+ </html>`;
1917
+ }
1918
+ function startCallbackServer(expectedState) {
1919
+ return new Promise((resolve2, reject) => {
1920
+ let settleWait;
1921
+ const waitForCodePromise = new Promise((res) => {
1922
+ settleWait = res;
1923
+ });
1924
+ let idcVerifyUrl = null;
1925
+ const server = createServer((req, res) => {
1926
+ try {
1927
+ const url = new URL(req.url ?? "", SOCIAL_REDIRECT_URI);
1928
+ if (url.pathname === "/idc-verify") {
1929
+ res.writeHead(200, {
1930
+ "Content-Type": "application/json",
1931
+ "Access-Control-Allow-Origin": "*",
1932
+ "Cache-Control": "no-store"
1933
+ });
1934
+ res.end(JSON.stringify({ url: idcVerifyUrl }));
1935
+ return;
1936
+ }
1937
+ const code = url.searchParams.get("code");
1938
+ const state = url.searchParams.get("state");
1939
+ const loginOption = url.searchParams.get("login_option");
1940
+ const issuerUrl = url.searchParams.get("issuer_url");
1941
+ const idcRegion = url.searchParams.get("idc_region");
1942
+ const isIdcDelegation = loginOption === "awsidc" && !!issuerUrl && !!state;
1943
+ if (!state || !code && !isIdcDelegation) {
1944
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1945
+ res.end("");
1946
+ return;
1947
+ }
1948
+ if (state !== expectedState) {
1949
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
1950
+ res.end(oauthCallbackPage("error", "State mismatch", "The OAuth state parameter did not match. Please try logging in again."));
1951
+ return;
1952
+ }
1953
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1954
+ if (isIdcDelegation) {
1955
+ res.end(oauthIdcDelegationPage());
1956
+ } else {
1957
+ res.end(oauthCallbackPage("success", "Request approved", "OPENCODE-KIRO has been given requested permissions."));
1958
+ }
1959
+ settleWait?.({
1960
+ code,
1961
+ state,
1962
+ callbackPath: url.pathname,
1963
+ loginOption,
1964
+ issuerUrl: issuerUrl ?? undefined,
1965
+ idcRegion: idcRegion ?? undefined
1966
+ });
1967
+ } catch {
1968
+ res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
1969
+ res.end("Internal error");
1970
+ }
1971
+ });
1972
+ server.on("error", (err) => {
1973
+ reject(err);
1974
+ });
1975
+ server.listen(SOCIAL_REDIRECT_PORT, "localhost", () => {
1976
+ resolve2({
1977
+ server,
1978
+ redirectUri: SOCIAL_REDIRECT_URI,
1979
+ cancelWait: () => {
1980
+ settleWait?.(null);
1981
+ },
1982
+ waitForCode: () => waitForCodePromise,
1983
+ setIdcVerifyUrl: (url) => {
1984
+ idcVerifyUrl = url;
1985
+ }
1986
+ });
1987
+ });
1988
+ });
1989
+ }
1990
+ async function startSocialLogin() {
1991
+ const verifier = generateCodeVerifier();
1992
+ const challenge = generateCodeChallenge(verifier);
1993
+ const state = generateRandomState();
1994
+ const callbackServer = await startCallbackServer(state);
1995
+ const signInUrl = buildSocialSignInURL(callbackServer.redirectUri, challenge, state);
1996
+ const waitForCredentials = async () => {
1997
+ try {
1998
+ const result = await callbackServer.waitForCode();
1999
+ if (result?.loginOption === "awsidc" && result.issuerUrl) {
2000
+ log.info("[social-login] Enterprise IdC delegation detected");
2001
+ const idcRegion = result.idcRegion || BUILDER_ID_REGION;
2002
+ const regions = [idcRegion];
2003
+ let regResult = null;
2004
+ let detectedRegion = "";
2005
+ for (const region of regions) {
2006
+ regResult = await tryRegisterAndAuthorize(result.issuerUrl, region);
2007
+ if (regResult) {
2008
+ detectedRegion = region;
2009
+ break;
2010
+ }
2011
+ }
2012
+ if (!regResult || !detectedRegion) {
2013
+ throw new Error(`Could not authorize ${result.issuerUrl} in ${regions.join(", ")}. Check your start URL and region and try again.`);
2014
+ }
2015
+ callbackServer.setIdcVerifyUrl(regResult.devAuth.verificationUriComplete);
2016
+ log.info(`[social-login] IdC device auth ready, verification URL sent to browser`);
2017
+ const tok = await pollForToken(regResult.oidcEndpoint, regResult.clientId, regResult.clientSecret, regResult.devAuth, undefined);
2018
+ if (!tok.accessToken || !tok.refreshToken) {
2019
+ throw new Error("IdC authorization completed but no tokens returned");
2020
+ }
2021
+ let profileArn;
2022
+ try {
2023
+ const apiRegion = resolveApiRegion(detectedRegion);
2024
+ const { resolveProfileArn: resolveProfileArn2 } = await Promise.resolve().then(() => (init_models(), exports_models));
2025
+ const resolved = await resolveProfileArn2(tok.accessToken, apiRegion);
2026
+ if (resolved) {
2027
+ profileArn = resolved;
2028
+ try {
2029
+ const apiModels = await fetchAvailableModels(tok.accessToken, apiRegion, profileArn);
2030
+ setCachedDynamicModels(buildModelsFromApi(apiModels));
2031
+ log.info(`[social-login] IdC: fetched and cached ${apiModels.length} models`);
2032
+ } catch (err) {
2033
+ log.warn(`[social-login] IdC: failed to fetch models: ${err}`);
2034
+ }
2035
+ }
2036
+ } catch (err) {
2037
+ log.warn(`[social-login] IdC: failed to resolve profileArn: ${err}`);
2038
+ }
2039
+ return {
2040
+ accessToken: tok.accessToken,
2041
+ refreshToken: tok.refreshToken,
2042
+ refreshPacked: `${tok.refreshToken}|${regResult.clientId}|${regResult.clientSecret}|idc`,
2043
+ profileArn,
2044
+ region: detectedRegion,
2045
+ authMethod: "idc",
2046
+ expiresAt: Date.now() + (tok.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS
2047
+ };
2048
+ }
2049
+ if (!result?.code) {
2050
+ throw new Error("Missing authorization code — sign-in was not completed");
2051
+ }
2052
+ const tokenRedirectUri = buildTokenRedirectUri(result.callbackPath, result.loginOption);
2053
+ log.info("[social-login] Exchanging authorization code…");
2054
+ const resp = await fetch(`${KIRO_SOCIAL_AUTH_ENDPOINT}/oauth/token`, {
2055
+ method: "POST",
2056
+ headers: { "Content-Type": "application/json", "User-Agent": "opencode-kiro" },
2057
+ body: JSON.stringify({
2058
+ code: result.code,
2059
+ code_verifier: verifier,
2060
+ redirect_uri: tokenRedirectUri
2061
+ })
2062
+ });
2063
+ if (!resp.ok) {
2064
+ const body = await resp.text().catch(() => "");
2065
+ throw new Error(`Token exchange failed: ${resp.status} ${body}`);
2066
+ }
2067
+ const data = await resp.json();
2068
+ if (!data.accessToken || !data.refreshToken) {
2069
+ throw new Error("Token exchange returned no tokens");
2070
+ }
2071
+ if (data.profileArn) {
2072
+ try {
2073
+ const apiRegion = resolveApiRegion(BUILDER_ID_REGION);
2074
+ const apiModels = await fetchAvailableModels(data.accessToken, apiRegion, data.profileArn);
2075
+ setCachedDynamicModels(buildModelsFromApi(apiModels));
2076
+ log.info(`[social-login] Fetched and cached ${apiModels.length} models`);
2077
+ } catch (err) {
2078
+ log.warn(`[social-login] Failed to fetch models: ${err}`);
2079
+ }
2080
+ }
2081
+ return {
2082
+ accessToken: data.accessToken,
2083
+ refreshToken: data.refreshToken,
2084
+ refreshPacked: `${data.refreshToken}|||social`,
2085
+ profileArn: data.profileArn,
2086
+ region: BUILDER_ID_REGION,
2087
+ authMethod: "social",
2088
+ expiresAt: Date.now() + (data.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS
2089
+ };
2090
+ } finally {
2091
+ callbackServer.server.close();
2092
+ }
2093
+ };
2094
+ return { signInUrl, waitForCredentials };
2095
+ }
1377
2096
 
1378
2097
  // src/transform.ts
1379
- import { createHash } from "node:crypto";
2098
+ import { createHash as createHash2 } from "node:crypto";
1380
2099
 
1381
2100
  // src/kiro-defaults.ts
1382
2101
  var SYSTEM_SEED_INSTRUCTION = `Follow this instruction: # Kiro CLI Default Agent
@@ -1459,7 +2178,7 @@ var KIRO_TOOL_USE_ID_RE = /^tooluse_[A-Za-z0-9]+$/;
1459
2178
  function toKiroToolUseId(id) {
1460
2179
  if (KIRO_TOOL_USE_ID_RE.test(id))
1461
2180
  return id;
1462
- const digest = createHash("sha256").update(id).digest("hex").slice(0, 22);
2181
+ const digest = createHash2("sha256").update(id).digest("hex").slice(0, 22);
1463
2182
  return `tooluse_${digest}`;
1464
2183
  }
1465
2184
  var ALLOWED_SCHEMA_KEYS = new Set([
@@ -1790,49 +2509,6 @@ function emitHiddenReasoningLate(output, stream) {
1790
2509
  partial: output
1791
2510
  });
1792
2511
  }
1793
- var profileArnCache = new Map;
1794
- var profileArnSkipResolution = false;
1795
- function seedProfileArn(endpoint, arn) {
1796
- profileArnCache.set(endpoint, arn);
1797
- }
1798
- async function resolveProfileArn(accessToken, endpoint) {
1799
- if (profileArnSkipResolution)
1800
- return;
1801
- const cached = profileArnCache.get(endpoint);
1802
- if (cached !== undefined)
1803
- return cached;
1804
- try {
1805
- const ep = new URL(endpoint);
1806
- ep.hostname = ep.hostname.replace("runtime.", "management.");
1807
- ep.pathname = "/";
1808
- ep.search = "";
1809
- ep.hash = "";
1810
- const resp = await fetch(ep.toString(), {
1811
- method: "POST",
1812
- headers: {
1813
- "Content-Type": "application/x-amz-json-1.0",
1814
- Authorization: `Bearer ${accessToken}`,
1815
- "X-Amz-Target": "AmazonCodeWhispererService.ListAvailableProfiles"
1816
- },
1817
- body: "{}"
1818
- });
1819
- if (!resp.ok) {
1820
- log.debug(`profileArn resolution failed: ${resp.status} ${resp.statusText}`);
1821
- return;
1822
- }
1823
- const j = await resp.json();
1824
- const arn = j.profiles?.find((p) => p.arn)?.arn;
1825
- if (!arn) {
1826
- log.debug("profileArn resolution returned no profile ARN");
1827
- return;
1828
- }
1829
- profileArnCache.set(endpoint, arn);
1830
- return arn;
1831
- } catch (error) {
1832
- log.debug(`profileArn resolution threw: ${error instanceof Error ? error.message : String(error)}`);
1833
- return;
1834
- }
1835
- }
1836
2512
  function emitToolCall(state, output, stream) {
1837
2513
  if (!state.input.trim())
1838
2514
  state.input = "{}";
@@ -2115,8 +2791,9 @@ ${currentContent}`;
2115
2791
  let transientRetryCount = 0;
2116
2792
  let contextTruncationAttempt = 0;
2117
2793
  while (true) {
2118
- const mid = crypto.randomUUID().replace(/-/g, "");
2119
- const ua = `aws-sdk-rust/1.0.0 ua/2.1 os/other lang/rust api/codewhispererstreaming#1.28.3 m/E app/AmazonQ-For-CLI md/appVersion-1.28.3-${mid}`;
2794
+ const osName = resolveOS();
2795
+ const ua = `aws-sdk-rust/1.3.15 ua/2.1 api/codewhispererstreaming/0.1.16551 os/${osName} lang/rust/1.92.0 md/appVersion-2.7.1 app/AmazonQ-For-CLI`;
2796
+ const xAmzUa = `aws-sdk-rust/1.3.15 ua/2.1 api/codewhispererstreaming/0.1.16551 os/${osName} lang/rust/1.92.0 m/F app/AmazonQ-For-CLI`;
2120
2797
  const requestBody = JSON.stringify(request);
2121
2798
  log.debug("request.send", {
2122
2799
  attempt: retryCount,
@@ -2137,15 +2814,17 @@ ${currentContent}`;
2137
2814
  method: "POST",
2138
2815
  headers: {
2139
2816
  "Content-Type": "application/x-amz-json-1.0",
2140
- Accept: "application/json",
2817
+ Accept: "*/*",
2818
+ "Accept-Encoding": "gzip",
2141
2819
  Authorization: `Bearer ${accessToken}`,
2142
2820
  "X-Amz-Target": "AmazonCodeWhispererStreamingService.GenerateAssistantResponse",
2143
2821
  "x-amzn-codewhisperer-optout": "true",
2144
2822
  "amz-sdk-invocation-id": crypto.randomUUID(),
2145
- "amz-sdk-request": "attempt=1; max=1",
2146
- "x-amzn-kiro-agent-mode": "vibe",
2147
- "x-amz-user-agent": ua,
2148
- "user-agent": ua
2823
+ "amz-sdk-request": "attempt=1; max=3",
2824
+ "user-agent": ua,
2825
+ "x-amz-user-agent": xAmzUa,
2826
+ Pragma: "no-cache",
2827
+ "Cache-Control": "no-cache"
2149
2828
  },
2150
2829
  body: requestBody,
2151
2830
  signal: options?.signal
@@ -2195,12 +2874,12 @@ ${currentContent}`;
2195
2874
  if (response.status === 401) {
2196
2875
  const permanent = isPermanentError(errText);
2197
2876
  if (permanent) {
2198
- profileArnCache.delete(endpoint);
2877
+ resetProfileArnCache();
2199
2878
  throw new Error(`Kiro API error: credentials permanently invalid — run /login kiro to re-authenticate. ${errText}`);
2200
2879
  }
2201
2880
  }
2202
2881
  if (response.status === 403) {
2203
- profileArnCache.delete(endpoint);
2882
+ resetProfileArnCache();
2204
2883
  throw new Error(`Kiro API error: access token rejected (403) — run /login kiro to re-authenticate. ${errText}`);
2205
2884
  }
2206
2885
  throw new Error(`Kiro API error: ${response.status} ${response.statusText} ${errText}`);
@@ -2508,6 +3187,8 @@ ${currentContent}`;
2508
3187
 
2509
3188
  // src/server.ts
2510
3189
  init_debug();
3190
+ init_models();
3191
+ init_models();
2511
3192
 
2512
3193
  // src/dashboard-stats.ts
2513
3194
  var MAX_HISTORY = 100;
@@ -2880,11 +3561,29 @@ async function initGatewayAuth() {
2880
3561
  expiresAt: Date.now() + 3500000
2881
3562
  };
2882
3563
  log.info(`[gateway-auth] Initialized (method=${imported.authMethod}, region=${imported.region})`);
3564
+ let activeAccessToken = imported.accessToken;
3565
+ if (imported.refreshToken) {
3566
+ try {
3567
+ log.info("[gateway-auth] Refreshing token at startup…");
3568
+ const refreshed = await refreshKiroToken(_creds.refreshPacked, _creds.region, _creds.authMethod);
3569
+ _creds.accessToken = refreshed.access;
3570
+ _creds.refreshPacked = refreshed.refresh;
3571
+ _creds.expiresAt = refreshed.expires;
3572
+ activeAccessToken = refreshed.access;
3573
+ log.info("[gateway-auth] Token refreshed on startup successfully");
3574
+ } catch (err) {
3575
+ log.warn("[gateway-auth] Startup token refresh failed, trying with existing token", err);
3576
+ }
3577
+ }
2883
3578
  try {
2884
- const apiModels = await fetchAvailableModels(imported.accessToken, imported.region, imported.profileArn);
2885
- const built = buildModelsFromApi(apiModels);
2886
- setCachedDynamicModels(built);
2887
- log.info(`[kiro-models.fetched] Found ${built.length} available models`);
3579
+ if (imported.profileArn) {
3580
+ const apiModels = await fetchAvailableModels(activeAccessToken, imported.region, imported.profileArn);
3581
+ const built = buildModelsFromApi(apiModels);
3582
+ setCachedDynamicModels(built);
3583
+ log.info(`[kiro-models.fetched] Found ${built.length} available models`);
3584
+ } else {
3585
+ log.info(`[kiro-models.fetched] Skipping fetch, no profileArn available`);
3586
+ }
2888
3587
  } catch (err) {
2889
3588
  log.warn("[gateway-auth] Failed to fetch dynamic models (will fallback to static list)", err);
2890
3589
  }
@@ -2945,6 +3644,36 @@ function startGatewayServer(port = 0) {
2945
3644
  headers: { "Content-Type": "application/json" }
2946
3645
  });
2947
3646
  }
3647
+ if (url.pathname === "/auth/login" && req.method === "GET") {
3648
+ try {
3649
+ const { signInUrl, waitForCredentials } = await startSocialLogin();
3650
+ log.info("[gateway] Social login initiated, redirecting to Kiro sign-in");
3651
+ waitForCredentials().then((creds) => {
3652
+ _creds = {
3653
+ accessToken: creds.accessToken,
3654
+ refreshPacked: creds.refreshPacked,
3655
+ region: creds.region,
3656
+ authMethod: creds.authMethod,
3657
+ profileArn: creds.profileArn,
3658
+ expiresAt: creds.expiresAt
3659
+ };
3660
+ if (creds.profileArn) {
3661
+ seedProfileArn(creds.profileArn);
3662
+ }
3663
+ log.info(`[gateway] Login completed (${creds.authMethod}) — credentials updated`);
3664
+ }).catch((err) => {
3665
+ log.error("[gateway] Login failed:", err);
3666
+ });
3667
+ return new Response(null, {
3668
+ status: 302,
3669
+ headers: { Location: signInUrl }
3670
+ });
3671
+ } catch (err) {
3672
+ const msg = err instanceof Error ? err.message : String(err);
3673
+ log.error("[gateway] Failed to start social login:", msg);
3674
+ return anthropicError(500, "api_error", `Failed to start login: ${msg}`);
3675
+ }
3676
+ }
2948
3677
  if ((url.pathname === "/v1/messages" || url.pathname === "/messages") && req.method === "POST") {
2949
3678
  let accessToken;
2950
3679
  try {
@@ -2996,7 +3725,7 @@ function startGatewayServer(port = 0) {
2996
3725
  const reasoningEffort = body.output_config?.effort ?? body.reasoning_effort ?? undefined;
2997
3726
  log.info(`[gateway] → ${kiroEndpoint} model=${anthropicModelId} region=${apiRegion} stream=${streamRequested}`);
2998
3727
  if (_creds.profileArn) {
2999
- seedProfileArn(kiroEndpoint, _creds.profileArn);
3728
+ seedProfileArn(_creds.profileArn);
3000
3729
  }
3001
3730
  const kiroStream = streamKiro(piModel, context, {
3002
3731
  apiKey: accessToken,
@@ -3367,6 +4096,7 @@ function translateAnthropicToolsToPi(tools) {
3367
4096
 
3368
4097
  // src/index.ts
3369
4098
  init_debug();
4099
+ init_models();
3370
4100
  process.env.KIRO_LOG = process.env.KIRO_LOG || "debug";
3371
4101
  process.env.KIRO_LOG_FILE = process.env.KIRO_LOG_FILE || "/tmp/opencode-kiro.log";
3372
4102
  var gatewayServer = null;
@@ -3415,30 +4145,28 @@ var KiroPlugin = async (input) => {
3415
4145
  label: "AWS Builder ID (personal account)",
3416
4146
  prompts: [],
3417
4147
  authorize: async () => {
3418
- const result = await tryRegisterAndAuthorize(BUILDER_ID_START_URL, BUILDER_ID_REGION);
3419
- if (!result) {
3420
- throw new Error("Could not authorize with AWS Builder ID.");
3421
- }
4148
+ const { signInUrl, waitForCredentials } = await startSocialLogin();
3422
4149
  return {
3423
- url: result.devAuth.verificationUriComplete,
3424
- instructions: `AWS Verification Code: ${result.devAuth.userCode}
3425
- Complete authorization in your browser, then OpenCode will continue automatically.`,
4150
+ url: signInUrl,
4151
+ instructions: "Complete sign-in in your browser. OpenCode will continue automatically.",
3426
4152
  method: "auto",
3427
4153
  callback: async () => {
3428
- const tok = await pollForToken(result.oidcEndpoint, result.clientId, result.clientSecret, result.devAuth, undefined);
3429
- if (!tok.accessToken || !tok.refreshToken) {
4154
+ try {
4155
+ const creds = await waitForCredentials();
4156
+ return {
4157
+ type: "success",
4158
+ access: creds.accessToken,
4159
+ refresh: creds.refreshPacked,
4160
+ expires: creds.expiresAt,
4161
+ metadata: {
4162
+ region: creds.region,
4163
+ authMethod: creds.authMethod,
4164
+ profileArn: creds.profileArn
4165
+ }
4166
+ };
4167
+ } catch {
3430
4168
  return { type: "failed" };
3431
4169
  }
3432
- return {
3433
- type: "success",
3434
- access: tok.accessToken,
3435
- refresh: `${tok.refreshToken}|${result.clientId}|${result.clientSecret}|builder-id`,
3436
- expires: Date.now() + (tok.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS,
3437
- metadata: {
3438
- region: BUILDER_ID_REGION,
3439
- authMethod: "builder-id"
3440
- }
3441
- };
3442
4170
  }
3443
4171
  };
3444
4172
  }
@@ -3489,14 +4217,25 @@ Complete authorization in your browser, then OpenCode will continue automaticall
3489
4217
  if (!tok.accessToken || !tok.refreshToken) {
3490
4218
  return { type: "failed" };
3491
4219
  }
4220
+ const apiRegion = resolveApiRegion(detectedRegion);
4221
+ const arn = await resolveProfileArn(tok.accessToken, apiRegion);
4222
+ if (arn) {
4223
+ try {
4224
+ const models = await fetchAvailableModels(tok.accessToken, apiRegion, arn);
4225
+ setCachedDynamicModels(buildModelsFromApi(models));
4226
+ } catch (e) {
4227
+ log.warn("Failed to precache models", e);
4228
+ }
4229
+ }
3492
4230
  return {
3493
4231
  type: "success",
3494
4232
  access: tok.accessToken,
3495
- refresh: `${tok.refreshToken}|${result.clientId}|${result.clientSecret}|idc`,
4233
+ refresh: `${tok.refreshToken}|${result.clientId}|${result.clientSecret}|idc||`,
3496
4234
  expires: Date.now() + (tok.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS,
3497
4235
  metadata: {
3498
4236
  region: detectedRegion,
3499
- authMethod: "idc"
4237
+ authMethod: "idc",
4238
+ profileArn: arn
3500
4239
  }
3501
4240
  };
3502
4241
  }
@@ -3520,7 +4259,9 @@ Make sure Kiro CLI or IDE is installed and you're logged in, then try again.`);
3520
4259
  imported.refreshToken,
3521
4260
  imported.clientId || "",
3522
4261
  imported.clientSecret || "",
3523
- authMethod
4262
+ authMethod,
4263
+ imported.source || "",
4264
+ imported.tokenKey || ""
3524
4265
  ];
3525
4266
  return {
3526
4267
  url: "",
@@ -3576,7 +4317,7 @@ Make sure Kiro CLI or IDE is installed and you're logged in, then try again.`);
3576
4317
  throw new Error("No refresh token provided.");
3577
4318
  }
3578
4319
  const region = inputs.region?.trim() || "us-east-1";
3579
- const packed = `${refreshToken}|||desktop`;
4320
+ const packed = `${refreshToken}|||desktop||`;
3580
4321
  return {
3581
4322
  url: "",
3582
4323
  method: "auto",
@@ -3604,7 +4345,7 @@ Make sure Kiro CLI or IDE is installed and you're logged in, then try again.`);
3604
4345
  cfg.provider = cfg.provider ?? {};
3605
4346
  cfg.provider.kiro = cfg.provider.kiro ?? {};
3606
4347
  const kiro = cfg.provider.kiro;
3607
- kiro.name = kiro.name ?? "Kiro";
4348
+ kiro.name = kiro.name ?? "Kiro AWS";
3608
4349
  kiro.npm = kiro.npm ?? "@ai-sdk/anthropic";
3609
4350
  kiro.api = kiro.api ?? `http://127.0.0.1:${localPort}/v1`;
3610
4351
  kiro.models = kiro.models ?? {};