@javargasm/pi-kiro 0.4.2 → 0.4.4

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
@@ -551,2521 +551,2380 @@ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync
551
551
  import { homedir as homedir2 } from "node:os";
552
552
  import { join as join2 } from "node:path";
553
553
 
554
- // src/stream.ts
555
- init_debug();
556
- import { calculateCost, createAssistantMessageEventStream } from "@earendil-works/pi-ai";
557
-
558
- // src/event-parser.ts
559
- init_debug();
560
- function findJsonEnd(text, start) {
561
- let braceCount = 0;
562
- let inString = false;
563
- let escapeNext = false;
564
- for (let i = start;i < text.length; i++) {
565
- const char = text[i];
566
- if (escapeNext) {
567
- escapeNext = false;
568
- continue;
569
- }
570
- if (char === "\\") {
571
- escapeNext = true;
572
- continue;
573
- }
574
- if (char === '"') {
575
- inString = !inString;
576
- continue;
577
- }
578
- if (!inString) {
579
- if (char === "{")
580
- braceCount++;
581
- else if (char === "}") {
582
- braceCount--;
583
- if (braceCount === 0)
584
- return i;
585
- }
586
- }
587
- }
588
- return -1;
554
+ // src/models.ts
555
+ var KIRO_MODEL_IDS = new Set([
556
+ "claude-fable-5",
557
+ "claude-opus-4.8",
558
+ "claude-opus-4.7",
559
+ "claude-opus-4.6",
560
+ "claude-opus-4.6-1m",
561
+ "claude-sonnet-4.6",
562
+ "claude-sonnet-4.6-1m",
563
+ "claude-opus-4.5",
564
+ "claude-sonnet-4.5",
565
+ "claude-sonnet-4.5-1m",
566
+ "claude-sonnet-4",
567
+ "claude-haiku-4.5",
568
+ "deepseek-3.2",
569
+ "kimi-k2.5",
570
+ "minimax-m2.1",
571
+ "minimax-m2.5",
572
+ "glm-4.7",
573
+ "glm-4.7-flash",
574
+ "qwen3-coder-next",
575
+ "agi-nova-beta-1m",
576
+ "qwen3-coder-480b",
577
+ "auto"
578
+ ]);
579
+ function dashToDot(modelId) {
580
+ return modelId.replace(/(\d)-(\d)/g, "$1.$2");
589
581
  }
590
- function parseKiroEvent(parsed) {
591
- if (parsed.content !== undefined) {
592
- return { type: "content", data: parsed.content };
593
- }
594
- if (parsed.reasoningText !== undefined || parsed.signature !== undefined || parsed.text !== undefined && !parsed.content && !parsed.name && !parsed.message) {
595
- let text = "";
596
- let signature;
597
- if (parsed.reasoningText) {
598
- const rt = parsed.reasoningText;
599
- text = (rt.text ?? rt.Text) || "";
600
- signature = rt.signature ?? rt.Signature;
601
- } else {
602
- text = parsed.text || "";
603
- signature = parsed.signature;
604
- }
605
- return {
606
- type: "reasoning",
607
- data: { text, signature }
608
- };
609
- }
610
- if (parsed.name && parsed.toolUseId) {
611
- const rawInput = parsed.input;
612
- const input = typeof rawInput === "string" ? rawInput : rawInput && typeof rawInput === "object" && Object.keys(rawInput).length > 0 ? JSON.stringify(rawInput) : "";
613
- return {
614
- type: "toolUse",
615
- data: {
616
- name: parsed.name,
617
- toolUseId: parsed.toolUseId,
618
- input,
619
- stop: parsed.stop
620
- }
621
- };
622
- }
623
- if (parsed.input !== undefined && !parsed.name) {
624
- return {
625
- type: "toolUseInput",
626
- data: {
627
- input: typeof parsed.input === "string" ? parsed.input : JSON.stringify(parsed.input)
628
- }
629
- };
630
- }
631
- if (parsed.stop !== undefined && parsed.contextUsagePercentage === undefined) {
632
- return { type: "toolUseStop", data: { stop: parsed.stop } };
633
- }
634
- if (parsed.contextUsagePercentage !== undefined) {
635
- return {
636
- type: "contextUsage",
637
- data: { contextUsagePercentage: parsed.contextUsagePercentage }
638
- };
639
- }
640
- if (parsed.followupPrompt !== undefined) {
641
- return { type: "followupPrompt", data: parsed.followupPrompt };
642
- }
643
- if (parsed.error !== undefined || parsed.Error !== undefined) {
644
- const err = parsed.error || parsed.Error || "unknown";
645
- const message = parsed.message || parsed.Message || parsed.reason;
646
- return {
647
- type: "error",
648
- data: {
649
- error: typeof err === "string" ? err : JSON.stringify(err),
650
- message
651
- }
652
- };
653
- }
654
- if (parsed.usage !== undefined) {
655
- const u = parsed.usage;
656
- return {
657
- type: "usage",
658
- data: {
659
- inputTokens: u.inputTokens,
660
- outputTokens: u.outputTokens
661
- }
662
- };
663
- }
664
- return null;
582
+ function dotToDash(modelId) {
583
+ return modelId.replace(/(\d)\.(\d)/g, "$1-$2");
665
584
  }
666
- var EVENT_PATTERNS = [
667
- '{"content":',
668
- '{"reasoningText":',
669
- '{"signature":',
670
- '{"text":',
671
- '{"name":',
672
- '{"input":',
673
- '{"stop":',
674
- '{"contextUsagePercentage":',
675
- '{"followupPrompt":',
676
- '{"usage":',
677
- '{"toolUseId":',
678
- '{"unit":',
679
- '{"error":',
680
- '{"Error":',
681
- '{"message":'
682
- ];
683
- function findNextEventStart(buffer, from) {
684
- let earliest = -1;
685
- for (const pattern of EVENT_PATTERNS) {
686
- const idx = buffer.indexOf(pattern, from);
687
- if (idx >= 0 && (earliest < 0 || idx < earliest))
688
- earliest = idx;
585
+ function resolveKiroModel(modelId) {
586
+ const kiroId = dashToDot(modelId);
587
+ if (!KIRO_MODEL_IDS.has(kiroId)) {
588
+ throw new Error(`Unknown Kiro model ID: ${modelId}`);
689
589
  }
690
- return earliest;
590
+ return kiroId;
691
591
  }
692
- function parseKiroEvents(buffer) {
693
- const events = [];
694
- let pos = 0;
695
- while (pos < buffer.length) {
696
- const jsonStart = findNextEventStart(buffer, pos);
697
- if (jsonStart < 0) {
698
- if (log.isDebug()) {
699
- const gap = buffer.substring(pos);
700
- const braceIdx = gap.indexOf('{"');
701
- if (braceIdx >= 0) {
702
- log.debug("event.unmatchedBrace", {
703
- from: pos + braceIdx,
704
- preview: gap.substring(braceIdx, Math.min(braceIdx + 200, gap.length))
705
- });
706
- }
707
- }
708
- break;
709
- }
710
- if (log.isDebug() && jsonStart > pos) {
711
- const skipped = buffer.substring(pos, jsonStart);
712
- const braceIdx = skipped.indexOf('{"');
713
- if (braceIdx >= 0) {
714
- log.debug("event.skippedBrace", {
715
- from: pos + braceIdx,
716
- preview: skipped.substring(braceIdx, Math.min(braceIdx + 200, skipped.length))
717
- });
718
- }
719
- }
720
- const jsonEnd = findJsonEnd(buffer, jsonStart);
721
- if (jsonEnd < 0) {
722
- return { events, remaining: buffer.substring(jsonStart) };
723
- }
724
- try {
725
- const parsed = JSON.parse(buffer.substring(jsonStart, jsonEnd + 1));
726
- const event = parseKiroEvent(parsed);
727
- if (event) {
728
- events.push(event);
729
- } else if (log.isDebug()) {
730
- log.debug("event.unknown", { keys: Object.keys(parsed), raw: parsed });
731
- }
732
- } catch (err) {
733
- if (log.isDebug()) {
734
- log.debug("event.parseFail", {
735
- err: err instanceof Error ? err.message : String(err),
736
- snippet: buffer.substring(jsonStart, Math.min(jsonEnd + 1, jsonStart + 200))
737
- });
738
- }
739
- }
740
- pos = jsonEnd + 1;
741
- }
742
- return { events, remaining: "" };
592
+ var API_REGION_MAP = {
593
+ "us-west-1": "us-east-1",
594
+ "us-west-2": "us-east-1",
595
+ "us-east-2": "us-east-1",
596
+ "eu-west-1": "eu-central-1",
597
+ "eu-west-2": "eu-central-1",
598
+ "eu-west-3": "eu-central-1",
599
+ "eu-north-1": "eu-central-1",
600
+ "eu-south-1": "eu-central-1",
601
+ "eu-south-2": "eu-central-1",
602
+ "eu-central-2": "eu-central-1",
603
+ "ap-northeast-1": "us-east-1",
604
+ "ap-northeast-2": "us-east-1",
605
+ "ap-northeast-3": "us-east-1",
606
+ "ap-southeast-1": "us-east-1",
607
+ "ap-southeast-2": "us-east-1",
608
+ "ap-south-1": "us-east-1",
609
+ "ap-east-1": "us-east-1",
610
+ "ap-south-2": "us-east-1",
611
+ "ap-southeast-3": "us-east-1",
612
+ "ap-southeast-4": "us-east-1"
613
+ };
614
+ function resolveApiRegion(ssoRegion) {
615
+ if (!ssoRegion)
616
+ return "us-east-1";
617
+ return API_REGION_MAP[ssoRegion] ?? ssoRegion;
743
618
  }
744
-
745
- // src/health.ts
746
- var PERMANENT_PATTERNS = [
747
- "Invalid refresh token",
748
- "Invalid grant provided",
749
- "InvalidGrantException",
750
- "UnauthorizedClientException",
751
- "AuthorizationPendingException",
752
- "ExpiredTokenException",
753
- "client is not registered",
754
- "The security token is expired",
755
- "Access denied"
756
- ];
757
- function isPermanentError(reason) {
758
- if (!reason)
759
- return false;
760
- return PERMANENT_PATTERNS.some((p) => reason.includes(p));
761
- }
762
-
763
- // src/thinking-parser.ts
764
- init_debug();
765
- var THINKING_END_TAG = "</thinking>";
766
- var THINKING_TAG_VARIANTS = [
767
- { open: "<thinking>", close: "</thinking>" },
768
- { open: "<think>", close: "</think>" },
769
- { open: "<reasoning>", close: "</reasoning>" },
770
- { open: "<thought>", close: "</thought>" }
771
- ];
772
- function trailingPrefixLength(text, tag) {
773
- const max = Math.min(text.length, tag.length - 1);
774
- for (let len = max;len > 0; len--) {
775
- if (text.endsWith(tag.slice(0, len)))
776
- return len;
619
+ var MODELS_BY_REGION = {
620
+ "us-east-1": new Set([
621
+ "claude-fable-5",
622
+ "claude-opus-4-8",
623
+ "claude-opus-4-7",
624
+ "claude-opus-4-6",
625
+ "claude-opus-4-6-1m",
626
+ "claude-sonnet-4-6",
627
+ "claude-sonnet-4-6-1m",
628
+ "claude-opus-4-5",
629
+ "claude-sonnet-4-5",
630
+ "claude-sonnet-4-5-1m",
631
+ "claude-sonnet-4",
632
+ "claude-haiku-4-5",
633
+ "deepseek-3-2",
634
+ "kimi-k2-5",
635
+ "minimax-m2-1",
636
+ "minimax-m2-5",
637
+ "glm-4-7",
638
+ "glm-4-7-flash",
639
+ "qwen3-coder-next",
640
+ "qwen3-coder-480b",
641
+ "agi-nova-beta-1m",
642
+ "auto"
643
+ ]),
644
+ "eu-central-1": new Set([
645
+ "claude-fable-5",
646
+ "claude-opus-4-8",
647
+ "claude-opus-4-7",
648
+ "claude-opus-4-6",
649
+ "claude-sonnet-4-6",
650
+ "claude-opus-4-5",
651
+ "claude-sonnet-4-5",
652
+ "claude-sonnet-4",
653
+ "claude-haiku-4-5",
654
+ "minimax-m2-1",
655
+ "minimax-m2-5",
656
+ "qwen3-coder-next",
657
+ "auto"
658
+ ])
659
+ };
660
+ function filterModelsByRegion(models, apiRegion) {
661
+ const allowed = MODELS_BY_REGION[apiRegion];
662
+ if (!allowed) {
663
+ console.warn(`[pi-kiro] Unknown API region "${apiRegion}" — no models available. Update MODELS_BY_REGION in models.ts.`);
664
+ return [];
777
665
  }
778
- return 0;
666
+ return models.filter((m) => allowed.has(m.id));
779
667
  }
780
- function maxTrailingPrefixLength(text, tags) {
781
- let max = 0;
782
- for (const tag of tags) {
783
- max = Math.max(max, trailingPrefixLength(text, tag));
784
- }
785
- return max;
668
+ var RUNTIME_ENDPOINTS = {
669
+ "us-east-1": "https://runtime.us-east-1.kiro.dev",
670
+ "eu-central-1": "https://runtime.eu-central-1.kiro.dev"
671
+ };
672
+ function resolveRuntimeUrl(apiRegion) {
673
+ return RUNTIME_ENDPOINTS[apiRegion] ?? `https://runtime.${apiRegion}.kiro.dev`;
786
674
  }
787
-
788
- class ThinkingTagParser {
789
- output;
790
- stream;
791
- textBuffer = "";
792
- inThinking = false;
793
- thinkingExtracted = false;
794
- thinkingBlockIndex = null;
795
- textBlockIndex = null;
796
- lastTextBlockIndex = null;
797
- activeEndTag = THINKING_END_TAG;
798
- constructor(output, stream) {
799
- this.output = output;
800
- this.stream = stream;
801
- }
802
- processChunk(chunk) {
803
- this.textBuffer += chunk;
804
- if (log.isDebug()) {
805
- log.debug("thinking.chunk", {
806
- chunkLen: chunk.length,
807
- bufferLen: this.textBuffer.length,
808
- inThinking: this.inThinking,
809
- thinkingExtracted: this.thinkingExtracted
810
- });
811
- }
812
- while (this.textBuffer.length > 0) {
813
- const prev = this.textBuffer.length;
814
- if (!this.inThinking && !this.thinkingExtracted) {
815
- this.processBeforeThinking();
816
- if (this.textBuffer.length === 0)
817
- break;
818
- }
819
- if (this.inThinking) {
820
- this.processInsideThinking();
821
- if (this.textBuffer.length === 0)
822
- break;
823
- }
824
- if (this.thinkingExtracted) {
825
- this.processAfterThinking();
826
- break;
827
- }
828
- if (this.textBuffer.length >= prev)
829
- break;
830
- }
831
- }
832
- finalize() {
833
- if (log.isDebug()) {
834
- log.debug("thinking.finalize", {
835
- bufferLen: this.textBuffer.length,
836
- inThinking: this.inThinking,
837
- thinkingExtracted: this.thinkingExtracted,
838
- textBlockIndex: this.textBlockIndex,
839
- thinkingBlockIndex: this.thinkingBlockIndex
840
- });
841
- }
842
- if (this.textBuffer.length === 0)
843
- return;
844
- if (this.inThinking && this.thinkingBlockIndex !== null) {
845
- const block = this.output.content[this.thinkingBlockIndex];
846
- if (block) {
847
- block.thinking += this.textBuffer;
848
- this.stream.push({
849
- type: "thinking_delta",
850
- contentIndex: this.thinkingBlockIndex,
851
- delta: this.textBuffer,
852
- partial: this.output
853
- });
854
- this.stream.push({
855
- type: "thinking_end",
856
- contentIndex: this.thinkingBlockIndex,
857
- content: block.thinking,
858
- partial: this.output
859
- });
860
- }
861
- } else {
862
- this.emitText(this.textBuffer);
863
- }
864
- this.textBuffer = "";
865
- }
866
- getTextBlockIndex() {
867
- return this.textBlockIndex ?? this.lastTextBlockIndex;
868
- }
869
- processBeforeThinking() {
870
- let bestPos = -1;
871
- let bestVariant = null;
872
- for (const variant of THINKING_TAG_VARIANTS) {
873
- const pos = this.textBuffer.indexOf(variant.open);
874
- if (pos !== -1 && (bestPos === -1 || pos < bestPos)) {
875
- bestPos = pos;
876
- bestVariant = variant;
877
- }
878
- }
879
- if (bestPos !== -1 && bestVariant) {
880
- if (log.isDebug()) {
881
- log.debug("thinking.open", { tag: bestVariant.open, at: bestPos });
882
- }
883
- if (bestPos > 0)
884
- this.emitText(this.textBuffer.slice(0, bestPos));
885
- this.textBuffer = this.textBuffer.slice(bestPos + bestVariant.open.length);
886
- this.activeEndTag = bestVariant.close;
887
- this.inThinking = true;
888
- return;
889
- }
890
- const trailing = maxTrailingPrefixLength(this.textBuffer, THINKING_TAG_VARIANTS.map((v) => v.open));
891
- const safeLen = this.textBuffer.length - trailing;
892
- if (safeLen > 0) {
893
- this.emitText(this.textBuffer.slice(0, safeLen));
894
- this.textBuffer = this.textBuffer.slice(safeLen);
895
- }
896
- }
897
- processInsideThinking() {
898
- const endPos = this.textBuffer.indexOf(this.activeEndTag);
899
- if (endPos !== -1) {
900
- if (log.isDebug()) {
901
- log.debug("thinking.close", { tag: this.activeEndTag, at: endPos });
902
- }
903
- if (endPos > 0)
904
- this.emitThinking(this.textBuffer.slice(0, endPos));
905
- if (this.thinkingBlockIndex !== null) {
906
- const block = this.output.content[this.thinkingBlockIndex];
907
- if (block) {
908
- this.stream.push({
909
- type: "thinking_end",
910
- contentIndex: this.thinkingBlockIndex,
911
- content: block.thinking,
912
- partial: this.output
913
- });
914
- }
915
- }
916
- this.textBuffer = this.textBuffer.slice(endPos + this.activeEndTag.length);
917
- this.inThinking = false;
918
- this.thinkingExtracted = true;
919
- this.lastTextBlockIndex = this.textBlockIndex;
920
- this.textBlockIndex = null;
921
- if (this.textBuffer.startsWith(`
922
-
923
- `))
924
- this.textBuffer = this.textBuffer.slice(2);
925
- return;
926
- }
927
- const trailing = trailingPrefixLength(this.textBuffer, this.activeEndTag);
928
- const safeLen = this.textBuffer.length - trailing;
929
- if (safeLen > 0) {
930
- this.emitThinking(this.textBuffer.slice(0, safeLen));
931
- this.textBuffer = this.textBuffer.slice(safeLen);
932
- }
933
- }
934
- processAfterThinking() {
935
- this.emitText(this.textBuffer);
936
- this.textBuffer = "";
937
- }
938
- emitText(text) {
939
- if (!text)
940
- return;
941
- if (this.textBlockIndex === null) {
942
- this.textBlockIndex = this.output.content.length;
943
- this.output.content.push({ type: "text", text: "" });
944
- this.stream.push({ type: "text_start", contentIndex: this.textBlockIndex, partial: this.output });
945
- }
946
- const block = this.output.content[this.textBlockIndex];
947
- if (!block)
948
- return;
949
- block.text += text;
950
- this.stream.push({
951
- type: "text_delta",
952
- contentIndex: this.textBlockIndex,
953
- delta: text,
954
- partial: this.output
955
- });
956
- }
957
- emitThinking(thinking) {
958
- if (!thinking)
959
- return;
960
- if (this.thinkingBlockIndex === null) {
961
- if (this.textBlockIndex !== null) {
962
- this.thinkingBlockIndex = this.textBlockIndex;
963
- this.output.content.splice(this.thinkingBlockIndex, 0, { type: "thinking", thinking: "" });
964
- this.textBlockIndex = this.textBlockIndex + 1;
965
- } else {
966
- this.thinkingBlockIndex = this.output.content.length;
967
- this.output.content.push({ type: "thinking", thinking: "" });
968
- }
969
- this.stream.push({
970
- type: "thinking_start",
971
- contentIndex: this.thinkingBlockIndex,
972
- partial: this.output
973
- });
675
+ var BASE_URL = resolveRuntimeUrl("us-east-1");
676
+ var ZERO_COST = Object.freeze({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 });
677
+ var KIRO_DEFAULTS = {
678
+ api: "kiro-api",
679
+ provider: "kiro",
680
+ baseUrl: BASE_URL,
681
+ cost: ZERO_COST
682
+ };
683
+ var MULTIMODAL = ["text", "image"];
684
+ var TEXT_ONLY = ["text"];
685
+ var kiroModels = [
686
+ {
687
+ ...KIRO_DEFAULTS,
688
+ id: "claude-fable-5",
689
+ name: "Claude Fable 5",
690
+ reasoning: true,
691
+ input: MULTIMODAL,
692
+ contextWindow: 1e6,
693
+ maxTokens: 128000,
694
+ firstTokenTimeout: 180000,
695
+ supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
696
+ supportsThinkingConfig: true
697
+ },
698
+ {
699
+ ...KIRO_DEFAULTS,
700
+ id: "claude-opus-4-8",
701
+ name: "Claude Opus 4.8",
702
+ reasoning: true,
703
+ input: MULTIMODAL,
704
+ contextWindow: 1e6,
705
+ maxTokens: 128000,
706
+ firstTokenTimeout: 180000,
707
+ supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
708
+ supportsThinkingConfig: true
709
+ },
710
+ {
711
+ ...KIRO_DEFAULTS,
712
+ id: "claude-opus-4-7",
713
+ name: "Claude Opus 4.7",
714
+ reasoning: true,
715
+ input: MULTIMODAL,
716
+ contextWindow: 1e6,
717
+ maxTokens: 128000,
718
+ firstTokenTimeout: 180000,
719
+ supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
720
+ supportsThinkingConfig: true
721
+ },
722
+ {
723
+ ...KIRO_DEFAULTS,
724
+ id: "claude-opus-4-6",
725
+ name: "Claude Opus 4.6",
726
+ reasoning: true,
727
+ input: MULTIMODAL,
728
+ contextWindow: 1e6,
729
+ maxTokens: 64000,
730
+ supportedEfforts: ["low", "medium", "high", "max"],
731
+ supportsThinkingConfig: true
732
+ },
733
+ {
734
+ ...KIRO_DEFAULTS,
735
+ id: "claude-opus-4-6-1m",
736
+ name: "Claude Opus 4.6 (1M)",
737
+ reasoning: true,
738
+ input: MULTIMODAL,
739
+ contextWindow: 1e6,
740
+ maxTokens: 64000,
741
+ supportedEfforts: ["low", "medium", "high", "max"],
742
+ supportsThinkingConfig: true
743
+ },
744
+ {
745
+ ...KIRO_DEFAULTS,
746
+ id: "claude-sonnet-4-6",
747
+ name: "Claude Sonnet 4.6",
748
+ reasoning: true,
749
+ input: MULTIMODAL,
750
+ contextWindow: 1e6,
751
+ maxTokens: 64000,
752
+ supportedEfforts: ["low", "medium", "high", "max"],
753
+ supportsThinkingConfig: true
754
+ },
755
+ {
756
+ ...KIRO_DEFAULTS,
757
+ id: "claude-sonnet-4-6-1m",
758
+ name: "Claude Sonnet 4.6 (1M)",
759
+ reasoning: true,
760
+ input: MULTIMODAL,
761
+ contextWindow: 1e6,
762
+ maxTokens: 64000,
763
+ supportedEfforts: ["low", "medium", "high", "max"],
764
+ supportsThinkingConfig: true
765
+ },
766
+ {
767
+ ...KIRO_DEFAULTS,
768
+ id: "claude-opus-4-5",
769
+ name: "Claude Opus 4.5",
770
+ reasoning: true,
771
+ input: MULTIMODAL,
772
+ contextWindow: 200000,
773
+ maxTokens: 64000
774
+ },
775
+ {
776
+ ...KIRO_DEFAULTS,
777
+ id: "claude-sonnet-4-5",
778
+ name: "Claude Sonnet 4.5",
779
+ reasoning: true,
780
+ input: MULTIMODAL,
781
+ contextWindow: 200000,
782
+ maxTokens: 65536
783
+ },
784
+ {
785
+ ...KIRO_DEFAULTS,
786
+ id: "claude-sonnet-4-5-1m",
787
+ name: "Claude Sonnet 4.5 (1M)",
788
+ reasoning: true,
789
+ input: MULTIMODAL,
790
+ contextWindow: 1e6,
791
+ maxTokens: 65536
792
+ },
793
+ {
794
+ ...KIRO_DEFAULTS,
795
+ id: "claude-sonnet-4",
796
+ name: "Claude Sonnet 4",
797
+ reasoning: true,
798
+ input: MULTIMODAL,
799
+ contextWindow: 200000,
800
+ maxTokens: 65536
801
+ },
802
+ {
803
+ ...KIRO_DEFAULTS,
804
+ id: "claude-haiku-4-5",
805
+ name: "Claude Haiku 4.5",
806
+ reasoning: false,
807
+ input: MULTIMODAL,
808
+ contextWindow: 200000,
809
+ maxTokens: 65536
810
+ },
811
+ {
812
+ ...KIRO_DEFAULTS,
813
+ id: "deepseek-3-2",
814
+ name: "DeepSeek 3.2",
815
+ reasoning: true,
816
+ input: TEXT_ONLY,
817
+ contextWindow: 128000,
818
+ maxTokens: 8192
819
+ },
820
+ {
821
+ ...KIRO_DEFAULTS,
822
+ id: "kimi-k2-5",
823
+ name: "Kimi K2.5",
824
+ reasoning: true,
825
+ input: TEXT_ONLY,
826
+ contextWindow: 200000,
827
+ maxTokens: 8192
828
+ },
829
+ {
830
+ ...KIRO_DEFAULTS,
831
+ id: "minimax-m2-5",
832
+ name: "MiniMax M2.5",
833
+ reasoning: false,
834
+ input: TEXT_ONLY,
835
+ contextWindow: 196000,
836
+ maxTokens: 64000
837
+ },
838
+ {
839
+ ...KIRO_DEFAULTS,
840
+ id: "minimax-m2-1",
841
+ name: "MiniMax M2.1",
842
+ reasoning: false,
843
+ input: MULTIMODAL,
844
+ contextWindow: 196000,
845
+ maxTokens: 64000
846
+ },
847
+ {
848
+ ...KIRO_DEFAULTS,
849
+ id: "glm-4-7",
850
+ name: "GLM 4.7",
851
+ reasoning: true,
852
+ input: TEXT_ONLY,
853
+ contextWindow: 128000,
854
+ maxTokens: 8192
855
+ },
856
+ {
857
+ ...KIRO_DEFAULTS,
858
+ id: "glm-4-7-flash",
859
+ name: "GLM 4.7 Flash",
860
+ reasoning: false,
861
+ input: TEXT_ONLY,
862
+ contextWindow: 128000,
863
+ maxTokens: 8192
864
+ },
865
+ {
866
+ ...KIRO_DEFAULTS,
867
+ id: "qwen3-coder-next",
868
+ name: "Qwen3 Coder Next",
869
+ reasoning: true,
870
+ input: MULTIMODAL,
871
+ contextWindow: 256000,
872
+ maxTokens: 64000
873
+ },
874
+ {
875
+ ...KIRO_DEFAULTS,
876
+ id: "qwen3-coder-480b",
877
+ name: "Qwen3 Coder 480B",
878
+ reasoning: true,
879
+ input: TEXT_ONLY,
880
+ contextWindow: 128000,
881
+ maxTokens: 8192
882
+ },
883
+ {
884
+ ...KIRO_DEFAULTS,
885
+ id: "agi-nova-beta-1m",
886
+ name: "AGI Nova Beta (1M)",
887
+ reasoning: true,
888
+ input: MULTIMODAL,
889
+ contextWindow: 1e6,
890
+ maxTokens: 65536
891
+ },
892
+ {
893
+ ...KIRO_DEFAULTS,
894
+ id: "auto",
895
+ name: "Auto",
896
+ reasoning: true,
897
+ input: MULTIMODAL,
898
+ contextWindow: 200000,
899
+ maxTokens: 65536
900
+ }
901
+ ];
902
+ async function fetchAvailableModels(accessToken, apiRegion, profileArn) {
903
+ const url = `https://management.${apiRegion}.kiro.dev/?origin=KIRO_CLI&profileArn=${encodeURIComponent(profileArn)}`;
904
+ const resp = await fetch(url, {
905
+ method: "POST",
906
+ headers: {
907
+ Authorization: `Bearer ${accessToken}`,
908
+ "Content-Type": "application/x-amz-json-1.0",
909
+ "X-Amz-Target": "AmazonCodeWhispererService.ListAvailableModels",
910
+ "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",
911
+ "x-amz-user-agent": "aws-sdk-rust/1.3.15 ua/2.1 api/codewhispererruntime/0.1.16551 os/macos lang/rust/1.92.0 m/F app/AmazonQ-For-CLI",
912
+ "x-amzn-codewhisperer-optout": "true",
913
+ "amz-sdk-request": "attempt=1; max=3",
914
+ "amz-sdk-invocation-id": crypto.randomUUID(),
915
+ accept: "*/*",
916
+ Pragma: "no-cache",
917
+ "Cache-Control": "no-cache"
918
+ },
919
+ body: JSON.stringify({ origin: "KIRO_CLI", profileArn })
920
+ });
921
+ if (!resp.ok) {
922
+ const body = await resp.text().catch(() => "");
923
+ if (resp.status === 401 || resp.status === 400 && body.includes("Invalid token")) {
924
+ throw new Error(`Authentication failed: 401 ListAvailableModels failed - ${body}`);
974
925
  }
975
- const block = this.output.content[this.thinkingBlockIndex];
976
- if (!block)
977
- return;
978
- block.thinking += thinking;
979
- this.stream.push({
980
- type: "thinking_delta",
981
- contentIndex: this.thinkingBlockIndex,
982
- delta: thinking,
983
- partial: this.output
984
- });
926
+ throw new Error(`ListAvailableModels failed: HTTP ${resp.status} - ${body}`);
985
927
  }
928
+ const data = await resp.json();
929
+ return (data.models ?? []).filter((m) => m.modelId !== "auto");
986
930
  }
987
-
988
- // src/tokenizer.ts
989
- function countTokens(text) {
990
- if (!text)
991
- return 0;
992
- return Math.ceil(text.length / 4);
993
- }
994
-
995
- // src/transform.ts
996
- import { createHash } from "node:crypto";
997
-
998
- // src/kiro-defaults.ts
999
- var SYSTEM_SEED_INSTRUCTION = `Follow this instruction: # Kiro CLI Default Agent
1000
-
1001
- ` + "You are the default Kiro CLI agent, bringing the power of AI-assisted development " + "directly to the user's terminal. You help with coding tasks, system operations, " + `AWS management, and development workflows.
1002
-
1003
- ` + `The current model is {{modelId}}.
1004
- `;
1005
- var SYSTEM_SEED_ACK = "I will fully incorporate this information when generating my responses, " + "and explicitly acknowledge relevant parts of the summary when answering questions.";
1006
- function resolveOS() {
1007
- switch (process.platform) {
1008
- case "darwin":
1009
- return "macos";
1010
- case "win32":
1011
- return "windows";
1012
- default:
1013
- return process.platform;
931
+ var REASONING_FAMILIES = new Set([
932
+ "claude-fable",
933
+ "claude-sonnet",
934
+ "claude-opus",
935
+ "deepseek",
936
+ "kimi",
937
+ "glm",
938
+ "qwen",
939
+ "agi-nova",
940
+ "minimax"
941
+ ]);
942
+ function isReasoningModel(dotId) {
943
+ for (const family of REASONING_FAMILIES) {
944
+ if (dotId.startsWith(family))
945
+ return true;
1014
946
  }
947
+ return false;
1015
948
  }
1016
- var COMPACTION_THRESHOLD_PCT = 95;
1017
-
1018
- // src/transform.ts
1019
- function normalizeMessages(messages) {
1020
- return messages.filter((msg) => msg.role !== "assistant" || msg.stopReason !== "error" && msg.stopReason !== "aborted");
949
+ function firstTokenTimeout(dotId) {
950
+ if (dotId.startsWith("claude-fable") || dotId.startsWith("claude-opus"))
951
+ return 180000;
952
+ return 90000;
1021
953
  }
1022
- var TOOL_RESULT_LIMIT = 250000;
1023
- var MAX_KIRO_IMAGES = 4;
1024
- var MAX_KIRO_IMAGE_BYTES = 3750000;
1025
- function truncate(text, limit) {
1026
- if (text.length <= limit)
1027
- return text;
1028
- const half = Math.floor(limit / 2);
1029
- return `${text.substring(0, half)}
1030
- ... [TRUNCATED] ...
1031
- ${text.substring(text.length - half)}`;
954
+ function buildModelsFromApi(apiModels) {
955
+ return apiModels.map((m) => {
956
+ KIRO_MODEL_IDS.add(m.modelId);
957
+ const dashId = dotToDash(m.modelId);
958
+ const supportedTypes = m.supportedInputTypes ?? ["TEXT"];
959
+ const input = supportedTypes.includes("IMAGE") ? ["text", "image"] : ["text"];
960
+ const effortEnum = m.additionalModelRequestFieldsSchema?.properties?.output_config?.properties?.effort?.enum;
961
+ const supportedEfforts = Array.isArray(effortEnum) && effortEnum.length > 0 ? effortEnum : undefined;
962
+ const supportsThinkingConfig = !!m.additionalModelRequestFieldsSchema?.properties?.thinking;
963
+ return {
964
+ id: dashId,
965
+ name: m.modelName,
966
+ reasoning: isReasoningModel(m.modelId),
967
+ input,
968
+ contextWindow: m.tokenLimits?.maxInputTokens ?? 200000,
969
+ maxTokens: m.tokenLimits?.maxOutputTokens ?? 8192,
970
+ firstTokenTimeout: firstTokenTimeout(m.modelId),
971
+ ...supportedEfforts ? { supportedEfforts } : {},
972
+ ...supportsThinkingConfig ? { supportsThinkingConfig } : {}
973
+ };
974
+ });
1032
975
  }
1033
- function extractImages(msg) {
1034
- if (msg.role === "toolResult" || typeof msg.content === "string")
1035
- return [];
1036
- if (!Array.isArray(msg.content))
1037
- return [];
1038
- return msg.content.filter((c) => c.type === "image");
976
+ var cachedDynamicModels = null;
977
+ function getCachedDynamicModels() {
978
+ return cachedDynamicModels;
1039
979
  }
1040
- function getContentText(msg) {
1041
- if (msg.role === "toolResult") {
1042
- return msg.content.map((c) => c.type === "text" ? c.text : "").join("");
1043
- }
1044
- if (typeof msg.content === "string")
1045
- return msg.content;
1046
- if (!Array.isArray(msg.content))
1047
- return "";
1048
- return msg.content.map((c) => {
1049
- if (c.type === "text")
1050
- return c.text;
1051
- if (c.type === "thinking")
1052
- return c.thinking;
1053
- return "";
1054
- }).join("");
980
+ function setCachedDynamicModels(models) {
981
+ cachedDynamicModels = models;
1055
982
  }
1056
- function parseToolArgs(input) {
1057
- if (input && typeof input === "object")
1058
- return input;
1059
- if (typeof input !== "string")
1060
- return {};
1061
- try {
1062
- return JSON.parse(input);
1063
- } catch {
1064
- return {};
1065
- }
983
+
984
+ // src/oauth.ts
985
+ init_debug();
986
+ var BUILDER_ID_START_URL = "https://view.awsapps.com/start";
987
+ var BUILDER_ID_REGION = "us-east-1";
988
+ var SSO_SCOPES = [
989
+ "codewhisperer:completions",
990
+ "codewhisperer:analysis",
991
+ "codewhisperer:conversations",
992
+ "codewhisperer:transformations",
993
+ "codewhisperer:taskassist"
994
+ ];
995
+ var IDC_PROBE_REGIONS = [
996
+ "us-east-1",
997
+ "eu-west-1",
998
+ "eu-central-1",
999
+ "us-east-2",
1000
+ "eu-west-2",
1001
+ "eu-west-3",
1002
+ "eu-north-1",
1003
+ "ap-southeast-1",
1004
+ "ap-northeast-1",
1005
+ "us-west-2"
1006
+ ];
1007
+ var EXPIRES_BUFFER_MS = 5 * 60 * 1000;
1008
+ function abortableDelay(ms, signal) {
1009
+ if (signal?.aborted)
1010
+ return Promise.reject(signal.reason ?? new Error("Login cancelled"));
1011
+ return new Promise((resolve2, reject) => {
1012
+ const timer = setTimeout(resolve2, ms);
1013
+ signal?.addEventListener("abort", () => {
1014
+ clearTimeout(timer);
1015
+ reject(signal.reason ?? new Error("Login cancelled"));
1016
+ }, { once: true });
1017
+ });
1066
1018
  }
1067
- var KIRO_TOOL_USE_ID_RE = /^tooluse_[A-Za-z0-9]+$/;
1068
- function toKiroToolUseId(id) {
1069
- if (KIRO_TOOL_USE_ID_RE.test(id))
1070
- return id;
1071
- const digest = createHash("sha256").update(id).digest("hex").slice(0, 22);
1072
- return `tooluse_${digest}`;
1019
+ async function tryRegisterAndAuthorize(startUrl, region) {
1020
+ const oidcEndpoint = `https://oidc.${region}.amazonaws.com`;
1021
+ const regResp = await fetch(`${oidcEndpoint}/client/register`, {
1022
+ method: "POST",
1023
+ headers: { "Content-Type": "application/json", "User-Agent": "pi-kiro" },
1024
+ body: JSON.stringify({
1025
+ clientName: "pi-kiro",
1026
+ clientType: "public",
1027
+ scopes: SSO_SCOPES,
1028
+ grantTypes: ["urn:ietf:params:oauth:grant-type:device_code", "refresh_token"]
1029
+ })
1030
+ });
1031
+ if (!regResp.ok)
1032
+ return null;
1033
+ const { clientId, clientSecret } = await regResp.json();
1034
+ const devResp = await fetch(`${oidcEndpoint}/device_authorization`, {
1035
+ method: "POST",
1036
+ headers: { "Content-Type": "application/json", "User-Agent": "pi-kiro" },
1037
+ body: JSON.stringify({ clientId, clientSecret, startUrl })
1038
+ });
1039
+ if (!devResp.ok)
1040
+ return null;
1041
+ return {
1042
+ clientId,
1043
+ clientSecret,
1044
+ oidcEndpoint,
1045
+ devAuth: await devResp.json()
1046
+ };
1073
1047
  }
1074
- function convertImagesToKiro(images) {
1075
- let omitted = 0;
1076
- const valid = [];
1077
- for (const img of images) {
1078
- const estimatedBytes = Math.ceil(img.data.length * 3 / 4);
1079
- if (estimatedBytes > MAX_KIRO_IMAGE_BYTES) {
1080
- omitted++;
1048
+ async function pollForToken(oidcEndpoint, clientId, clientSecret, devAuth, signal) {
1049
+ const deadline = Date.now() + (devAuth.expiresIn || 600) * 1000;
1050
+ const baseInterval = (devAuth.interval || 5) * 1000;
1051
+ let interval = baseInterval;
1052
+ while (Date.now() < deadline) {
1053
+ if (signal?.aborted)
1054
+ throw new Error("Login cancelled");
1055
+ await abortableDelay(interval, signal);
1056
+ let resp;
1057
+ try {
1058
+ resp = await fetch(`${oidcEndpoint}/token`, {
1059
+ method: "POST",
1060
+ headers: { "Content-Type": "application/json", "User-Agent": "pi-kiro" },
1061
+ body: JSON.stringify({
1062
+ clientId,
1063
+ clientSecret,
1064
+ deviceCode: devAuth.deviceCode,
1065
+ grantType: "urn:ietf:params:oauth:grant-type:device_code"
1066
+ })
1067
+ });
1068
+ } catch {
1081
1069
  continue;
1082
1070
  }
1083
- if (valid.length >= MAX_KIRO_IMAGES) {
1084
- omitted++;
1071
+ if (resp.status >= 500)
1072
+ continue;
1073
+ let data;
1074
+ try {
1075
+ data = await resp.json();
1076
+ } catch {
1077
+ if (!resp.ok) {
1078
+ throw new Error(`Authorization failed: HTTP ${resp.status}`);
1079
+ }
1085
1080
  continue;
1086
1081
  }
1087
- valid.push({
1088
- format: img.mimeType.split("/")[1] || "png",
1089
- source: { bytes: img.data }
1090
- });
1082
+ if (!data.error && data.accessToken && data.refreshToken)
1083
+ return data;
1084
+ if (data.error === "authorization_pending")
1085
+ continue;
1086
+ if (data.error === "slow_down") {
1087
+ interval += baseInterval;
1088
+ continue;
1089
+ }
1090
+ if (data.error)
1091
+ throw new Error(`Authorization failed: ${data.error}`);
1091
1092
  }
1092
- return { images: valid, omitted };
1093
+ throw new Error("Authorization timed out");
1093
1094
  }
1094
- function buildHistory(messages, _modelId, systemPrompt) {
1095
- const history = [];
1096
- let systemPrepended = false;
1097
- let currentMsgStartIdx = messages.length - 1;
1098
- while (currentMsgStartIdx > 0 && messages[currentMsgStartIdx]?.role === "toolResult") {
1099
- currentMsgStartIdx--;
1095
+ async function loginKiro(callbacks) {
1096
+ const method = await callbacks.onSelect({
1097
+ message: "Select login method:",
1098
+ options: [
1099
+ { id: "builder-id", label: "AWS Builder ID (personal account)" },
1100
+ { id: "idc", label: "IAM Identity Center (enterprise SSO)" },
1101
+ { id: "sync", label: "Import from Kiro CLI/IDE (auto-sync local DB)" },
1102
+ { id: "desktop", label: "Desktop refresh token (manual)" }
1103
+ ]
1104
+ });
1105
+ if (!method)
1106
+ throw new Error("Login cancelled");
1107
+ if (method === "sync") {
1108
+ return loginCliSync(callbacks);
1100
1109
  }
1101
- const anchor = messages[currentMsgStartIdx];
1102
- if (anchor?.role === "assistant") {
1103
- const hasToolCall = Array.isArray(anchor.content) && anchor.content.some((b) => b.type === "toolCall");
1104
- if (!hasToolCall)
1105
- currentMsgStartIdx++;
1110
+ if (method === "desktop") {
1111
+ return loginDesktopManual(callbacks);
1106
1112
  }
1107
- const historyMessages = messages.slice(0, currentMsgStartIdx);
1108
- for (let i = 0;i < historyMessages.length; i++) {
1109
- const msg = historyMessages[i];
1110
- if (!msg)
1111
- continue;
1112
- if (msg.role === "user") {
1113
- let content = typeof msg.content === "string" ? msg.content : getContentText(msg);
1114
- if (systemPrompt && !systemPrepended) {
1115
- content = `${systemPrompt}
1116
-
1117
- ${content}`;
1118
- systemPrepended = true;
1119
- }
1120
- const images = extractImages(msg);
1121
- const uim = {
1122
- content,
1123
- origin: "KIRO_CLI",
1124
- ...images.length > 0 ? { images: convertImagesToKiro(images).images } : {}
1125
- };
1126
- const prev2 = history[history.length - 1];
1127
- if (prev2?.userInputMessage) {
1128
- prev2.userInputMessage.content += `
1129
-
1130
- ${uim.content}`;
1131
- if (uim.images) {
1132
- prev2.userInputMessage.images = [...prev2.userInputMessage.images ?? [], ...uim.images];
1133
- }
1134
- } else {
1135
- history.push({ userInputMessage: uim });
1136
- }
1137
- continue;
1138
- }
1139
- if (msg.role === "assistant") {
1140
- let armContent = "";
1141
- const armToolUses = [];
1142
- if (Array.isArray(msg.content)) {
1143
- for (const block of msg.content) {
1144
- if (block.type === "text") {
1145
- armContent += block.text;
1146
- } else if (block.type === "thinking") {
1147
- armContent = `<thinking>${block.thinking}</thinking>
1148
-
1149
- ${armContent}`;
1150
- } else if (block.type === "toolCall") {
1151
- const tc = block;
1152
- armToolUses.push({
1153
- name: tc.name,
1154
- toolUseId: toKiroToolUseId(tc.id),
1155
- input: parseToolArgs(tc.arguments)
1156
- });
1157
- }
1158
- }
1159
- }
1160
- if (!armContent && armToolUses.length === 0)
1161
- continue;
1162
- history.push({
1163
- assistantResponseMessage: {
1164
- content: armContent,
1165
- ...armToolUses.length > 0 ? { toolUses: armToolUses } : {}
1166
- }
1167
- });
1168
- continue;
1169
- }
1170
- const trMsg = msg;
1171
- const toolResults = [
1172
- {
1173
- content: [{ text: truncate(getContentText(msg), TOOL_RESULT_LIMIT) }],
1174
- status: trMsg.isError ? "error" : "success",
1175
- toolUseId: toKiroToolUseId(trMsg.toolCallId)
1176
- }
1177
- ];
1178
- const trImages = [];
1179
- if (Array.isArray(trMsg.content)) {
1180
- for (const c of trMsg.content)
1181
- if (c.type === "image")
1182
- trImages.push(c);
1183
- }
1184
- let j = i + 1;
1185
- while (j < historyMessages.length && historyMessages[j]?.role === "toolResult") {
1186
- const next = historyMessages[j];
1187
- toolResults.push({
1188
- content: [{ text: truncate(getContentText(next), TOOL_RESULT_LIMIT) }],
1189
- status: next.isError ? "error" : "success",
1190
- toolUseId: toKiroToolUseId(next.toolCallId)
1191
- });
1192
- if (Array.isArray(next.content)) {
1193
- for (const c of next.content)
1194
- if (c.type === "image")
1195
- trImages.push(c);
1196
- }
1197
- j++;
1198
- }
1199
- i = j - 1;
1200
- const prev = history[history.length - 1];
1201
- if (prev?.userInputMessage) {
1202
- prev.userInputMessage.content += `
1203
-
1204
- Tool results provided.`;
1205
- if (trImages.length > 0) {
1206
- prev.userInputMessage.images = [
1207
- ...prev.userInputMessage.images ?? [],
1208
- ...convertImagesToKiro(trImages).images
1209
- ];
1210
- }
1211
- if (!prev.userInputMessage.userInputMessageContext) {
1212
- prev.userInputMessage.userInputMessageContext = {};
1213
- }
1214
- prev.userInputMessage.userInputMessageContext.toolResults = [
1215
- ...prev.userInputMessage.userInputMessageContext.toolResults ?? [],
1216
- ...toolResults
1217
- ];
1218
- } else {
1219
- history.push({
1220
- userInputMessage: {
1221
- content: "Tool results provided.",
1222
- origin: "KIRO_CLI",
1223
- ...trImages.length > 0 ? { images: convertImagesToKiro(trImages).images } : {},
1224
- userInputMessageContext: { toolResults }
1225
- }
1226
- });
1227
- }
1113
+ if (method === "builder-id") {
1114
+ return runDeviceCodeFlow(callbacks, BUILDER_ID_START_URL, [BUILDER_ID_REGION], "builder-id");
1228
1115
  }
1229
- return { history: collapseAgenticLoops(history), systemPrepended, currentMsgStartIdx };
1116
+ const startUrl = (await callbacks.onPrompt({
1117
+ message: "Paste your IAM Identity Center start URL:",
1118
+ placeholder: "https://mycompany.awsapps.com/start",
1119
+ allowEmpty: false
1120
+ }))?.trim();
1121
+ if (!startUrl || !startUrl.startsWith("http")) {
1122
+ throw new Error(`Invalid start URL "${startUrl ?? ""}" — expected https://…`);
1123
+ }
1124
+ const regionRaw = await callbacks.onPrompt({
1125
+ message: `Identity Center region, or blank to auto-detect (${IDC_PROBE_REGIONS.join(", ")})`,
1126
+ placeholder: "us-east-1",
1127
+ allowEmpty: true
1128
+ });
1129
+ const region = (regionRaw ?? "").trim();
1130
+ const regions = region ? [region] : IDC_PROBE_REGIONS;
1131
+ callbacks.onProgress?.(region ? `Connecting to ${region}…` : "Detecting your Identity Center region…");
1132
+ return runDeviceCodeFlow(callbacks, startUrl, regions, "idc");
1230
1133
  }
1231
- function collapseAgenticLoops(history) {
1232
- if (history.length < 4)
1233
- return history;
1234
- const result = [];
1235
- let i = 0;
1236
- while (i < history.length) {
1237
- const entry = history[i];
1238
- if (entry?.assistantResponseMessage?.toolUses && i + 1 < history.length && history[i + 1]?.userInputMessage?.userInputMessageContext?.toolResults) {
1239
- let j = i;
1240
- while (j < history.length) {
1241
- const asst = history[j];
1242
- if (!asst?.assistantResponseMessage?.toolUses)
1243
- break;
1244
- const nextUser = j + 1 < history.length ? history[j + 1] : null;
1245
- if (!nextUser?.userInputMessage?.userInputMessageContext?.toolResults)
1246
- break;
1247
- j += 2;
1248
- }
1249
- const pairCount = (j - i) / 2;
1250
- if (pairCount > 1) {
1251
- for (let k = i;k < j; k += 2) {
1252
- const asst = history[k];
1253
- const user = history[k + 1];
1254
- if (k === i) {
1255
- result.push(asst);
1256
- } else {
1257
- result.push({
1258
- assistantResponseMessage: {
1259
- content: "[tool calling continues]",
1260
- toolUses: asst.assistantResponseMessage.toolUses
1261
- }
1262
- });
1263
- }
1264
- result.push(user);
1265
- }
1266
- } else {
1267
- result.push(history[i], history[i + 1]);
1268
- }
1269
- i = j;
1270
- } else {
1271
- result.push(entry);
1272
- i++;
1134
+ async function loginCliSync(callbacks) {
1135
+ callbacks.onProgress?.("Scanning for Kiro IDE credentials (~/.kiro/db)…");
1136
+ const { importFromKiroCli: importFromKiroCli2 } = await Promise.resolve().then(() => (init_kiro_cli_sync(), exports_kiro_cli_sync));
1137
+ const imported = await importFromKiroCli2();
1138
+ if (!imported || !imported.accessToken && !imported.refreshToken) {
1139
+ throw new Error(`No Kiro IDE credentials found.
1140
+ Make sure Kiro IDE is installed and you're logged in, then try again.
1141
+ Alternatively, use 'desktop' to paste a refresh token manually.`);
1142
+ }
1143
+ log.info("Successfully imported credentials from Kiro IDE");
1144
+ callbacks.onProgress?.(`Imported from Kiro IDE (${imported.authMethod}, ${imported.region}${imported.email ? `, ${imported.email}` : ""})`);
1145
+ if (imported.profileArn) {
1146
+ try {
1147
+ const apiRegion = resolveApiRegion(imported.region);
1148
+ const apiModels = await fetchAvailableModels(imported.accessToken, apiRegion, imported.profileArn);
1149
+ setCachedDynamicModels(buildModelsFromApi(apiModels));
1150
+ log.info(`Fetched and cached ${apiModels.length} models after CLI sync`);
1151
+ } catch (err) {
1152
+ log.warn(`Failed to fetch models after CLI sync: ${err}`);
1273
1153
  }
1274
1154
  }
1275
- return result;
1276
- }
1277
-
1278
- // src/stream.ts
1279
- var FIRST_TOKEN_TIMEOUT_DEFAULT_MS = 90000;
1280
- var IDLE_TIMEOUT_MS = 60000;
1281
- var MAX_RETRIES = 3;
1282
- var MAX_RETRY_DELAY_MS = 1e4;
1283
- var CAPACITY_MAX_RETRIES = 3;
1284
- var CAPACITY_BASE_DELAY_MS = 5000;
1285
- var CAPACITY_MAX_DELAY_MS = 30000;
1286
- var TRANSIENT_MAX_RETRIES = 3;
1287
- var TRANSIENT_BASE_DELAY_MS = 2000;
1288
- var TRANSIENT_MAX_DELAY_MS = 15000;
1289
- var CONTEXT_TRUNCATION_MAX_RETRIES = 3;
1290
- var CONTEXT_TRUNCATION_DROP_RATIO = 0.3;
1291
- var TOO_BIG_PATTERNS = ["CONTENT_LENGTH_EXCEEDS_THRESHOLD", "Input is too long", "Improperly formed"];
1292
- var NON_RETRYABLE_BODY_PATTERNS = ["MONTHLY_REQUEST_COUNT"];
1293
- var CAPACITY_PATTERN = "INSUFFICIENT_MODEL_CAPACITY";
1294
- function exponentialBackoff(attempt, baseMs, maxMs) {
1295
- return Math.min(baseMs * 2 ** attempt, maxMs);
1296
- }
1297
- function isTooBigError(status, body) {
1298
- return status === 413 || status === 400 && TOO_BIG_PATTERNS.some((p) => body.includes(p));
1299
- }
1300
- function isNonRetryableBodyError(body) {
1301
- return NON_RETRYABLE_BODY_PATTERNS.some((p) => body.includes(p));
1155
+ return kiroCredsFromCliImport(imported);
1302
1156
  }
1303
- function isCapacityError(body) {
1304
- return body.includes(CAPACITY_PATTERN);
1157
+ async function loginDesktopManual(callbacks) {
1158
+ const refreshRaw = await callbacks.onPrompt({
1159
+ message: `Paste your Kiro desktop refresh token
1160
+ ` + "(find it in ~/.kiro/db/kiro.db → auth_kv table):",
1161
+ placeholder: "refresh-token",
1162
+ allowEmpty: true
1163
+ });
1164
+ const refreshToken = (refreshRaw ?? "").trim();
1165
+ if (!refreshToken) {
1166
+ throw new Error("Login cancelled — no refresh token provided");
1167
+ }
1168
+ const regionRaw = await callbacks.onPrompt({
1169
+ message: "Kiro region:",
1170
+ placeholder: "us-east-1",
1171
+ allowEmpty: true
1172
+ });
1173
+ const region = (regionRaw ?? "").trim() || "us-east-1";
1174
+ const refreshCreds = {
1175
+ refresh: `${refreshToken}|||desktop`,
1176
+ access: "",
1177
+ expires: 0,
1178
+ clientId: "",
1179
+ clientSecret: "",
1180
+ region,
1181
+ authMethod: "desktop"
1182
+ };
1183
+ callbacks.onProgress?.("Exchanging refresh token…");
1184
+ return refreshKiroToken(refreshCreds);
1305
1185
  }
1306
- function isTransientError(status) {
1307
- return status === 429 || status >= 500;
1308
- }
1309
- function firstTokenTimeoutForModel(modelId) {
1310
- const m = kiroModels.find((x) => x.id === modelId);
1311
- return m?.firstTokenTimeout ?? FIRST_TOKEN_TIMEOUT_DEFAULT_MS;
1312
- }
1313
- var HIDDEN_REASONING_PLACEHOLDER = "Reasoning hidden by provider";
1314
- var HIDDEN_REASONING_COUNTDOWN_MS = 2000;
1315
- function emitHiddenReasoningLate(output, stream) {
1316
- const contentIndex = output.content.length;
1317
- const block = {
1318
- type: "thinking",
1319
- thinking: HIDDEN_REASONING_PLACEHOLDER,
1320
- redacted: true
1321
- };
1322
- output.content.push(block);
1323
- stream.push({ type: "thinking_start", contentIndex, partial: output });
1324
- stream.push({
1325
- type: "thinking_delta",
1326
- contentIndex,
1327
- delta: HIDDEN_REASONING_PLACEHOLDER,
1328
- partial: output
1329
- });
1330
- stream.push({
1331
- type: "thinking_end",
1332
- contentIndex,
1333
- content: "",
1334
- partial: output
1335
- });
1336
- }
1337
- function abortableDelay(ms, signal) {
1338
- if (signal?.aborted)
1339
- return Promise.reject(signal.reason);
1340
- return new Promise((resolve2, reject) => {
1341
- const timer = setTimeout(resolve2, ms);
1342
- signal?.addEventListener("abort", () => {
1343
- clearTimeout(timer);
1344
- reject(signal.reason);
1345
- }, { once: true });
1346
- });
1347
- }
1348
- var profileArnCache = new Map;
1349
- var profileArnSkipResolution = false;
1350
- async function resolveProfileArn(accessToken, endpoint) {
1351
- if (profileArnSkipResolution)
1352
- return;
1353
- const cached = profileArnCache.get(endpoint);
1354
- if (cached !== undefined)
1355
- return cached;
1356
- try {
1357
- const ep = new URL(endpoint);
1358
- ep.hostname = ep.hostname.replace("runtime.", "management.");
1359
- ep.pathname = "/";
1360
- ep.search = "";
1361
- ep.hash = "";
1362
- const resp = await fetch(ep.toString(), {
1363
- method: "POST",
1364
- headers: {
1365
- "Content-Type": "application/x-amz-json-1.0",
1366
- Authorization: `Bearer ${accessToken}`,
1367
- "X-Amz-Target": "AmazonCodeWhispererService.ListAvailableProfiles"
1368
- },
1369
- body: "{}"
1370
- });
1371
- if (!resp.ok) {
1372
- log.warn(`profileArn resolution failed: ${resp.status} ${resp.statusText}`);
1373
- return;
1374
- }
1375
- const j = await resp.json();
1376
- const arn = j.profiles?.find((p) => p.arn)?.arn;
1377
- if (!arn) {
1378
- log.warn("profileArn resolution returned no profile ARN");
1379
- return;
1186
+ async function runDeviceCodeFlow(callbacks, startUrl, regions, authMethod) {
1187
+ let result = null;
1188
+ let detectedRegion = "";
1189
+ for (const region of regions) {
1190
+ result = await tryRegisterAndAuthorize(startUrl, region);
1191
+ if (result) {
1192
+ detectedRegion = region;
1193
+ if (regions.length > 1)
1194
+ callbacks.onProgress?.(`Region: ${region}`);
1195
+ break;
1380
1196
  }
1381
- profileArnCache.set(endpoint, arn);
1382
- return arn;
1383
- } catch (error) {
1384
- log.warn(`profileArn resolution threw: ${error instanceof Error ? error.message : String(error)}`);
1385
- return;
1386
1197
  }
1387
- }
1388
- function uniqueSorted(values) {
1389
- return [...new Set(values)].sort();
1390
- }
1391
- function duplicateValues(values) {
1392
- const seen = new Set;
1393
- const dupes = new Set;
1394
- for (const value of values) {
1395
- if (seen.has(value))
1396
- dupes.add(value);
1397
- else
1398
- seen.add(value);
1198
+ if (!result || !detectedRegion) {
1199
+ throw new Error(`Could not authorize ${startUrl} in ${regions.join(", ")}. Check your start URL${regions.length === 1 ? " and region" : ""} and try again.`);
1200
+ }
1201
+ callbacks.onAuth({
1202
+ url: result.devAuth.verificationUriComplete,
1203
+ instructions: `Code: ${result.devAuth.userCode}
1204
+ Complete authorization within 10 minutes.`
1205
+ });
1206
+ callbacks.onProgress?.("Waiting for browser authorization (up to 10 minutes)…");
1207
+ const tok = await pollForToken(result.oidcEndpoint, result.clientId, result.clientSecret, result.devAuth, callbacks.signal);
1208
+ if (!tok.accessToken || !tok.refreshToken) {
1209
+ throw new Error("Authorization completed but no tokens returned");
1399
1210
  }
1400
- return [...dupes].sort();
1401
- }
1402
- function objectKeys(value) {
1403
- if (!value || typeof value !== "object" || Array.isArray(value))
1404
- return [];
1405
- return Object.keys(value).sort();
1406
- }
1407
- function summarizeToolUse(toolUse) {
1408
- const input = toolUse.input;
1409
1211
  return {
1410
- name: toolUse.name,
1411
- toolUseId: toolUse.toolUseId,
1412
- inputType: Array.isArray(input) ? "array" : typeof input,
1413
- inputKeys: objectKeys(input)
1212
+ refresh: `${tok.refreshToken}|${result.clientId}|${result.clientSecret}|${authMethod}`,
1213
+ access: tok.accessToken,
1214
+ expires: Date.now() + (tok.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS,
1215
+ clientId: result.clientId,
1216
+ clientSecret: result.clientSecret,
1217
+ region: detectedRegion,
1218
+ authMethod
1414
1219
  };
1415
1220
  }
1416
- function summarizeToolResult(toolResult) {
1417
- return {
1418
- toolUseId: toolResult.toolUseId,
1419
- status: toolResult.status,
1420
- contentCount: toolResult.content.length,
1421
- textChars: toolResult.content.reduce((sum, part) => sum + part.text.length, 0)
1422
- };
1221
+ async function syncBackToKiroCli(result) {
1222
+ if (result.kiroSyncSource !== "kiro-cli-db" || !result.kiroSyncTokenKey) {
1223
+ log.debug("Credential sync-back skipped: credential did not originate from kiro-cli DB");
1224
+ return;
1225
+ }
1226
+ try {
1227
+ const { saveKiroCliCredentials: saveKiroCliCredentials2 } = await Promise.resolve().then(() => (init_kiro_cli_sync(), exports_kiro_cli_sync));
1228
+ const synced = await saveKiroCliCredentials2({
1229
+ accessToken: result.access,
1230
+ refreshToken: result.refresh.split("|")[0] ?? "",
1231
+ region: result.region,
1232
+ authMethod: result.authMethod === "builder-id" ? "idc" : result.authMethod,
1233
+ source: result.kiroSyncSource,
1234
+ tokenKey: result.kiroSyncTokenKey
1235
+ });
1236
+ if (synced)
1237
+ log.info("Synced refreshed credentials back to Kiro CLI DB");
1238
+ } catch (err) {
1239
+ log.debug(`Credential sync-back skipped: ${err}`);
1240
+ }
1423
1241
  }
1424
- function summarizeToolSpec(toolSpec) {
1425
- const spec = toolSpec.toolSpecification;
1426
- const schema = spec.inputSchema.json;
1427
- const properties = schema.properties;
1428
- const required = schema.required;
1242
+ function kiroCredsFromCliImport(imported) {
1243
+ const hasOidcCreds = !!imported.clientId && !!imported.clientSecret;
1244
+ const authMethod = hasOidcCreds && imported.authMethod === "idc" ? "idc" : "desktop";
1245
+ const refreshPacked = hasOidcCreds ? `${imported.refreshToken}|${imported.clientId}|${imported.clientSecret ?? ""}|${authMethod}` : `${imported.refreshToken}|||desktop`;
1429
1246
  return {
1430
- name: spec.name,
1431
- descriptionChars: spec.description.length,
1432
- schemaKeys: objectKeys(schema),
1433
- propertyNames: properties && typeof properties === "object" && !Array.isArray(properties) ? Object.keys(properties).sort() : [],
1434
- requiredCount: Array.isArray(required) ? required.length : 0
1247
+ refresh: refreshPacked,
1248
+ access: imported.accessToken,
1249
+ expires: Date.now() + 3600000 - EXPIRES_BUFFER_MS,
1250
+ clientId: imported.clientId ?? "",
1251
+ clientSecret: imported.clientSecret ?? "",
1252
+ region: imported.region,
1253
+ authMethod,
1254
+ profileArn: imported.profileArn,
1255
+ kiroSyncSource: imported.source,
1256
+ kiroSyncTokenKey: imported.tokenKey
1435
1257
  };
1436
1258
  }
1437
- function summarizeHistoryEntry(entry, index) {
1438
- if (entry.userInputMessage) {
1439
- const toolResults = entry.userInputMessage.userInputMessageContext?.toolResults ?? [];
1440
- return {
1441
- index,
1442
- role: "user",
1443
- contentChars: entry.userInputMessage.content.length,
1444
- imageCount: entry.userInputMessage.images?.length ?? 0,
1445
- ...toolResults.length > 0 ? { toolResults: toolResults.map(summarizeToolResult) } : {}
1446
- };
1259
+ async function refreshTokenInner(credentials) {
1260
+ const parts = credentials.refresh.split("|");
1261
+ const refreshToken = parts[0] ?? "";
1262
+ const clientId = parts[1] ?? credentials.clientId ?? "";
1263
+ const clientSecret = parts[2] ?? credentials.clientSecret ?? "";
1264
+ const region = credentials.region;
1265
+ const authMethod = credentials.authMethod;
1266
+ if (!refreshToken || !region) {
1267
+ throw new Error("Refresh token is missing region re-login required");
1268
+ }
1269
+ if (authMethod !== "desktop" && (!clientId || !clientSecret)) {
1270
+ throw new Error("Refresh token is missing clientId/clientSecret — re-login required");
1447
1271
  }
1448
- if (entry.assistantResponseMessage) {
1449
- const toolUses = entry.assistantResponseMessage.toolUses ?? [];
1272
+ if (authMethod === "desktop") {
1273
+ const desktopEndpoint = `https://prod.${region}.auth.desktop.kiro.dev/refreshToken`;
1274
+ const resp2 = await fetch(desktopEndpoint, {
1275
+ method: "POST",
1276
+ headers: { "Content-Type": "application/json", "User-Agent": "pi-kiro" },
1277
+ body: JSON.stringify({ refreshToken })
1278
+ });
1279
+ if (!resp2.ok) {
1280
+ const body = await resp2.text().catch(() => "");
1281
+ throw new Error(`Desktop token refresh failed: ${resp2.status} ${body}`);
1282
+ }
1283
+ const data2 = await resp2.json();
1284
+ if (credentials.profileArn) {
1285
+ try {
1286
+ const apiRegion = resolveApiRegion(region);
1287
+ const apiModels = await fetchAvailableModels(data2.accessToken, apiRegion, credentials.profileArn);
1288
+ setCachedDynamicModels(buildModelsFromApi(apiModels));
1289
+ log.info(`Fetched and cached ${apiModels.length} models after desktop token refresh`);
1290
+ } catch (err) {
1291
+ log.warn(`Failed to fetch models after desktop token refresh: ${err}`);
1292
+ }
1293
+ }
1450
1294
  return {
1451
- index,
1452
- role: "assistant",
1453
- contentChars: entry.assistantResponseMessage.content.length,
1454
- ...toolUses.length > 0 ? { toolUses: toolUses.map(summarizeToolUse) } : {}
1295
+ refresh: `${data2.refreshToken}|||desktop`,
1296
+ access: data2.accessToken,
1297
+ expires: Date.now() + (data2.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS,
1298
+ clientId: "",
1299
+ clientSecret: "",
1300
+ region,
1301
+ authMethod: "desktop",
1302
+ profileArn: credentials.profileArn,
1303
+ kiroSyncSource: credentials.kiroSyncSource,
1304
+ kiroSyncTokenKey: credentials.kiroSyncTokenKey
1455
1305
  };
1456
1306
  }
1457
- return { index, role: "unknown", contentChars: 0 };
1458
- }
1459
- function collectToolIntegrity(history, currentToolResults) {
1460
- const toolUseIds = [];
1461
- const toolResultIds = currentToolResults.map((toolResult) => toolResult.toolUseId);
1462
- for (const entry of history) {
1463
- for (const toolUse of entry.assistantResponseMessage?.toolUses ?? []) {
1464
- toolUseIds.push(toolUse.toolUseId);
1465
- }
1466
- for (const toolResult of entry.userInputMessage?.userInputMessageContext?.toolResults ?? []) {
1467
- toolResultIds.push(toolResult.toolUseId);
1307
+ const endpoint = `https://oidc.${region}.amazonaws.com/token`;
1308
+ const resp = await fetch(endpoint, {
1309
+ method: "POST",
1310
+ headers: { "Content-Type": "application/json", "User-Agent": "pi-kiro" },
1311
+ body: JSON.stringify({ clientId, clientSecret, refreshToken, grantType: "refresh_token" })
1312
+ });
1313
+ if (!resp.ok) {
1314
+ const body = await resp.text().catch(() => "");
1315
+ throw new Error(`Token refresh failed: ${resp.status} ${body}`);
1316
+ }
1317
+ const data = await resp.json();
1318
+ if (credentials.profileArn) {
1319
+ try {
1320
+ const apiRegion = resolveApiRegion(region);
1321
+ const apiModels = await fetchAvailableModels(data.accessToken, apiRegion, credentials.profileArn);
1322
+ setCachedDynamicModels(buildModelsFromApi(apiModels));
1323
+ log.info(`Fetched and cached ${apiModels.length} models after token refresh`);
1324
+ } catch (err) {
1325
+ log.warn(`Failed to fetch models after token refresh, falling back: ${err}`);
1468
1326
  }
1469
1327
  }
1470
- const useSet = new Set(toolUseIds);
1471
- const resultSet = new Set(toolResultIds);
1472
1328
  return {
1473
- toolUseIds: uniqueSorted(toolUseIds),
1474
- toolResultIds: uniqueSorted(toolResultIds),
1475
- duplicateToolUseIds: duplicateValues(toolUseIds),
1476
- duplicateToolResultIds: duplicateValues(toolResultIds),
1477
- unmatchedToolResults: uniqueSorted(toolResultIds.filter((id) => !useSet.has(id))),
1478
- toolUsesWithoutResults: uniqueSorted(toolUseIds.filter((id) => !resultSet.has(id)))
1329
+ refresh: `${data.refreshToken}|${clientId}|${clientSecret}|${authMethod}`,
1330
+ access: data.accessToken,
1331
+ expires: Date.now() + (data.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS,
1332
+ clientId,
1333
+ clientSecret,
1334
+ region,
1335
+ authMethod,
1336
+ profileArn: credentials.profileArn,
1337
+ kiroSyncSource: credentials.kiroSyncSource,
1338
+ kiroSyncTokenKey: credentials.kiroSyncTokenKey
1479
1339
  };
1480
1340
  }
1481
- function summarizeKiroRequest(request, requestBody) {
1482
- const current = request.conversationState.currentMessage.userInputMessage;
1483
- const history = request.conversationState.history ?? [];
1484
- const currentContext = current.userInputMessageContext;
1485
- const currentToolResults = currentContext?.toolResults ?? [];
1486
- const currentTools = currentContext?.tools ?? [];
1487
- const historySummary = history.map(summarizeHistoryEntry);
1488
- return {
1489
- conversationId: request.conversationState.conversationId,
1490
- modelId: current.modelId,
1491
- requestJsonChars: requestBody.length,
1492
- historyLen: history.length,
1493
- roleSequence: historySummary.map((entry) => entry.role),
1494
- history: historySummary,
1495
- current: {
1496
- contentChars: current.content.length,
1497
- imageCount: current.images?.length ?? 0,
1498
- toolResultCount: currentToolResults.length,
1499
- toolSpecCount: currentTools.length,
1500
- toolSpecNames: currentTools.map((toolSpec) => toolSpec.toolSpecification.name).sort(),
1501
- toolResults: currentToolResults.map(summarizeToolResult),
1502
- toolSpecs: currentTools.map(summarizeToolSpec)
1503
- },
1504
- toolIntegrity: collectToolIntegrity(history, currentToolResults),
1505
- additionalModelRequestFieldKeys: objectKeys(request.additionalModelRequestFields),
1506
- hasProfileArn: !!request.profileArn,
1507
- agentMode: request.agentMode
1341
+ async function refreshKiroToken(credentials) {
1342
+ 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") {
1345
+ log.warn(`refreshKiroToken: unrecognized authMethod "${String(inputMethod)}" defaulting to "idc"`);
1346
+ }
1347
+ const baseCreds = {
1348
+ ...credentials,
1349
+ clientId: credentials.clientId ?? credentials.refresh.split("|")[1] ?? "",
1350
+ clientSecret: credentials.clientSecret ?? credentials.refresh.split("|")[2] ?? "",
1351
+ region: credentials.region,
1352
+ authMethod
1508
1353
  };
1509
- }
1510
- function emitToolCall(state, output, stream) {
1511
- if (!state.input.trim())
1512
- state.input = "{}";
1513
- let args;
1354
+ const errors = [];
1514
1355
  try {
1515
- args = JSON.parse(state.input);
1516
- if (args && typeof args === "object" && "__tool_use_purpose" in args) {
1517
- delete args.__tool_use_purpose;
1356
+ log.debug("refresh.cascade: layer 1 — normal refresh");
1357
+ const result = await refreshTokenInner(baseCreds);
1358
+ syncBackToKiroCli(result);
1359
+ return result;
1360
+ } catch (err) {
1361
+ const msg = err instanceof Error ? err.message : String(err);
1362
+ errors.push(`L1(normal): ${msg}`);
1363
+ log.warn(`refresh.cascade: layer 1 failed — ${msg}`);
1364
+ }
1365
+ let freshImport = null;
1366
+ try {
1367
+ log.debug("refresh.cascade: layer 2 — fresh kiro-cli import");
1368
+ const { importFromKiroCli: importFromKiroCli2 } = await Promise.resolve().then(() => (init_kiro_cli_sync(), exports_kiro_cli_sync));
1369
+ freshImport = await importFromKiroCli2();
1370
+ if (freshImport?.accessToken) {
1371
+ const result = kiroCredsFromCliImport(freshImport);
1372
+ log.info("refresh.cascade: layer 2 succeeded — using fresh kiro-cli credentials");
1373
+ return result;
1518
1374
  }
1519
- } catch (e) {
1520
- log.warn(`failed to parse tool input for "${state.name}" (${state.toolUseId}): ${e instanceof Error ? e.message : String(e)}`);
1521
- return false;
1375
+ errors.push("L2(fresh-import): no valid credentials found");
1376
+ log.debug("refresh.cascade: layer 2 no fresh credentials");
1377
+ } catch (err) {
1378
+ const msg = err instanceof Error ? err.message : String(err);
1379
+ errors.push(`L2(fresh-import): ${msg}`);
1380
+ log.warn(`refresh.cascade: layer 2 failed — ${msg}`);
1522
1381
  }
1523
- const contentIndex = output.content.length;
1524
- const toolCall = { type: "toolCall", id: state.toolUseId, name: state.name, arguments: args };
1525
- output.content.push(toolCall);
1526
- stream.push({ type: "toolcall_start", contentIndex, partial: output });
1527
- stream.push({ type: "toolcall_delta", contentIndex, delta: state.input, partial: output });
1528
- stream.push({ type: "toolcall_end", contentIndex, toolCall, partial: output });
1529
- return true;
1530
- }
1531
- function streamKiro(model, context, options) {
1532
- const stream = createAssistantMessageEventStream();
1533
- (async () => {
1534
- const output = {
1535
- role: "assistant",
1536
- content: [],
1537
- api: model.api,
1538
- provider: model.provider,
1539
- model: model.id,
1540
- usage: {
1541
- input: 0,
1542
- output: 0,
1543
- cacheRead: 0,
1544
- cacheWrite: 0,
1545
- totalTokens: 0,
1546
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
1547
- },
1548
- stopReason: "stop",
1549
- timestamp: Date.now()
1550
- };
1551
- let hiddenShimTimer = null;
1382
+ if (freshImport?.refreshToken) {
1552
1383
  try {
1553
- const accessToken = options?.apiKey;
1554
- if (!accessToken) {
1555
- throw new Error("Kiro credentials not set. Run /login kiro.");
1556
- }
1557
- const endpoint = model.baseUrl || "https://runtime.us-east-1.kiro.dev";
1558
- const profileArn = await resolveProfileArn(accessToken, endpoint);
1559
- const kiroModelId = resolveKiroModel(model.id);
1560
- const thinkingEnabled = !!options?.reasoning || model.reasoning;
1561
- const reasoningHidden = !!model.reasoningHidden;
1562
- log.debug("request.init", {
1563
- endpoint,
1564
- model: model.id,
1565
- kiroModelId,
1566
- contextWindow: model.contextWindow,
1567
- thinkingEnabled,
1568
- reasoningHidden,
1569
- reasoning: options?.reasoning,
1570
- messageCount: context.messages.length,
1571
- toolCount: context.tools?.length ?? 0,
1572
- hasSystemPrompt: !!context.systemPrompt,
1573
- profileArn,
1574
- sessionId: options?.sessionId
1575
- });
1576
- let systemPrompt = context.systemPrompt ?? "";
1577
- if (thinkingEnabled && !reasoningHidden) {
1578
- const budget = options?.reasoning === "xhigh" ? 50000 : options?.reasoning === "high" ? 30000 : options?.reasoning === "medium" ? 20000 : 1e4;
1579
- systemPrompt = `<thinking_mode>enabled</thinking_mode><max_thinking_length>${budget}</max_thinking_length>${systemPrompt ? `
1580
- ${systemPrompt}` : ""}`;
1581
- }
1582
- const envState = {
1583
- operatingSystem: resolveOS(),
1584
- currentWorkingDirectory: process.cwd()
1585
- };
1586
- const conversationId = options?.sessionId ?? crypto.randomUUID();
1587
- let retryCount = 0;
1588
- while (retryCount <= MAX_RETRIES) {
1589
- if (options?.signal?.aborted)
1590
- throw options.signal.reason;
1591
- const normalized = normalizeMessages(context.messages);
1592
- const {
1593
- history,
1594
- systemPrepended,
1595
- currentMsgStartIdx
1596
- } = buildHistory(normalized, kiroModelId, systemPrompt);
1597
- const seedInstruction = SYSTEM_SEED_INSTRUCTION.replace("{{modelId}}", kiroModelId);
1598
- const seedPair = [
1599
- { userInputMessage: { content: seedInstruction, origin: "KIRO_CLI" } },
1600
- { assistantResponseMessage: { content: SYSTEM_SEED_ACK } }
1601
- ];
1602
- history.unshift(...seedPair);
1603
- const currentMessages = normalized.slice(currentMsgStartIdx);
1604
- const firstMsg = currentMessages[0];
1605
- let currentContent = "";
1606
- const currentToolResults = [];
1607
- let currentImages;
1608
- if (firstMsg?.role === "assistant") {
1609
- const am = firstMsg;
1610
- let armContent = "";
1611
- const armToolUses = [];
1612
- if (Array.isArray(am.content)) {
1613
- for (const b of am.content) {
1614
- if (b.type === "text") {
1615
- armContent += b.text;
1616
- } else if (b.type === "thinking") {
1617
- armContent = `<thinking>${b.thinking}</thinking>
1618
-
1619
- ${armContent}`;
1620
- } else if (b.type === "toolCall") {
1621
- const tc = b;
1622
- armToolUses.push({
1623
- name: tc.name,
1624
- toolUseId: toKiroToolUseId(tc.id),
1625
- input: parseToolArgs(tc.arguments)
1626
- });
1627
- }
1628
- }
1629
- }
1630
- if (armContent || armToolUses.length > 0) {
1631
- const last = history[history.length - 1];
1632
- if (last && !last.userInputMessage && last.assistantResponseMessage) {
1633
- last.assistantResponseMessage.content += `
1634
-
1635
- ${armContent}`;
1636
- if (armToolUses.length > 0) {
1637
- last.assistantResponseMessage.toolUses = [
1638
- ...last.assistantResponseMessage.toolUses ?? [],
1639
- ...armToolUses
1640
- ];
1641
- }
1642
- } else {
1643
- history.push({
1644
- assistantResponseMessage: {
1645
- content: armContent,
1646
- ...armToolUses.length > 0 ? { toolUses: armToolUses } : {}
1647
- }
1648
- });
1649
- }
1650
- }
1651
- const toolResultImages = [];
1652
- for (let i = 1;i < currentMessages.length; i++) {
1653
- const m = currentMessages[i];
1654
- if (m?.role === "toolResult") {
1655
- const trm = m;
1656
- currentToolResults.push({
1657
- content: [{ text: truncate(getContentText(m), TOOL_RESULT_LIMIT) }],
1658
- status: trm.isError ? "error" : "success",
1659
- toolUseId: toKiroToolUseId(trm.toolCallId)
1660
- });
1661
- if (Array.isArray(trm.content)) {
1662
- for (const c of trm.content) {
1663
- if (c.type === "image")
1664
- toolResultImages.push(c);
1665
- }
1666
- }
1667
- }
1668
- }
1669
- if (toolResultImages.length > 0) {
1670
- const { images: converted, omitted } = convertImagesToKiro(toolResultImages);
1671
- if (omitted > 0)
1672
- log.warn(`${omitted} tool-result image(s) omitted (size/count limit)`);
1673
- currentImages = currentImages ? [...currentImages, ...converted] : converted;
1674
- }
1675
- currentContent = currentToolResults.length > 0 ? "Tool results provided." : "Please proceed with the task.";
1676
- } else if (firstMsg?.role === "toolResult") {
1677
- const toolResultImages = [];
1678
- for (const m of currentMessages) {
1679
- if (m?.role === "toolResult") {
1680
- const trm = m;
1681
- currentToolResults.push({
1682
- content: [{ text: truncate(getContentText(m), TOOL_RESULT_LIMIT) }],
1683
- status: trm.isError ? "error" : "success",
1684
- toolUseId: toKiroToolUseId(trm.toolCallId)
1685
- });
1686
- if (Array.isArray(trm.content)) {
1687
- for (const c of trm.content) {
1688
- if (c.type === "image")
1689
- toolResultImages.push(c);
1690
- }
1691
- }
1692
- }
1693
- }
1694
- if (toolResultImages.length > 0) {
1695
- const { images: converted, omitted } = convertImagesToKiro(toolResultImages);
1696
- if (omitted > 0)
1697
- log.warn(`${omitted} tool-result image(s) omitted (size/count limit)`);
1698
- currentImages = currentImages ? [...currentImages, ...converted] : converted;
1699
- }
1700
- currentContent = "Tool results provided.";
1701
- } else if (firstMsg?.role === "user") {
1702
- currentContent = typeof firstMsg.content === "string" ? firstMsg.content : getContentText(firstMsg);
1703
- if (systemPrompt && !systemPrepended) {
1704
- currentContent = `${systemPrompt}
1384
+ log.debug("refresh.cascade: layer 3 — refresh fresh kiro-cli creds");
1385
+ const freshCreds = kiroCredsFromCliImport(freshImport);
1386
+ const result = await refreshTokenInner(freshCreds);
1387
+ syncBackToKiroCli(result);
1388
+ log.info("refresh.cascade: layer 3 succeeded refreshed fresh kiro-cli credentials");
1389
+ return result;
1390
+ } catch (err) {
1391
+ const msg = err instanceof Error ? err.message : String(err);
1392
+ errors.push(`L3(refresh-fresh): ${msg}`);
1393
+ log.warn(`refresh.cascade: layer 3 failed — ${msg}`);
1394
+ }
1395
+ }
1396
+ let expiredImport = null;
1397
+ try {
1398
+ log.debug("refresh.cascade: layer 4 — expired kiro-cli import");
1399
+ const { getKiroCliCredentialsAllowExpired: getKiroCliCredentialsAllowExpired2 } = await Promise.resolve().then(() => (init_kiro_cli_sync(), exports_kiro_cli_sync));
1400
+ expiredImport = await getKiroCliCredentialsAllowExpired2(freshImport);
1401
+ if (expiredImport?.accessToken) {
1402
+ const result = kiroCredsFromCliImport(expiredImport);
1403
+ log.info("refresh.cascade: layer 4 succeeded — using expired kiro-cli credentials");
1404
+ return result;
1405
+ } else {
1406
+ errors.push("L4(expired-import): no different expired credentials");
1407
+ log.debug("refresh.cascade: layer 4 no additional expired credentials");
1408
+ }
1409
+ } catch (err) {
1410
+ const msg = err instanceof Error ? err.message : String(err);
1411
+ errors.push(`L4(expired-import): ${msg}`);
1412
+ log.warn(`refresh.cascade: layer 4 failed — ${msg}`);
1413
+ }
1414
+ if (expiredImport?.refreshToken) {
1415
+ try {
1416
+ log.debug("refresh.cascade: layer 5 — refresh expired kiro-cli creds");
1417
+ const expiredCreds = kiroCredsFromCliImport(expiredImport);
1418
+ const result = await refreshTokenInner(expiredCreds);
1419
+ syncBackToKiroCli(result);
1420
+ log.info("refresh.cascade: layer 5 succeeded — refreshed expired kiro-cli credentials");
1421
+ return result;
1422
+ } catch (err) {
1423
+ const msg = err instanceof Error ? err.message : String(err);
1424
+ errors.push(`L5(refresh-expired): ${msg}`);
1425
+ log.warn(`refresh.cascade: layer 5 failed — ${msg}`);
1426
+ }
1427
+ }
1428
+ throw new Error(`Kiro token refresh failed — all 5 cascade layers exhausted. ` + `Re-login required.
1429
+ ${errors.join(`
1430
+ `)}`);
1431
+ }
1705
1432
 
1706
- ${currentContent}`;
1707
- }
1708
- }
1709
- const now = new Date;
1710
- const tzOffset = -now.getTimezoneOffset();
1711
- const tzSign = tzOffset >= 0 ? "+" : "-";
1712
- const tzH = String(Math.floor(Math.abs(tzOffset) / 60)).padStart(2, "0");
1713
- const tzM = String(Math.abs(tzOffset) % 60).padStart(2, "0");
1714
- const isoLocal = now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0") + "-" + String(now.getDate()).padStart(2, "0") + "T" + String(now.getHours()).padStart(2, "0") + ":" + String(now.getMinutes()).padStart(2, "0") + ":" + String(now.getSeconds()).padStart(2, "0") + "." + String(now.getMilliseconds()).padStart(3, "0") + tzSign + tzH + ":" + tzM;
1715
- const weekday = now.toLocaleDateString("en-US", { weekday: "long" });
1716
- currentContent = `--- CONTEXT ENTRY BEGIN ---
1717
- ` + `Current time: ${weekday}, ${isoLocal}
1718
- ` + `--- CONTEXT ENTRY END ---
1433
+ // src/stream.ts
1434
+ init_debug();
1435
+ import { calculateCost, createAssistantMessageEventStream } from "@earendil-works/pi-ai";
1719
1436
 
1720
- ` + `--- USER MESSAGE BEGIN ---
1721
- ` + `${currentContent}
1722
- ` + `--- USER MESSAGE END ---`;
1723
- let uimc = {
1724
- envState
1725
- };
1726
- if (currentToolResults.length > 0)
1727
- uimc.toolResults = currentToolResults;
1728
- if (context.tools?.length) {
1729
- const deepCleanSchema = (obj) => {
1730
- if (Array.isArray(obj)) {
1731
- return obj.map(deepCleanSchema);
1732
- } else if (obj !== null && typeof obj === "object") {
1733
- const cleaned = {};
1734
- for (const [k, v] of Object.entries(obj)) {
1735
- if (k === "$schema")
1736
- continue;
1737
- if (k === "required" && Array.isArray(v) && v.length === 0)
1738
- continue;
1739
- cleaned[k] = deepCleanSchema(v);
1740
- }
1741
- return cleaned;
1742
- }
1743
- return obj;
1744
- };
1745
- uimc.tools = context.tools.map((t) => {
1746
- const params = deepCleanSchema(t.parameters);
1747
- if (params.properties) {
1748
- params.properties.__tool_use_purpose = {
1749
- type: "string",
1750
- description: "A brief explanation why you are making this tool use."
1751
- };
1752
- }
1753
- return {
1754
- toolSpecification: {
1755
- name: t.name,
1756
- description: t.description || `Use ${t.name}`,
1757
- inputSchema: { json: params }
1758
- }
1759
- };
1437
+ // src/event-parser.ts
1438
+ init_debug();
1439
+ function findJsonEnd(text, start) {
1440
+ let braceCount = 0;
1441
+ let inString = false;
1442
+ let escapeNext = false;
1443
+ for (let i = start;i < text.length; i++) {
1444
+ const char = text[i];
1445
+ if (escapeNext) {
1446
+ escapeNext = false;
1447
+ continue;
1448
+ }
1449
+ if (char === "\\") {
1450
+ escapeNext = true;
1451
+ continue;
1452
+ }
1453
+ if (char === '"') {
1454
+ inString = !inString;
1455
+ continue;
1456
+ }
1457
+ if (!inString) {
1458
+ if (char === "{")
1459
+ braceCount++;
1460
+ else if (char === "}") {
1461
+ braceCount--;
1462
+ if (braceCount === 0)
1463
+ return i;
1464
+ }
1465
+ }
1466
+ }
1467
+ return -1;
1468
+ }
1469
+ function parseKiroEvent(parsed) {
1470
+ if (parsed.content !== undefined) {
1471
+ return { type: "content", data: parsed.content };
1472
+ }
1473
+ if (parsed.reasoningText !== undefined || parsed.signature !== undefined || parsed.text !== undefined && !parsed.content && !parsed.name && !parsed.message) {
1474
+ let text = "";
1475
+ let signature;
1476
+ if (parsed.reasoningText) {
1477
+ const rt = parsed.reasoningText;
1478
+ text = (rt.text ?? rt.Text) || "";
1479
+ signature = rt.signature ?? rt.Signature;
1480
+ } else {
1481
+ text = parsed.text || "";
1482
+ signature = parsed.signature;
1483
+ }
1484
+ return {
1485
+ type: "reasoning",
1486
+ data: { text, signature }
1487
+ };
1488
+ }
1489
+ if (parsed.name && parsed.toolUseId) {
1490
+ const rawInput = parsed.input;
1491
+ const input = typeof rawInput === "string" ? rawInput : rawInput && typeof rawInput === "object" && Object.keys(rawInput).length > 0 ? JSON.stringify(rawInput) : "";
1492
+ return {
1493
+ type: "toolUse",
1494
+ data: {
1495
+ name: parsed.name,
1496
+ toolUseId: parsed.toolUseId,
1497
+ input,
1498
+ stop: parsed.stop
1499
+ }
1500
+ };
1501
+ }
1502
+ if (parsed.input !== undefined && !parsed.name) {
1503
+ return {
1504
+ type: "toolUseInput",
1505
+ data: {
1506
+ input: typeof parsed.input === "string" ? parsed.input : JSON.stringify(parsed.input)
1507
+ }
1508
+ };
1509
+ }
1510
+ if (parsed.stop !== undefined && parsed.contextUsagePercentage === undefined) {
1511
+ return { type: "toolUseStop", data: { stop: parsed.stop } };
1512
+ }
1513
+ if (parsed.contextUsagePercentage !== undefined) {
1514
+ return {
1515
+ type: "contextUsage",
1516
+ data: { contextUsagePercentage: parsed.contextUsagePercentage }
1517
+ };
1518
+ }
1519
+ if (parsed.followupPrompt !== undefined) {
1520
+ return { type: "followupPrompt", data: parsed.followupPrompt };
1521
+ }
1522
+ if (parsed.error !== undefined || parsed.Error !== undefined) {
1523
+ const err = parsed.error || parsed.Error || "unknown";
1524
+ const message = parsed.message || parsed.Message || parsed.reason;
1525
+ return {
1526
+ type: "error",
1527
+ data: {
1528
+ error: typeof err === "string" ? err : JSON.stringify(err),
1529
+ message
1530
+ }
1531
+ };
1532
+ }
1533
+ if (parsed.usage !== undefined) {
1534
+ const u = parsed.usage;
1535
+ return {
1536
+ type: "usage",
1537
+ data: {
1538
+ inputTokens: u.inputTokens,
1539
+ outputTokens: u.outputTokens
1540
+ }
1541
+ };
1542
+ }
1543
+ return null;
1544
+ }
1545
+ var EVENT_PATTERNS = [
1546
+ '{"content":',
1547
+ '{"reasoningText":',
1548
+ '{"signature":',
1549
+ '{"text":',
1550
+ '{"name":',
1551
+ '{"input":',
1552
+ '{"stop":',
1553
+ '{"contextUsagePercentage":',
1554
+ '{"followupPrompt":',
1555
+ '{"usage":',
1556
+ '{"toolUseId":',
1557
+ '{"unit":',
1558
+ '{"error":',
1559
+ '{"Error":',
1560
+ '{"message":'
1561
+ ];
1562
+ function findNextEventStart(buffer, from) {
1563
+ let earliest = -1;
1564
+ for (const pattern of EVENT_PATTERNS) {
1565
+ const idx = buffer.indexOf(pattern, from);
1566
+ if (idx >= 0 && (earliest < 0 || idx < earliest))
1567
+ earliest = idx;
1568
+ }
1569
+ return earliest;
1570
+ }
1571
+ function parseKiroEvents(buffer) {
1572
+ const events = [];
1573
+ let pos = 0;
1574
+ while (pos < buffer.length) {
1575
+ const jsonStart = findNextEventStart(buffer, pos);
1576
+ if (jsonStart < 0) {
1577
+ if (log.isDebug()) {
1578
+ const gap = buffer.substring(pos);
1579
+ const braceIdx = gap.indexOf('{"');
1580
+ if (braceIdx >= 0) {
1581
+ log.debug("event.unmatchedBrace", {
1582
+ from: pos + braceIdx,
1583
+ preview: gap.substring(braceIdx, Math.min(braceIdx + 200, gap.length))
1760
1584
  });
1761
1585
  }
1762
- if (firstMsg?.role === "user") {
1763
- const imgs = extractImages(firstMsg);
1764
- if (imgs.length > 0) {
1765
- const { images: converted, omitted } = convertImagesToKiro(imgs);
1766
- if (omitted > 0)
1767
- log.warn(`${omitted} user image(s) omitted (size/count limit)`);
1768
- currentImages = converted;
1769
- }
1770
- }
1771
- const request = {
1772
- conversationState: {
1773
- chatTriggerType: "MANUAL",
1774
- agentTaskType: "vibe",
1775
- conversationId,
1776
- currentMessage: {
1777
- userInputMessage: {
1778
- content: currentContent,
1779
- modelId: kiroModelId,
1780
- origin: "KIRO_CLI",
1781
- ...currentImages ? { images: currentImages } : {},
1782
- userInputMessageContext: uimc
1783
- }
1784
- },
1785
- ...history.length > 0 ? { history } : {}
1786
- },
1787
- ...profileArn ? { profileArn } : {},
1788
- agentMode: "vibe"
1789
- };
1790
- const EFFORT_MAP = {
1791
- minimal: "low",
1792
- low: "medium",
1793
- medium: "high",
1794
- high: "xhigh",
1795
- xhigh: "max"
1796
- };
1797
- const staticModel = kiroModels.find((m) => m.id === model.id);
1798
- const dynamicModel = getCachedDynamicModels()?.find((m) => m.id === model.id);
1799
- const supportedEfforts = staticModel?.supportedEfforts ?? dynamicModel?.supportedEfforts;
1800
- const supportsThinkingConfig = staticModel?.supportsThinkingConfig ?? dynamicModel?.supportsThinkingConfig;
1801
- if (supportedEfforts && supportedEfforts.length > 0 && options?.reasoning && typeof options.reasoning === "string") {
1802
- const kiroEffort = EFFORT_MAP[options.reasoning];
1803
- if (kiroEffort && supportedEfforts.includes(kiroEffort)) {
1804
- request.additionalModelRequestFields = request.additionalModelRequestFields || {};
1805
- request.additionalModelRequestFields.output_config = { effort: kiroEffort };
1806
- log.debug("effort.set", { piReasoning: options.reasoning, kiroEffort, model: model.id });
1807
- }
1808
- }
1809
- if (supportsThinkingConfig && thinkingEnabled) {
1810
- request.additionalModelRequestFields = request.additionalModelRequestFields || {};
1811
- request.additionalModelRequestFields.thinking = {
1812
- type: "adaptive",
1813
- display: "summarized"
1814
- };
1815
- log.debug("thinking.set", { type: "adaptive", display: "summarized", model: model.id });
1816
- }
1817
- stream.push({ type: "start", partial: output });
1818
- if (reasoningHidden && thinkingEnabled && hiddenShimTimer === null) {
1819
- hiddenShimTimer = setTimeout(() => {
1820
- hiddenShimTimer = null;
1821
- emitHiddenReasoningLate(output, stream);
1822
- }, HIDDEN_REASONING_COUNTDOWN_MS);
1823
- }
1824
- let response;
1825
- let capacityRetryCount = 0;
1826
- let transientRetryCount = 0;
1827
- let contextTruncationAttempt = 0;
1828
- while (true) {
1829
- const osName = resolveOS();
1830
- 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`;
1831
- 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`;
1832
- const requestBody = JSON.stringify(request);
1833
- const requestShape = log.isDebug() ? summarizeKiroRequest(request, requestBody) : undefined;
1834
- if (requestShape)
1835
- log.debug("request.shape", requestShape);
1836
- log.debug("request.send", {
1837
- attempt: retryCount,
1838
- capacityAttempt: capacityRetryCount,
1839
- historyLen: history.length,
1840
- currentContentLen: currentContent.length,
1841
- hasImages: !!currentImages,
1842
- toolResultCount: currentToolResults.length,
1843
- requestJsonChars: requestBody.length
1844
- });
1845
- response = await fetch(endpoint, {
1846
- method: "POST",
1847
- headers: {
1848
- "Content-Type": "application/x-amz-json-1.0",
1849
- Accept: "*/*",
1850
- "Accept-Encoding": "gzip",
1851
- Authorization: `Bearer ${accessToken}`,
1852
- "X-Amz-Target": "AmazonCodeWhispererStreamingService.GenerateAssistantResponse",
1853
- "x-amzn-codewhisperer-optout": "true",
1854
- "amz-sdk-invocation-id": crypto.randomUUID(),
1855
- "amz-sdk-request": "attempt=1; max=3",
1856
- "user-agent": ua,
1857
- "x-amz-user-agent": xAmzUa,
1858
- Pragma: "no-cache",
1859
- "Cache-Control": "no-cache"
1860
- },
1861
- body: requestBody,
1862
- signal: options?.signal
1863
- });
1864
- if (response.ok)
1865
- break;
1866
- let errText = "";
1867
- try {
1868
- errText = await response.text();
1869
- } catch {
1870
- errText = "";
1871
- }
1872
- log.debug("response.error", {
1873
- status: response.status,
1874
- body: errText,
1875
- ...requestShape ? { requestShape } : {}
1586
+ }
1587
+ break;
1588
+ }
1589
+ if (log.isDebug() && jsonStart > pos) {
1590
+ const skipped = buffer.substring(pos, jsonStart);
1591
+ const braceIdx = skipped.indexOf('{"');
1592
+ if (braceIdx >= 0) {
1593
+ log.debug("event.skippedBrace", {
1594
+ from: pos + braceIdx,
1595
+ preview: skipped.substring(braceIdx, Math.min(braceIdx + 200, skipped.length))
1596
+ });
1597
+ }
1598
+ }
1599
+ const jsonEnd = findJsonEnd(buffer, jsonStart);
1600
+ if (jsonEnd < 0) {
1601
+ return { events, remaining: buffer.substring(jsonStart) };
1602
+ }
1603
+ try {
1604
+ const parsed = JSON.parse(buffer.substring(jsonStart, jsonEnd + 1));
1605
+ const event = parseKiroEvent(parsed);
1606
+ if (event) {
1607
+ events.push(event);
1608
+ } else if (log.isDebug()) {
1609
+ log.debug("event.unknown", { keys: Object.keys(parsed), raw: parsed });
1610
+ }
1611
+ } catch (err) {
1612
+ if (log.isDebug()) {
1613
+ log.debug("event.parseFail", {
1614
+ err: err instanceof Error ? err.message : String(err),
1615
+ snippet: buffer.substring(jsonStart, Math.min(jsonEnd + 1, jsonStart + 200))
1616
+ });
1617
+ }
1618
+ }
1619
+ pos = jsonEnd + 1;
1620
+ }
1621
+ return { events, remaining: "" };
1622
+ }
1623
+
1624
+ // src/health.ts
1625
+ var PERMANENT_PATTERNS = [
1626
+ "Invalid refresh token",
1627
+ "Invalid grant provided",
1628
+ "InvalidGrantException",
1629
+ "UnauthorizedClientException",
1630
+ "AuthorizationPendingException",
1631
+ "ExpiredTokenException",
1632
+ "client is not registered",
1633
+ "The security token is expired",
1634
+ "Access denied"
1635
+ ];
1636
+ function isPermanentError(reason) {
1637
+ if (!reason)
1638
+ return false;
1639
+ return PERMANENT_PATTERNS.some((p) => reason.includes(p));
1640
+ }
1641
+
1642
+ // src/thinking-parser.ts
1643
+ init_debug();
1644
+ var THINKING_END_TAG = "</thinking>";
1645
+ var THINKING_TAG_VARIANTS = [
1646
+ { open: "<thinking>", close: "</thinking>" },
1647
+ { open: "<think>", close: "</think>" },
1648
+ { open: "<reasoning>", close: "</reasoning>" },
1649
+ { open: "<thought>", close: "</thought>" }
1650
+ ];
1651
+ function trailingPrefixLength(text, tag) {
1652
+ const max = Math.min(text.length, tag.length - 1);
1653
+ for (let len = max;len > 0; len--) {
1654
+ if (text.endsWith(tag.slice(0, len)))
1655
+ return len;
1656
+ }
1657
+ return 0;
1658
+ }
1659
+ function maxTrailingPrefixLength(text, tags) {
1660
+ let max = 0;
1661
+ for (const tag of tags) {
1662
+ max = Math.max(max, trailingPrefixLength(text, tag));
1663
+ }
1664
+ return max;
1665
+ }
1666
+
1667
+ class ThinkingTagParser {
1668
+ output;
1669
+ stream;
1670
+ textBuffer = "";
1671
+ inThinking = false;
1672
+ thinkingExtracted = false;
1673
+ thinkingBlockIndex = null;
1674
+ textBlockIndex = null;
1675
+ lastTextBlockIndex = null;
1676
+ activeEndTag = THINKING_END_TAG;
1677
+ constructor(output, stream) {
1678
+ this.output = output;
1679
+ this.stream = stream;
1680
+ }
1681
+ processChunk(chunk) {
1682
+ this.textBuffer += chunk;
1683
+ if (log.isDebug()) {
1684
+ log.debug("thinking.chunk", {
1685
+ chunkLen: chunk.length,
1686
+ bufferLen: this.textBuffer.length,
1687
+ inThinking: this.inThinking,
1688
+ thinkingExtracted: this.thinkingExtracted
1689
+ });
1690
+ }
1691
+ while (this.textBuffer.length > 0) {
1692
+ const prev = this.textBuffer.length;
1693
+ if (!this.inThinking && !this.thinkingExtracted) {
1694
+ this.processBeforeThinking();
1695
+ if (this.textBuffer.length === 0)
1696
+ break;
1697
+ }
1698
+ if (this.inThinking) {
1699
+ this.processInsideThinking();
1700
+ if (this.textBuffer.length === 0)
1701
+ break;
1702
+ }
1703
+ if (this.thinkingExtracted) {
1704
+ this.processAfterThinking();
1705
+ break;
1706
+ }
1707
+ if (this.textBuffer.length >= prev)
1708
+ break;
1709
+ }
1710
+ }
1711
+ finalize() {
1712
+ if (log.isDebug()) {
1713
+ log.debug("thinking.finalize", {
1714
+ bufferLen: this.textBuffer.length,
1715
+ inThinking: this.inThinking,
1716
+ thinkingExtracted: this.thinkingExtracted,
1717
+ textBlockIndex: this.textBlockIndex,
1718
+ thinkingBlockIndex: this.thinkingBlockIndex
1719
+ });
1720
+ }
1721
+ if (this.textBuffer.length === 0)
1722
+ return;
1723
+ if (this.inThinking && this.thinkingBlockIndex !== null) {
1724
+ const block = this.output.content[this.thinkingBlockIndex];
1725
+ if (block) {
1726
+ block.thinking += this.textBuffer;
1727
+ this.stream.push({
1728
+ type: "thinking_delta",
1729
+ contentIndex: this.thinkingBlockIndex,
1730
+ delta: this.textBuffer,
1731
+ partial: this.output
1732
+ });
1733
+ this.stream.push({
1734
+ type: "thinking_end",
1735
+ contentIndex: this.thinkingBlockIndex,
1736
+ content: block.thinking,
1737
+ partial: this.output
1738
+ });
1739
+ }
1740
+ } else {
1741
+ this.emitText(this.textBuffer);
1742
+ }
1743
+ this.textBuffer = "";
1744
+ }
1745
+ getTextBlockIndex() {
1746
+ return this.textBlockIndex ?? this.lastTextBlockIndex;
1747
+ }
1748
+ processBeforeThinking() {
1749
+ let bestPos = -1;
1750
+ let bestVariant = null;
1751
+ for (const variant of THINKING_TAG_VARIANTS) {
1752
+ const pos = this.textBuffer.indexOf(variant.open);
1753
+ if (pos !== -1 && (bestPos === -1 || pos < bestPos)) {
1754
+ bestPos = pos;
1755
+ bestVariant = variant;
1756
+ }
1757
+ }
1758
+ if (bestPos !== -1 && bestVariant) {
1759
+ if (log.isDebug()) {
1760
+ log.debug("thinking.open", { tag: bestVariant.open, at: bestPos });
1761
+ }
1762
+ if (bestPos > 0)
1763
+ this.emitText(this.textBuffer.slice(0, bestPos));
1764
+ this.textBuffer = this.textBuffer.slice(bestPos + bestVariant.open.length);
1765
+ this.activeEndTag = bestVariant.close;
1766
+ this.inThinking = true;
1767
+ return;
1768
+ }
1769
+ const trailing = maxTrailingPrefixLength(this.textBuffer, THINKING_TAG_VARIANTS.map((v) => v.open));
1770
+ const safeLen = this.textBuffer.length - trailing;
1771
+ if (safeLen > 0) {
1772
+ this.emitText(this.textBuffer.slice(0, safeLen));
1773
+ this.textBuffer = this.textBuffer.slice(safeLen);
1774
+ }
1775
+ }
1776
+ processInsideThinking() {
1777
+ const endPos = this.textBuffer.indexOf(this.activeEndTag);
1778
+ if (endPos !== -1) {
1779
+ if (log.isDebug()) {
1780
+ log.debug("thinking.close", { tag: this.activeEndTag, at: endPos });
1781
+ }
1782
+ if (endPos > 0)
1783
+ this.emitThinking(this.textBuffer.slice(0, endPos));
1784
+ if (this.thinkingBlockIndex !== null) {
1785
+ const block = this.output.content[this.thinkingBlockIndex];
1786
+ if (block) {
1787
+ this.stream.push({
1788
+ type: "thinking_end",
1789
+ contentIndex: this.thinkingBlockIndex,
1790
+ content: block.thinking,
1791
+ partial: this.output
1876
1792
  });
1877
- if (isCapacityError(errText) && capacityRetryCount < CAPACITY_MAX_RETRIES) {
1878
- capacityRetryCount++;
1879
- const delayMs = exponentialBackoff(capacityRetryCount - 1, CAPACITY_BASE_DELAY_MS, CAPACITY_MAX_DELAY_MS);
1880
- log.warn(`INSUFFICIENT_MODEL_CAPACITY — retrying in ${delayMs}ms (${capacityRetryCount}/${CAPACITY_MAX_RETRIES})`);
1881
- await abortableDelay(delayMs, options?.signal);
1882
- continue;
1883
- }
1884
- if (isNonRetryableBodyError(errText) || isCapacityError(errText)) {
1885
- throw new Error(`Kiro API error: ${errText || response.statusText}`);
1886
- }
1887
- if (isTooBigError(response.status, errText)) {
1888
- if (contextTruncationAttempt < CONTEXT_TRUNCATION_MAX_RETRIES && history.length > 0) {
1889
- contextTruncationAttempt++;
1890
- const dropCount = Math.max(1, Math.floor(history.length * CONTEXT_TRUNCATION_DROP_RATIO));
1891
- const before = history.length;
1892
- history.splice(0, dropCount);
1893
- log.warn(`context too large — truncated history from ${before} to ${history.length} entries ` + `(attempt ${contextTruncationAttempt}/${CONTEXT_TRUNCATION_MAX_RETRIES})`);
1894
- request.conversationState.history = history.length > 0 ? history : undefined;
1895
- continue;
1896
- }
1897
- throw new Error(`Kiro API error: context_length_exceeded (${response.status} ${errText})`);
1898
- }
1899
- if (isTransientError(response.status) && transientRetryCount < TRANSIENT_MAX_RETRIES) {
1900
- transientRetryCount++;
1901
- const jitter = Math.floor(Math.random() * 1000);
1902
- const delayMs = exponentialBackoff(transientRetryCount - 1, TRANSIENT_BASE_DELAY_MS, TRANSIENT_MAX_DELAY_MS) + jitter;
1903
- log.warn(`transient error ${response.status} — retrying in ${delayMs}ms ` + `(${transientRetryCount}/${TRANSIENT_MAX_RETRIES})`);
1904
- await abortableDelay(delayMs, options?.signal);
1905
- continue;
1906
- }
1907
- if (response.status === 401) {
1908
- const permanent = isPermanentError(errText);
1909
- if (permanent) {
1910
- profileArnCache.delete(endpoint);
1911
- throw new Error(`Kiro API error: credentials permanently invalid — run /login kiro to re-authenticate. ${errText}`);
1912
- }
1913
- }
1914
- if (response.status === 403) {
1915
- profileArnCache.delete(endpoint);
1916
- throw new Error(`Kiro API error: access token rejected (403) — run /login kiro to re-authenticate. ${errText}`);
1917
- }
1918
- throw new Error(`Kiro API error: ${response.status} ${response.statusText} ${errText}`);
1919
1793
  }
1920
- if (capacityRetryCount > 0) {
1921
- log.info(`recovered from capacity pressure after ${capacityRetryCount} retries`);
1922
- }
1923
- if (transientRetryCount > 0) {
1924
- log.info(`recovered from transient error after ${transientRetryCount} retries`);
1925
- }
1926
- if (contextTruncationAttempt > 0) {
1927
- log.info(`recovered after ${contextTruncationAttempt} context truncation(s)`);
1794
+ }
1795
+ this.textBuffer = this.textBuffer.slice(endPos + this.activeEndTag.length);
1796
+ this.inThinking = false;
1797
+ this.thinkingExtracted = true;
1798
+ this.lastTextBlockIndex = this.textBlockIndex;
1799
+ this.textBlockIndex = null;
1800
+ if (this.textBuffer.startsWith(`
1801
+
1802
+ `))
1803
+ this.textBuffer = this.textBuffer.slice(2);
1804
+ return;
1805
+ }
1806
+ const trailing = trailingPrefixLength(this.textBuffer, this.activeEndTag);
1807
+ const safeLen = this.textBuffer.length - trailing;
1808
+ if (safeLen > 0) {
1809
+ this.emitThinking(this.textBuffer.slice(0, safeLen));
1810
+ this.textBuffer = this.textBuffer.slice(safeLen);
1811
+ }
1812
+ }
1813
+ processAfterThinking() {
1814
+ this.emitText(this.textBuffer);
1815
+ this.textBuffer = "";
1816
+ }
1817
+ emitText(text) {
1818
+ if (!text)
1819
+ return;
1820
+ if (this.textBlockIndex === null) {
1821
+ this.textBlockIndex = this.output.content.length;
1822
+ this.output.content.push({ type: "text", text: "" });
1823
+ this.stream.push({ type: "text_start", contentIndex: this.textBlockIndex, partial: this.output });
1824
+ }
1825
+ const block = this.output.content[this.textBlockIndex];
1826
+ if (!block)
1827
+ return;
1828
+ block.text += text;
1829
+ this.stream.push({
1830
+ type: "text_delta",
1831
+ contentIndex: this.textBlockIndex,
1832
+ delta: text,
1833
+ partial: this.output
1834
+ });
1835
+ }
1836
+ emitThinking(thinking) {
1837
+ if (!thinking)
1838
+ return;
1839
+ if (this.thinkingBlockIndex === null) {
1840
+ if (this.textBlockIndex !== null) {
1841
+ this.thinkingBlockIndex = this.textBlockIndex;
1842
+ this.output.content.splice(this.thinkingBlockIndex, 0, { type: "thinking", thinking: "" });
1843
+ this.textBlockIndex = this.textBlockIndex + 1;
1844
+ } else {
1845
+ this.thinkingBlockIndex = this.output.content.length;
1846
+ this.output.content.push({ type: "thinking", thinking: "" });
1847
+ }
1848
+ this.stream.push({
1849
+ type: "thinking_start",
1850
+ contentIndex: this.thinkingBlockIndex,
1851
+ partial: this.output
1852
+ });
1853
+ }
1854
+ const block = this.output.content[this.thinkingBlockIndex];
1855
+ if (!block)
1856
+ return;
1857
+ block.thinking += thinking;
1858
+ this.stream.push({
1859
+ type: "thinking_delta",
1860
+ contentIndex: this.thinkingBlockIndex,
1861
+ delta: thinking,
1862
+ partial: this.output
1863
+ });
1864
+ }
1865
+ }
1866
+
1867
+ // src/tokenizer.ts
1868
+ function countTokens(text) {
1869
+ if (!text)
1870
+ return 0;
1871
+ return Math.ceil(text.length / 4);
1872
+ }
1873
+
1874
+ // src/transform.ts
1875
+ import { createHash } from "node:crypto";
1876
+ function normalizeMessages(messages) {
1877
+ return messages.filter((msg) => msg.role !== "assistant" || msg.stopReason !== "error" && msg.stopReason !== "aborted");
1878
+ }
1879
+ var TOOL_RESULT_LIMIT = 250000;
1880
+ var MAX_KIRO_IMAGES = 4;
1881
+ var MAX_KIRO_IMAGE_BYTES = 3750000;
1882
+ function truncate(text, limit) {
1883
+ if (text.length <= limit)
1884
+ return text;
1885
+ const half = Math.floor(limit / 2);
1886
+ return `${text.substring(0, half)}
1887
+ ... [TRUNCATED] ...
1888
+ ${text.substring(text.length - half)}`;
1889
+ }
1890
+ function extractImages(msg) {
1891
+ if (msg.role === "toolResult" || typeof msg.content === "string")
1892
+ return [];
1893
+ if (!Array.isArray(msg.content))
1894
+ return [];
1895
+ return msg.content.filter((c) => c.type === "image");
1896
+ }
1897
+ function getContentText(msg) {
1898
+ if (msg.role === "toolResult") {
1899
+ return msg.content.map((c) => c.type === "text" ? c.text : "").join("");
1900
+ }
1901
+ if (typeof msg.content === "string")
1902
+ return msg.content;
1903
+ if (!Array.isArray(msg.content))
1904
+ return "";
1905
+ return msg.content.map((c) => {
1906
+ if (c.type === "text")
1907
+ return c.text;
1908
+ if (c.type === "thinking")
1909
+ return c.thinking;
1910
+ return "";
1911
+ }).join("");
1912
+ }
1913
+ function parseToolArgs(input) {
1914
+ if (input && typeof input === "object")
1915
+ return input;
1916
+ if (typeof input !== "string")
1917
+ return {};
1918
+ try {
1919
+ return JSON.parse(input);
1920
+ } catch {
1921
+ return {};
1922
+ }
1923
+ }
1924
+ var KIRO_TOOL_USE_ID_RE = /^tooluse_[A-Za-z0-9]+$/;
1925
+ function toKiroToolUseId(id) {
1926
+ if (KIRO_TOOL_USE_ID_RE.test(id))
1927
+ return id;
1928
+ const digest = createHash("sha256").update(id).digest("hex").slice(0, 22);
1929
+ return `tooluse_${digest}`;
1930
+ }
1931
+ function convertImagesToKiro(images) {
1932
+ let omitted = 0;
1933
+ const valid = [];
1934
+ for (const img of images) {
1935
+ const estimatedBytes = Math.ceil(img.data.length * 3 / 4);
1936
+ if (estimatedBytes > MAX_KIRO_IMAGE_BYTES) {
1937
+ omitted++;
1938
+ continue;
1939
+ }
1940
+ if (valid.length >= MAX_KIRO_IMAGES) {
1941
+ omitted++;
1942
+ continue;
1943
+ }
1944
+ valid.push({
1945
+ format: img.mimeType.split("/")[1] || "png",
1946
+ source: { bytes: img.data }
1947
+ });
1948
+ }
1949
+ return { images: valid, omitted };
1950
+ }
1951
+ function buildHistory(messages, _modelId, systemPrompt) {
1952
+ const history = [];
1953
+ let systemPrepended = false;
1954
+ let currentMsgStartIdx = messages.length - 1;
1955
+ while (currentMsgStartIdx > 0 && messages[currentMsgStartIdx]?.role === "toolResult") {
1956
+ currentMsgStartIdx--;
1957
+ }
1958
+ const anchor = messages[currentMsgStartIdx];
1959
+ if (anchor?.role === "assistant") {
1960
+ const hasToolCall = Array.isArray(anchor.content) && anchor.content.some((b) => b.type === "toolCall");
1961
+ if (!hasToolCall)
1962
+ currentMsgStartIdx++;
1963
+ }
1964
+ const historyMessages = messages.slice(0, currentMsgStartIdx);
1965
+ for (let i = 0;i < historyMessages.length; i++) {
1966
+ const msg = historyMessages[i];
1967
+ if (!msg)
1968
+ continue;
1969
+ if (msg.role === "user") {
1970
+ let content = typeof msg.content === "string" ? msg.content : getContentText(msg);
1971
+ if (systemPrompt && !systemPrepended) {
1972
+ content = `${systemPrompt}
1973
+
1974
+ ${content}`;
1975
+ systemPrepended = true;
1976
+ }
1977
+ const images = extractImages(msg);
1978
+ const uim = {
1979
+ content,
1980
+ origin: "KIRO_CLI",
1981
+ ...images.length > 0 ? { images: convertImagesToKiro(images).images } : {}
1982
+ };
1983
+ const prev2 = history[history.length - 1];
1984
+ if (prev2?.userInputMessage) {
1985
+ prev2.userInputMessage.content += `
1986
+
1987
+ ${uim.content}`;
1988
+ if (uim.images) {
1989
+ prev2.userInputMessage.images = [...prev2.userInputMessage.images ?? [], ...uim.images];
1928
1990
  }
1929
- const reader = response.body?.getReader();
1930
- if (!reader)
1931
- throw new Error("No response body");
1932
- const decoder = new TextDecoder;
1933
- let buffer = "";
1934
- let totalContent = "";
1935
- let lastContentData = "";
1936
- let usageEvent = null;
1937
- let receivedContextUsage = false;
1938
- let chunkSeq = 0;
1939
- let eventSeq = 0;
1940
- const thinkingParser = thinkingEnabled ? new ThinkingTagParser(output, stream) : null;
1941
- let textBlockIndex = null;
1942
- let emittedToolCalls = 0;
1943
- let sawAnyToolCalls = false;
1944
- let currentToolCall = null;
1945
- const flushToolCall = () => {
1946
- if (!currentToolCall)
1947
- return;
1948
- if (emitToolCall(currentToolCall, output, stream))
1949
- emittedToolCalls++;
1950
- currentToolCall = null;
1951
- };
1952
- const cancelHiddenShim = () => {
1953
- if (hiddenShimTimer) {
1954
- clearTimeout(hiddenShimTimer);
1955
- hiddenShimTimer = null;
1991
+ } else {
1992
+ history.push({ userInputMessage: uim });
1993
+ }
1994
+ continue;
1995
+ }
1996
+ if (msg.role === "assistant") {
1997
+ let armContent = "";
1998
+ const armToolUses = [];
1999
+ if (Array.isArray(msg.content)) {
2000
+ for (const block of msg.content) {
2001
+ if (block.type === "text") {
2002
+ armContent += block.text;
2003
+ } else if (block.type === "thinking") {
2004
+ armContent = `<thinking>${block.thinking}</thinking>
2005
+
2006
+ ${armContent}`;
2007
+ } else if (block.type === "toolCall") {
2008
+ const tc = block;
2009
+ armToolUses.push({
2010
+ name: tc.name,
2011
+ toolUseId: toKiroToolUseId(tc.id),
2012
+ input: parseToolArgs(tc.arguments)
2013
+ });
1956
2014
  }
1957
- };
1958
- let idleTimer = null;
1959
- let idleCancelled = false;
1960
- const resetIdle = () => {
1961
- if (idleTimer)
1962
- clearTimeout(idleTimer);
1963
- idleTimer = setTimeout(() => {
1964
- idleCancelled = true;
1965
- reader.cancel().catch(() => {});
1966
- }, IDLE_TIMEOUT_MS);
1967
- };
1968
- let gotFirstToken = false;
1969
- let firstTokenTimedOut = false;
1970
- let streamError = null;
1971
- const FIRST_TOKEN_SENTINEL = Symbol("firstTokenTimeout");
1972
- while (true) {
1973
- let readResult;
1974
- if (!gotFirstToken) {
1975
- const readPromise = reader.read();
1976
- let firstTokenTimer = null;
1977
- const result = await Promise.race([
1978
- readPromise,
1979
- new Promise((resolve2) => {
1980
- firstTokenTimer = setTimeout(() => resolve2(FIRST_TOKEN_SENTINEL), firstTokenTimeoutForModel(model.id));
1981
- })
1982
- ]);
1983
- if (firstTokenTimer)
1984
- clearTimeout(firstTokenTimer);
1985
- if (result === FIRST_TOKEN_SENTINEL) {
1986
- readPromise.catch(() => {});
1987
- reader.cancel().catch(() => {});
1988
- firstTokenTimedOut = true;
1989
- break;
1990
- }
1991
- readResult = result;
1992
- gotFirstToken = true;
1993
- resetIdle();
1994
- } else {
1995
- readResult = await reader.read();
1996
- }
1997
- const { done, value } = readResult;
1998
- if (done)
1999
- break;
2000
- const decoded = decoder.decode(value, { stream: true });
2001
- buffer += decoded;
2002
- if (log.isDebug()) {
2003
- log.debug("stream.chunk", {
2004
- seq: chunkSeq++,
2005
- bytes: value?.byteLength ?? 0,
2006
- decodedLen: decoded.length,
2007
- preview: previewChunk(decoded)
2008
- });
2009
- }
2010
- const { events, remaining } = parseKiroEvents(buffer);
2011
- buffer = remaining;
2012
- if (events.length > 0)
2013
- resetIdle();
2014
- if (log.isDebug() && events.length > 0) {
2015
- for (const ev of events) {
2016
- log.debug("stream.event", { seq: eventSeq++, event: ev });
2017
- }
2018
- }
2019
- for (const event of events) {
2020
- switch (event.type) {
2021
- case "contextUsage": {
2022
- const pct = event.data.contextUsagePercentage;
2023
- output.usage.input = pct >= COMPACTION_THRESHOLD_PCT ? model.contextWindow + 1 : Math.round(pct / 100 * model.contextWindow);
2024
- receivedContextUsage = true;
2025
- log.debug("contextUsage", { pct, threshold: COMPACTION_THRESHOLD_PCT, willCompact: pct >= COMPACTION_THRESHOLD_PCT });
2026
- break;
2027
- }
2028
- case "reasoning": {
2029
- cancelHiddenShim();
2030
- if (output.content.length === 0 || output.content[output.content.length - 1]?.type !== "thinking") {
2031
- output.content.push({ type: "thinking", thinking: "" });
2032
- stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
2033
- }
2034
- const contentIndex = output.content.length - 1;
2035
- const tc = output.content[contentIndex];
2036
- if (event.data.text) {
2037
- tc.thinking += event.data.text;
2038
- stream.push({ type: "thinking_delta", contentIndex, delta: event.data.text, partial: output });
2039
- }
2040
- if (event.data.signature) {
2041
- tc.thinkingSignature = event.data.signature;
2042
- }
2043
- break;
2044
- }
2045
- case "content": {
2046
- if (event.data === lastContentData)
2047
- continue;
2048
- lastContentData = event.data;
2049
- totalContent += event.data;
2050
- cancelHiddenShim();
2051
- if (thinkingParser) {
2052
- thinkingParser.processChunk(event.data);
2053
- } else {
2054
- if (textBlockIndex === null) {
2055
- textBlockIndex = output.content.length;
2056
- output.content.push({ type: "text", text: "" });
2057
- stream.push({ type: "text_start", contentIndex: textBlockIndex, partial: output });
2058
- }
2059
- const block = output.content[textBlockIndex];
2060
- if (block) {
2061
- block.text += event.data;
2062
- stream.push({
2063
- type: "text_delta",
2064
- contentIndex: textBlockIndex,
2065
- delta: event.data,
2066
- partial: output
2067
- });
2068
- }
2069
- }
2070
- break;
2071
- }
2072
- case "toolUse": {
2073
- const tc = event.data;
2074
- cancelHiddenShim();
2075
- sawAnyToolCalls = true;
2076
- if (!currentToolCall || currentToolCall.toolUseId !== tc.toolUseId) {
2077
- flushToolCall();
2078
- currentToolCall = { toolUseId: tc.toolUseId, name: tc.name, input: "" };
2079
- }
2080
- currentToolCall.input += tc.input || "";
2081
- if (tc.input)
2082
- totalContent += tc.input;
2083
- if (tc.stop)
2084
- flushToolCall();
2085
- break;
2086
- }
2087
- case "toolUseInput": {
2088
- if (currentToolCall)
2089
- currentToolCall.input += event.data.input || "";
2090
- if (event.data.input)
2091
- totalContent += event.data.input;
2092
- break;
2093
- }
2094
- case "toolUseStop": {
2095
- if (event.data.stop)
2096
- flushToolCall();
2097
- break;
2098
- }
2099
- case "usage": {
2100
- usageEvent = event.data;
2101
- break;
2102
- }
2103
- case "error": {
2104
- streamError = event.data.message ? `${event.data.error}: ${event.data.message}` : event.data.error;
2105
- reader.cancel().catch(() => {});
2106
- break;
2107
- }
2108
- }
2109
- if (streamError)
2110
- break;
2111
- }
2112
- }
2113
- if (idleTimer)
2114
- clearTimeout(idleTimer);
2115
- if (firstTokenTimedOut || idleCancelled || streamError) {
2116
- if (retryCount < MAX_RETRIES) {
2117
- retryCount++;
2118
- const delayMs = exponentialBackoff(retryCount - 1, 1000, MAX_RETRY_DELAY_MS);
2119
- log.warn(`stream ${firstTokenTimedOut ? "first-token timed out" : idleCancelled ? "idle timed out" : `error: ${streamError}`} — retrying (${retryCount}/${MAX_RETRIES})`);
2120
- cancelHiddenShim();
2121
- await abortableDelay(delayMs, options?.signal);
2122
- output.content = [];
2123
- textBlockIndex = null;
2124
- continue;
2125
- }
2126
- if (streamError)
2127
- throw new Error(`Kiro API stream error after max retries: ${streamError}`);
2128
- throw new Error(`Kiro API error: ${firstTokenTimedOut ? "first token" : "idle"} timeout after max retries`);
2129
- }
2130
- cancelHiddenShim();
2131
- if (currentToolCall && emitToolCall(currentToolCall, output, stream))
2132
- emittedToolCalls++;
2133
- if (thinkingParser) {
2134
- thinkingParser.finalize();
2135
- textBlockIndex = thinkingParser.getTextBlockIndex();
2136
- }
2137
- if (textBlockIndex !== null) {
2138
- const block = output.content[textBlockIndex];
2139
- if (block) {
2140
- stream.push({
2141
- type: "text_end",
2142
- contentIndex: textBlockIndex,
2143
- content: block.text,
2144
- partial: output
2145
- });
2146
- }
2147
- }
2148
- if (usageEvent?.inputTokens !== undefined)
2149
- output.usage.input = usageEvent.inputTokens;
2150
- output.usage.output = usageEvent?.outputTokens ?? countTokens(totalContent);
2151
- output.usage.totalTokens = output.usage.input + output.usage.output;
2152
- try {
2153
- calculateCost(model, output.usage);
2154
- } catch {
2155
- output.usage.cost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
2156
2015
  }
2157
- const textBlock = textBlockIndex !== null ? output.content[textBlockIndex] : undefined;
2158
- const hasText = !!textBlock && textBlock.text.length > 0;
2159
- if (!hasText && !sawAnyToolCalls) {
2160
- if (retryCount < MAX_RETRIES) {
2161
- retryCount++;
2162
- const delayMs = exponentialBackoff(retryCount - 1, 1000, MAX_RETRY_DELAY_MS);
2163
- log.warn(`empty response — retrying (${retryCount}/${MAX_RETRIES})`);
2164
- cancelHiddenShim();
2165
- output.content = [];
2166
- textBlockIndex = null;
2167
- await abortableDelay(delayMs, options?.signal);
2168
- continue;
2169
- }
2170
- log.warn(`empty response persisted after ${MAX_RETRIES} retries`);
2171
- cancelHiddenShim();
2172
- }
2173
- if (!receivedContextUsage && emittedToolCalls === 0) {
2174
- output.stopReason = "length";
2175
- } else {
2176
- output.stopReason = emittedToolCalls > 0 ? "toolUse" : "stop";
2177
- }
2178
- stream.push({
2179
- type: "done",
2180
- reason: output.stopReason,
2181
- message: output
2182
- });
2183
- log.debug("response.done", {
2184
- stopReason: output.stopReason,
2185
- emittedToolCalls,
2186
- sawAnyToolCalls,
2187
- usage: output.usage
2188
- });
2189
- stream.end();
2190
- return;
2191
2016
  }
2192
- } catch (error) {
2193
- output.stopReason = options?.signal?.aborted ? "aborted" : "error";
2194
- output.errorMessage = error instanceof Error ? error.message : String(error);
2195
- log.debug("response.caught", { stopReason: output.stopReason, error: output.errorMessage });
2196
- if (hiddenShimTimer) {
2197
- clearTimeout(hiddenShimTimer);
2198
- hiddenShimTimer = null;
2017
+ if (!armContent && armToolUses.length === 0)
2018
+ continue;
2019
+ history.push({
2020
+ assistantResponseMessage: {
2021
+ content: armContent,
2022
+ ...armToolUses.length > 0 ? { toolUses: armToolUses } : {}
2023
+ }
2024
+ });
2025
+ continue;
2026
+ }
2027
+ const trMsg = msg;
2028
+ const toolResults = [
2029
+ {
2030
+ content: [{ text: truncate(getContentText(msg), TOOL_RESULT_LIMIT) }],
2031
+ status: trMsg.isError ? "error" : "success",
2032
+ toolUseId: toKiroToolUseId(trMsg.toolCallId)
2199
2033
  }
2200
- stream.push({ type: "error", reason: output.stopReason, error: output });
2201
- stream.end();
2034
+ ];
2035
+ const trImages = [];
2036
+ if (Array.isArray(trMsg.content)) {
2037
+ for (const c of trMsg.content)
2038
+ if (c.type === "image")
2039
+ trImages.push(c);
2202
2040
  }
2203
- })().catch(() => {
2204
- try {
2205
- stream.end();
2206
- } catch {}
2207
- });
2208
- return stream;
2209
- }
2041
+ let j = i + 1;
2042
+ while (j < historyMessages.length && historyMessages[j]?.role === "toolResult") {
2043
+ const next = historyMessages[j];
2044
+ toolResults.push({
2045
+ content: [{ text: truncate(getContentText(next), TOOL_RESULT_LIMIT) }],
2046
+ status: next.isError ? "error" : "success",
2047
+ toolUseId: toKiroToolUseId(next.toolCallId)
2048
+ });
2049
+ if (Array.isArray(next.content)) {
2050
+ for (const c of next.content)
2051
+ if (c.type === "image")
2052
+ trImages.push(c);
2053
+ }
2054
+ j++;
2055
+ }
2056
+ i = j - 1;
2057
+ const prev = history[history.length - 1];
2058
+ if (prev?.userInputMessage) {
2059
+ prev.userInputMessage.content += `
2210
2060
 
2211
- // src/models.ts
2212
- var KIRO_MODEL_IDS = new Set([
2213
- "claude-fable-5",
2214
- "claude-opus-4.8",
2215
- "claude-opus-4.7",
2216
- "claude-opus-4.6",
2217
- "claude-opus-4.6-1m",
2218
- "claude-sonnet-4.6",
2219
- "claude-sonnet-4.6-1m",
2220
- "claude-opus-4.5",
2221
- "claude-sonnet-4.5",
2222
- "claude-sonnet-4.5-1m",
2223
- "claude-sonnet-4",
2224
- "claude-haiku-4.5",
2225
- "deepseek-3.2",
2226
- "kimi-k2.5",
2227
- "minimax-m2.1",
2228
- "minimax-m2.5",
2229
- "glm-4.7",
2230
- "glm-4.7-flash",
2231
- "qwen3-coder-next",
2232
- "agi-nova-beta-1m",
2233
- "qwen3-coder-480b",
2234
- "auto"
2235
- ]);
2236
- function dotToDash(modelId) {
2237
- return modelId.replace(/(\d)\.(\d)/g, "$1-$2");
2238
- }
2239
- function resolveKiroModel(modelId) {
2240
- const kiroId = modelId.replace(/(\d)-(\d)/g, "$1.$2");
2241
- if (!KIRO_MODEL_IDS.has(kiroId)) {
2242
- throw new Error(`Unknown Kiro model ID: ${modelId}`);
2061
+ Tool results provided.`;
2062
+ if (trImages.length > 0) {
2063
+ prev.userInputMessage.images = [
2064
+ ...prev.userInputMessage.images ?? [],
2065
+ ...convertImagesToKiro(trImages).images
2066
+ ];
2067
+ }
2068
+ if (!prev.userInputMessage.userInputMessageContext) {
2069
+ prev.userInputMessage.userInputMessageContext = {};
2070
+ }
2071
+ prev.userInputMessage.userInputMessageContext.toolResults = [
2072
+ ...prev.userInputMessage.userInputMessageContext.toolResults ?? [],
2073
+ ...toolResults
2074
+ ];
2075
+ } else {
2076
+ history.push({
2077
+ userInputMessage: {
2078
+ content: "Tool results provided.",
2079
+ origin: "KIRO_CLI",
2080
+ ...trImages.length > 0 ? { images: convertImagesToKiro(trImages).images } : {},
2081
+ userInputMessageContext: { toolResults }
2082
+ }
2083
+ });
2084
+ }
2243
2085
  }
2244
- return kiroId;
2086
+ return { history: collapseAgenticLoops(history), systemPrepended, currentMsgStartIdx };
2245
2087
  }
2246
- var API_REGION_MAP = {
2247
- "us-west-1": "us-east-1",
2248
- "us-west-2": "us-east-1",
2249
- "us-east-2": "us-east-1",
2250
- "eu-west-1": "eu-central-1",
2251
- "eu-west-2": "eu-central-1",
2252
- "eu-west-3": "eu-central-1",
2253
- "eu-north-1": "eu-central-1",
2254
- "eu-south-1": "eu-central-1",
2255
- "eu-south-2": "eu-central-1",
2256
- "eu-central-2": "eu-central-1",
2257
- "ap-northeast-1": "us-east-1",
2258
- "ap-northeast-2": "us-east-1",
2259
- "ap-northeast-3": "us-east-1",
2260
- "ap-southeast-1": "us-east-1",
2261
- "ap-southeast-2": "us-east-1",
2262
- "ap-south-1": "us-east-1",
2263
- "ap-east-1": "us-east-1",
2264
- "ap-south-2": "us-east-1",
2265
- "ap-southeast-3": "us-east-1",
2266
- "ap-southeast-4": "us-east-1"
2267
- };
2268
- function resolveApiRegion(ssoRegion) {
2269
- if (!ssoRegion)
2270
- return "us-east-1";
2271
- return API_REGION_MAP[ssoRegion] ?? ssoRegion;
2088
+ function collapseAgenticLoops(history) {
2089
+ if (history.length < 4)
2090
+ return history;
2091
+ const result = [];
2092
+ let i = 0;
2093
+ while (i < history.length) {
2094
+ const entry = history[i];
2095
+ if (entry?.assistantResponseMessage?.toolUses && i + 1 < history.length && history[i + 1]?.userInputMessage?.userInputMessageContext?.toolResults) {
2096
+ let j = i;
2097
+ while (j < history.length) {
2098
+ const asst = history[j];
2099
+ if (!asst?.assistantResponseMessage?.toolUses)
2100
+ break;
2101
+ const nextUser = j + 1 < history.length ? history[j + 1] : null;
2102
+ if (!nextUser?.userInputMessage?.userInputMessageContext?.toolResults)
2103
+ break;
2104
+ j += 2;
2105
+ }
2106
+ const pairCount = (j - i) / 2;
2107
+ if (pairCount > 1) {
2108
+ for (let k = i;k < j; k += 2) {
2109
+ const asst = history[k];
2110
+ const user = history[k + 1];
2111
+ if (k === i) {
2112
+ result.push(asst);
2113
+ } else {
2114
+ result.push({
2115
+ assistantResponseMessage: {
2116
+ content: "[tool calling continues]",
2117
+ toolUses: asst.assistantResponseMessage.toolUses
2118
+ }
2119
+ });
2120
+ }
2121
+ result.push(user);
2122
+ }
2123
+ } else {
2124
+ result.push(history[i], history[i + 1]);
2125
+ }
2126
+ i = j;
2127
+ } else {
2128
+ result.push(entry);
2129
+ i++;
2130
+ }
2131
+ }
2132
+ return result;
2272
2133
  }
2273
- var MODELS_BY_REGION = {
2274
- "us-east-1": new Set([
2275
- "claude-fable-5",
2276
- "claude-opus-4-8",
2277
- "claude-opus-4-7",
2278
- "claude-opus-4-6",
2279
- "claude-opus-4-6-1m",
2280
- "claude-sonnet-4-6",
2281
- "claude-sonnet-4-6-1m",
2282
- "claude-opus-4-5",
2283
- "claude-sonnet-4-5",
2284
- "claude-sonnet-4-5-1m",
2285
- "claude-sonnet-4",
2286
- "claude-haiku-4-5",
2287
- "deepseek-3-2",
2288
- "kimi-k2-5",
2289
- "minimax-m2-1",
2290
- "minimax-m2-5",
2291
- "glm-4-7",
2292
- "glm-4-7-flash",
2293
- "qwen3-coder-next",
2294
- "qwen3-coder-480b",
2295
- "agi-nova-beta-1m",
2296
- "auto"
2297
- ]),
2298
- "eu-central-1": new Set([
2299
- "claude-fable-5",
2300
- "claude-opus-4-8",
2301
- "claude-opus-4-7",
2302
- "claude-opus-4-6",
2303
- "claude-sonnet-4-6",
2304
- "claude-opus-4-5",
2305
- "claude-sonnet-4-5",
2306
- "claude-sonnet-4",
2307
- "claude-haiku-4-5",
2308
- "minimax-m2-1",
2309
- "minimax-m2-5",
2310
- "qwen3-coder-next",
2311
- "auto"
2312
- ])
2313
- };
2314
- function filterModelsByRegion(models, apiRegion) {
2315
- const allowed = MODELS_BY_REGION[apiRegion];
2316
- if (!allowed) {
2317
- console.warn(`[pi-kiro] Unknown API region "${apiRegion}" — no models available. Update MODELS_BY_REGION in models.ts.`);
2318
- return [];
2134
+
2135
+ // src/kiro-defaults.ts
2136
+ var SYSTEM_SEED_INSTRUCTION = `Follow this instruction: # Kiro CLI Default Agent
2137
+
2138
+ ` + "You are the default Kiro CLI agent, bringing the power of AI-assisted development " + "directly to the user's terminal. You help with coding tasks, system operations, " + `AWS management, and development workflows.
2139
+
2140
+ ` + `The current model is {{modelId}}.
2141
+ `;
2142
+ var SYSTEM_SEED_ACK = "I will fully incorporate this information when generating my responses, " + "and explicitly acknowledge relevant parts of the summary when answering questions.";
2143
+ function resolveOS() {
2144
+ switch (process.platform) {
2145
+ case "darwin":
2146
+ return "macos";
2147
+ case "win32":
2148
+ return "windows";
2149
+ default:
2150
+ return process.platform;
2319
2151
  }
2320
- return models.filter((m) => allowed.has(m.id));
2321
2152
  }
2322
- var RUNTIME_ENDPOINTS = {
2323
- "us-east-1": "https://runtime.us-east-1.kiro.dev",
2324
- "eu-central-1": "https://runtime.eu-central-1.kiro.dev"
2325
- };
2326
- function resolveRuntimeUrl(apiRegion) {
2327
- return RUNTIME_ENDPOINTS[apiRegion] ?? `https://runtime.${apiRegion}.kiro.dev`;
2153
+ var COMPACTION_THRESHOLD_PCT = 95;
2154
+
2155
+ // src/stream.ts
2156
+ var FIRST_TOKEN_TIMEOUT_DEFAULT_MS = 90000;
2157
+ var IDLE_TIMEOUT_MS = 60000;
2158
+ var MAX_RETRIES = 3;
2159
+ var MAX_RETRY_DELAY_MS = 1e4;
2160
+ var CAPACITY_MAX_RETRIES = 3;
2161
+ var CAPACITY_BASE_DELAY_MS = 5000;
2162
+ var CAPACITY_MAX_DELAY_MS = 30000;
2163
+ var TRANSIENT_MAX_RETRIES = 3;
2164
+ var TRANSIENT_BASE_DELAY_MS = 2000;
2165
+ var TRANSIENT_MAX_DELAY_MS = 15000;
2166
+ var CONTEXT_TRUNCATION_MAX_RETRIES = 3;
2167
+ var CONTEXT_TRUNCATION_DROP_RATIO = 0.3;
2168
+ var TOO_BIG_PATTERNS = ["CONTENT_LENGTH_EXCEEDS_THRESHOLD", "Input is too long", "Improperly formed"];
2169
+ var NON_RETRYABLE_BODY_PATTERNS = ["MONTHLY_REQUEST_COUNT"];
2170
+ var CAPACITY_PATTERN = "INSUFFICIENT_MODEL_CAPACITY";
2171
+ function exponentialBackoff(attempt, baseMs, maxMs) {
2172
+ return Math.min(baseMs * 2 ** attempt, maxMs);
2328
2173
  }
2329
- var BASE_URL = resolveRuntimeUrl("us-east-1");
2330
- var ZERO_COST = Object.freeze({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 });
2331
- var KIRO_DEFAULTS = {
2332
- api: "kiro-api",
2333
- provider: "kiro",
2334
- baseUrl: BASE_URL,
2335
- cost: ZERO_COST
2336
- };
2337
- var MULTIMODAL = ["text", "image"];
2338
- var TEXT_ONLY = ["text"];
2339
- var kiroModels = [
2340
- {
2341
- ...KIRO_DEFAULTS,
2342
- id: "claude-fable-5",
2343
- name: "Claude Fable 5",
2344
- reasoning: true,
2345
- input: MULTIMODAL,
2346
- contextWindow: 1e6,
2347
- maxTokens: 128000,
2348
- firstTokenTimeout: 180000,
2349
- supportedEfforts: ["low", "medium", "high", "xhigh", "max"]
2350
- },
2351
- {
2352
- ...KIRO_DEFAULTS,
2353
- id: "claude-opus-4-8",
2354
- name: "Claude Opus 4.8",
2355
- reasoning: true,
2356
- input: MULTIMODAL,
2357
- contextWindow: 1e6,
2358
- maxTokens: 128000,
2359
- firstTokenTimeout: 180000,
2360
- supportedEfforts: ["low", "medium", "high", "xhigh", "max"]
2361
- },
2362
- {
2363
- ...KIRO_DEFAULTS,
2364
- id: "claude-opus-4-7",
2365
- name: "Claude Opus 4.7",
2366
- reasoning: true,
2367
- input: MULTIMODAL,
2368
- contextWindow: 1e6,
2369
- maxTokens: 128000,
2370
- firstTokenTimeout: 180000,
2371
- supportedEfforts: ["low", "medium", "high", "xhigh", "max"]
2372
- },
2373
- {
2374
- ...KIRO_DEFAULTS,
2375
- id: "claude-opus-4-6",
2376
- name: "Claude Opus 4.6",
2377
- reasoning: true,
2378
- input: MULTIMODAL,
2379
- contextWindow: 1e6,
2380
- maxTokens: 64000,
2381
- supportedEfforts: ["low", "medium", "high", "max"]
2382
- },
2383
- {
2384
- ...KIRO_DEFAULTS,
2385
- id: "claude-opus-4-6-1m",
2386
- name: "Claude Opus 4.6 (1M)",
2387
- reasoning: true,
2388
- input: MULTIMODAL,
2389
- contextWindow: 1e6,
2390
- maxTokens: 64000,
2391
- supportedEfforts: ["low", "medium", "high", "max"]
2392
- },
2393
- {
2394
- ...KIRO_DEFAULTS,
2395
- id: "claude-sonnet-4-6",
2396
- name: "Claude Sonnet 4.6",
2397
- reasoning: true,
2398
- input: MULTIMODAL,
2399
- contextWindow: 1e6,
2400
- maxTokens: 64000,
2401
- supportedEfforts: ["low", "medium", "high", "max"]
2402
- },
2403
- {
2404
- ...KIRO_DEFAULTS,
2405
- id: "claude-sonnet-4-6-1m",
2406
- name: "Claude Sonnet 4.6 (1M)",
2407
- reasoning: true,
2408
- input: MULTIMODAL,
2409
- contextWindow: 1e6,
2410
- maxTokens: 64000,
2411
- supportedEfforts: ["low", "medium", "high", "max"]
2412
- },
2413
- {
2414
- ...KIRO_DEFAULTS,
2415
- id: "claude-opus-4-5",
2416
- name: "Claude Opus 4.5",
2417
- reasoning: true,
2418
- input: MULTIMODAL,
2419
- contextWindow: 200000,
2420
- maxTokens: 64000
2421
- },
2422
- {
2423
- ...KIRO_DEFAULTS,
2424
- id: "claude-sonnet-4-5",
2425
- name: "Claude Sonnet 4.5",
2426
- reasoning: true,
2427
- input: MULTIMODAL,
2428
- contextWindow: 200000,
2429
- maxTokens: 65536
2430
- },
2431
- {
2432
- ...KIRO_DEFAULTS,
2433
- id: "claude-sonnet-4-5-1m",
2434
- name: "Claude Sonnet 4.5 (1M)",
2435
- reasoning: true,
2436
- input: MULTIMODAL,
2437
- contextWindow: 1e6,
2438
- maxTokens: 65536
2439
- },
2440
- {
2441
- ...KIRO_DEFAULTS,
2442
- id: "claude-sonnet-4",
2443
- name: "Claude Sonnet 4",
2444
- reasoning: true,
2445
- input: MULTIMODAL,
2446
- contextWindow: 200000,
2447
- maxTokens: 65536
2448
- },
2449
- {
2450
- ...KIRO_DEFAULTS,
2451
- id: "claude-haiku-4-5",
2452
- name: "Claude Haiku 4.5",
2453
- reasoning: false,
2454
- input: MULTIMODAL,
2455
- contextWindow: 200000,
2456
- maxTokens: 65536
2457
- },
2458
- {
2459
- ...KIRO_DEFAULTS,
2460
- id: "deepseek-3-2",
2461
- name: "DeepSeek 3.2",
2462
- reasoning: true,
2463
- input: TEXT_ONLY,
2464
- contextWindow: 128000,
2465
- maxTokens: 8192
2466
- },
2467
- {
2468
- ...KIRO_DEFAULTS,
2469
- id: "kimi-k2-5",
2470
- name: "Kimi K2.5",
2471
- reasoning: true,
2472
- input: TEXT_ONLY,
2473
- contextWindow: 200000,
2474
- maxTokens: 8192
2475
- },
2476
- {
2477
- ...KIRO_DEFAULTS,
2478
- id: "minimax-m2-5",
2479
- name: "MiniMax M2.5",
2480
- reasoning: false,
2481
- input: TEXT_ONLY,
2482
- contextWindow: 196000,
2483
- maxTokens: 64000
2484
- },
2485
- {
2486
- ...KIRO_DEFAULTS,
2487
- id: "minimax-m2-1",
2488
- name: "MiniMax M2.1",
2489
- reasoning: false,
2490
- input: MULTIMODAL,
2491
- contextWindow: 196000,
2492
- maxTokens: 64000
2493
- },
2494
- {
2495
- ...KIRO_DEFAULTS,
2496
- id: "glm-4-7",
2497
- name: "GLM 4.7",
2498
- reasoning: true,
2499
- input: TEXT_ONLY,
2500
- contextWindow: 128000,
2501
- maxTokens: 8192
2502
- },
2503
- {
2504
- ...KIRO_DEFAULTS,
2505
- id: "glm-4-7-flash",
2506
- name: "GLM 4.7 Flash",
2507
- reasoning: false,
2508
- input: TEXT_ONLY,
2509
- contextWindow: 128000,
2510
- maxTokens: 8192
2511
- },
2512
- {
2513
- ...KIRO_DEFAULTS,
2514
- id: "qwen3-coder-next",
2515
- name: "Qwen3 Coder Next",
2516
- reasoning: true,
2517
- input: MULTIMODAL,
2518
- contextWindow: 256000,
2519
- maxTokens: 64000
2520
- },
2521
- {
2522
- ...KIRO_DEFAULTS,
2523
- id: "qwen3-coder-480b",
2524
- name: "Qwen3 Coder 480B",
2525
- reasoning: true,
2526
- input: TEXT_ONLY,
2527
- contextWindow: 128000,
2528
- maxTokens: 8192
2529
- },
2530
- {
2531
- ...KIRO_DEFAULTS,
2532
- id: "agi-nova-beta-1m",
2533
- name: "AGI Nova Beta (1M)",
2534
- reasoning: true,
2535
- input: MULTIMODAL,
2536
- contextWindow: 1e6,
2537
- maxTokens: 65536
2538
- },
2539
- {
2540
- ...KIRO_DEFAULTS,
2541
- id: "auto",
2542
- name: "Auto",
2543
- reasoning: true,
2544
- input: MULTIMODAL,
2545
- contextWindow: 200000,
2546
- maxTokens: 65536
2547
- }
2548
- ];
2549
- async function fetchAvailableModels(accessToken, apiRegion) {
2550
- const runtimeUrl = `https://runtime.${apiRegion}.kiro.dev/`;
2551
- const profileArn = await resolveProfileArn(accessToken, runtimeUrl);
2552
- if (!profileArn) {
2553
- throw new Error("Missing profileArn: cannot fetch available models.");
2554
- }
2555
- const url = `https://management.${apiRegion}.kiro.dev/ListAvailableModels?origin=KIRO_CLI&profileArn=${encodeURIComponent(profileArn)}`;
2556
- const resp = await fetch(url, {
2557
- method: "GET",
2558
- headers: {
2559
- Authorization: `Bearer ${accessToken}`,
2560
- Accept: "application/json",
2561
- "User-Agent": "pi-kiro"
2562
- }
2563
- });
2564
- if (!resp.ok) {
2565
- throw new Error(`ListAvailableModels failed: HTTP ${resp.status}`);
2566
- }
2567
- const data = await resp.json();
2568
- return (data.models ?? []).filter((m) => m.modelId !== "auto");
2174
+ function isTooBigError(status, body) {
2175
+ return status === 413 || status === 400 && TOO_BIG_PATTERNS.some((p) => body.includes(p));
2569
2176
  }
2570
- var REASONING_FAMILIES = new Set([
2571
- "claude-fable",
2572
- "claude-sonnet",
2573
- "claude-opus",
2574
- "deepseek",
2575
- "kimi",
2576
- "glm",
2577
- "qwen",
2578
- "agi-nova",
2579
- "minimax"
2580
- ]);
2581
- function isReasoningModel(dotId) {
2582
- for (const family of REASONING_FAMILIES) {
2583
- if (dotId.startsWith(family))
2584
- return true;
2585
- }
2586
- return false;
2177
+ function isNonRetryableBodyError(body) {
2178
+ return NON_RETRYABLE_BODY_PATTERNS.some((p) => body.includes(p));
2587
2179
  }
2588
- function firstTokenTimeout(dotId) {
2589
- if (dotId.startsWith("claude-fable") || dotId.startsWith("claude-opus"))
2590
- return 180000;
2591
- return 90000;
2180
+ function isCapacityError(body) {
2181
+ return body.includes(CAPACITY_PATTERN);
2592
2182
  }
2593
- function buildModelsFromApi(apiModels) {
2594
- return apiModels.map((m) => {
2595
- KIRO_MODEL_IDS.add(m.modelId);
2596
- const dashId = dotToDash(m.modelId);
2597
- const supportedTypes = m.supportedInputTypes ?? ["TEXT"];
2598
- const input = supportedTypes.includes("IMAGE") ? ["text", "image"] : ["text"];
2599
- const effortEnum = m.additionalModelRequestFieldsSchema?.properties?.output_config?.properties?.effort?.enum;
2600
- const supportedEfforts = Array.isArray(effortEnum) && effortEnum.length > 0 ? effortEnum : undefined;
2601
- const supportsThinkingConfig = !!m.additionalModelRequestFieldsSchema?.properties?.thinking;
2602
- return {
2603
- id: dashId,
2604
- name: m.modelName,
2605
- reasoning: isReasoningModel(m.modelId),
2606
- input,
2607
- contextWindow: m.tokenLimits?.maxInputTokens ?? 200000,
2608
- maxTokens: m.tokenLimits?.maxOutputTokens ?? 8192,
2609
- firstTokenTimeout: firstTokenTimeout(m.modelId),
2610
- ...supportedEfforts ? { supportedEfforts } : {},
2611
- ...supportsThinkingConfig ? { supportsThinkingConfig } : {}
2612
- };
2613
- });
2183
+ function isTransientError(status) {
2184
+ return status === 429 || status >= 500;
2614
2185
  }
2615
- var cachedDynamicModels = null;
2616
- function getCachedDynamicModels() {
2617
- return cachedDynamicModels;
2186
+ function firstTokenTimeoutForModel(modelId) {
2187
+ const m = kiroModels.find((x) => x.id === modelId);
2188
+ return m?.firstTokenTimeout ?? FIRST_TOKEN_TIMEOUT_DEFAULT_MS;
2618
2189
  }
2619
- function setCachedDynamicModels(models) {
2620
- cachedDynamicModels = models;
2190
+ var HIDDEN_REASONING_PLACEHOLDER = "Reasoning hidden by provider";
2191
+ var HIDDEN_REASONING_COUNTDOWN_MS = 2000;
2192
+ function emitHiddenReasoningLate(output, stream) {
2193
+ const contentIndex = output.content.length;
2194
+ const block = {
2195
+ type: "thinking",
2196
+ thinking: HIDDEN_REASONING_PLACEHOLDER,
2197
+ redacted: true
2198
+ };
2199
+ output.content.push(block);
2200
+ stream.push({ type: "thinking_start", contentIndex, partial: output });
2201
+ stream.push({
2202
+ type: "thinking_delta",
2203
+ contentIndex,
2204
+ delta: HIDDEN_REASONING_PLACEHOLDER,
2205
+ partial: output
2206
+ });
2207
+ stream.push({
2208
+ type: "thinking_end",
2209
+ contentIndex,
2210
+ content: "",
2211
+ partial: output
2212
+ });
2621
2213
  }
2622
-
2623
- // src/oauth.ts
2624
- init_debug();
2625
- var BUILDER_ID_START_URL = "https://view.awsapps.com/start";
2626
- var BUILDER_ID_REGION = "us-east-1";
2627
- var SSO_SCOPES = [
2628
- "codewhisperer:completions",
2629
- "codewhisperer:analysis",
2630
- "codewhisperer:conversations",
2631
- "codewhisperer:transformations",
2632
- "codewhisperer:taskassist"
2633
- ];
2634
- var IDC_PROBE_REGIONS = [
2635
- "us-east-1",
2636
- "eu-west-1",
2637
- "eu-central-1",
2638
- "us-east-2",
2639
- "eu-west-2",
2640
- "eu-west-3",
2641
- "eu-north-1",
2642
- "ap-southeast-1",
2643
- "ap-northeast-1",
2644
- "us-west-2"
2645
- ];
2646
- var EXPIRES_BUFFER_MS = 5 * 60 * 1000;
2647
2214
  function abortableDelay2(ms, signal) {
2648
2215
  if (signal?.aborted)
2649
- return Promise.reject(signal.reason ?? new Error("Login cancelled"));
2216
+ return Promise.reject(signal.reason);
2650
2217
  return new Promise((resolve2, reject) => {
2651
2218
  const timer = setTimeout(resolve2, ms);
2652
2219
  signal?.addEventListener("abort", () => {
2653
2220
  clearTimeout(timer);
2654
- reject(signal.reason ?? new Error("Login cancelled"));
2221
+ reject(signal.reason);
2655
2222
  }, { once: true });
2656
2223
  });
2657
2224
  }
2658
- async function tryRegisterAndAuthorize(startUrl, region) {
2659
- const oidcEndpoint = `https://oidc.${region}.amazonaws.com`;
2660
- const regResp = await fetch(`${oidcEndpoint}/client/register`, {
2661
- method: "POST",
2662
- headers: { "Content-Type": "application/json", "User-Agent": "pi-kiro" },
2663
- body: JSON.stringify({
2664
- clientName: "pi-kiro",
2665
- clientType: "public",
2666
- scopes: SSO_SCOPES,
2667
- grantTypes: ["urn:ietf:params:oauth:grant-type:device_code", "refresh_token"]
2668
- })
2669
- });
2670
- if (!regResp.ok)
2671
- return null;
2672
- const { clientId, clientSecret } = await regResp.json();
2673
- const devResp = await fetch(`${oidcEndpoint}/device_authorization`, {
2674
- method: "POST",
2675
- headers: { "Content-Type": "application/json", "User-Agent": "pi-kiro" },
2676
- body: JSON.stringify({ clientId, clientSecret, startUrl })
2677
- });
2678
- if (!devResp.ok)
2679
- return null;
2680
- return {
2681
- clientId,
2682
- clientSecret,
2683
- oidcEndpoint,
2684
- devAuth: await devResp.json()
2685
- };
2686
- }
2687
- async function pollForToken(oidcEndpoint, clientId, clientSecret, devAuth, signal) {
2688
- const deadline = Date.now() + (devAuth.expiresIn || 600) * 1000;
2689
- const baseInterval = (devAuth.interval || 5) * 1000;
2690
- let interval = baseInterval;
2691
- while (Date.now() < deadline) {
2692
- if (signal?.aborted)
2693
- throw new Error("Login cancelled");
2694
- await abortableDelay2(interval, signal);
2695
- let resp;
2696
- try {
2697
- resp = await fetch(`${oidcEndpoint}/token`, {
2698
- method: "POST",
2699
- headers: { "Content-Type": "application/json", "User-Agent": "pi-kiro" },
2700
- body: JSON.stringify({
2701
- clientId,
2702
- clientSecret,
2703
- deviceCode: devAuth.deviceCode,
2704
- grantType: "urn:ietf:params:oauth:grant-type:device_code"
2705
- })
2706
- });
2707
- } catch {
2708
- continue;
2709
- }
2710
- if (resp.status >= 500)
2711
- continue;
2712
- let data;
2713
- try {
2714
- data = await resp.json();
2715
- } catch {
2716
- if (!resp.ok) {
2717
- throw new Error(`Authorization failed: HTTP ${resp.status}`);
2718
- }
2719
- continue;
2720
- }
2721
- if (!data.error && data.accessToken && data.refreshToken)
2722
- return data;
2723
- if (data.error === "authorization_pending")
2724
- continue;
2725
- if (data.error === "slow_down") {
2726
- interval += baseInterval;
2727
- continue;
2728
- }
2729
- if (data.error)
2730
- throw new Error(`Authorization failed: ${data.error}`);
2731
- }
2732
- throw new Error("Authorization timed out");
2225
+ var profileArnStore = undefined;
2226
+ function seedProfileArn(arn) {
2227
+ profileArnStore = arn;
2733
2228
  }
2734
- async function loginKiro(callbacks) {
2735
- const method = await callbacks.onSelect({
2736
- message: "Select login method:",
2737
- options: [
2738
- { id: "builder-id", label: "AWS Builder ID (personal account)" },
2739
- { id: "idc", label: "IAM Identity Center (enterprise SSO)" },
2740
- { id: "sync", label: "Import from Kiro CLI/IDE (auto-sync local DB)" },
2741
- { id: "desktop", label: "Desktop refresh token (manual)" }
2742
- ]
2743
- });
2744
- if (!method)
2745
- throw new Error("Login cancelled");
2746
- if (method === "sync") {
2747
- return loginCliSync(callbacks);
2748
- }
2749
- if (method === "desktop") {
2750
- return loginDesktopManual(callbacks);
2751
- }
2752
- if (method === "builder-id") {
2753
- return runDeviceCodeFlow(callbacks, BUILDER_ID_START_URL, [BUILDER_ID_REGION], "builder-id");
2754
- }
2755
- const startUrl = (await callbacks.onPrompt({
2756
- message: "Paste your IAM Identity Center start URL:",
2757
- placeholder: "https://mycompany.awsapps.com/start",
2758
- allowEmpty: false
2759
- }))?.trim();
2760
- if (!startUrl || !startUrl.startsWith("http")) {
2761
- throw new Error(`Invalid start URL "${startUrl ?? ""}" — expected https://…`);
2762
- }
2763
- const regionRaw = await callbacks.onPrompt({
2764
- message: `Identity Center region, or blank to auto-detect (${IDC_PROBE_REGIONS.join(", ")})`,
2765
- placeholder: "us-east-1",
2766
- allowEmpty: true
2767
- });
2768
- const region = (regionRaw ?? "").trim();
2769
- const regions = region ? [region] : IDC_PROBE_REGIONS;
2770
- callbacks.onProgress?.(region ? `Connecting to ${region}…` : "Detecting your Identity Center region…");
2771
- return runDeviceCodeFlow(callbacks, startUrl, regions, "idc");
2229
+ function getProfileArn() {
2230
+ return profileArnStore;
2772
2231
  }
2773
- async function loginCliSync(callbacks) {
2774
- callbacks.onProgress?.("Scanning for Kiro IDE credentials (~/.kiro/db)…");
2775
- const { importFromKiroCli: importFromKiroCli2 } = await Promise.resolve().then(() => (init_kiro_cli_sync(), exports_kiro_cli_sync));
2776
- const imported = await importFromKiroCli2();
2777
- if (!imported || !imported.accessToken && !imported.refreshToken) {
2778
- throw new Error(`No Kiro IDE credentials found.
2779
- Make sure Kiro IDE is installed and you're logged in, then try again.
2780
- Alternatively, use 'desktop' to paste a refresh token manually.`);
2781
- }
2782
- log.info("Successfully imported credentials from Kiro IDE");
2783
- callbacks.onProgress?.(`Imported from Kiro IDE (${imported.authMethod}, ${imported.region}${imported.email ? `, ${imported.email}` : ""})`);
2232
+ function emitToolCall(state, output, stream) {
2233
+ if (!state.input.trim())
2234
+ state.input = "{}";
2235
+ let args;
2784
2236
  try {
2785
- const apiRegion = resolveApiRegion(imported.region);
2786
- const apiModels = await fetchAvailableModels(imported.accessToken, apiRegion);
2787
- setCachedDynamicModels(buildModelsFromApi(apiModels));
2788
- log.info(`Fetched and cached ${apiModels.length} models after CLI sync`);
2789
- } catch (err) {
2790
- log.warn(`Failed to fetch models after CLI sync: ${err}`);
2791
- }
2792
- return kiroCredsFromCliImport(imported);
2793
- }
2794
- async function loginDesktopManual(callbacks) {
2795
- const refreshRaw = await callbacks.onPrompt({
2796
- message: `Paste your Kiro desktop refresh token
2797
- ` + "(find it in ~/.kiro/db/kiro.db → auth_kv table):",
2798
- placeholder: "refresh-token",
2799
- allowEmpty: true
2800
- });
2801
- const refreshToken = (refreshRaw ?? "").trim();
2802
- if (!refreshToken) {
2803
- throw new Error("Login cancelled — no refresh token provided");
2804
- }
2805
- const regionRaw = await callbacks.onPrompt({
2806
- message: "Kiro region:",
2807
- placeholder: "us-east-1",
2808
- allowEmpty: true
2809
- });
2810
- const region = (regionRaw ?? "").trim() || "us-east-1";
2811
- const refreshCreds = {
2812
- refresh: `${refreshToken}|||desktop`,
2813
- access: "",
2814
- expires: 0,
2815
- clientId: "",
2816
- clientSecret: "",
2817
- region,
2818
- authMethod: "desktop"
2819
- };
2820
- callbacks.onProgress?.("Exchanging refresh token…");
2821
- return refreshKiroToken(refreshCreds);
2822
- }
2823
- async function runDeviceCodeFlow(callbacks, startUrl, regions, authMethod) {
2824
- let result = null;
2825
- let detectedRegion = "";
2826
- for (const region of regions) {
2827
- result = await tryRegisterAndAuthorize(startUrl, region);
2828
- if (result) {
2829
- detectedRegion = region;
2830
- if (regions.length > 1)
2831
- callbacks.onProgress?.(`Region: ${region}`);
2832
- break;
2237
+ args = JSON.parse(state.input);
2238
+ if (args && typeof args === "object" && "__tool_use_purpose" in args) {
2239
+ delete args.__tool_use_purpose;
2833
2240
  }
2241
+ } catch (e) {
2242
+ log.warn(`failed to parse tool input for "${state.name}" (${state.toolUseId}): ${e instanceof Error ? e.message : String(e)}`);
2243
+ return false;
2834
2244
  }
2835
- if (!result || !detectedRegion) {
2836
- throw new Error(`Could not authorize ${startUrl} in ${regions.join(", ")}. Check your start URL${regions.length === 1 ? " and region" : ""} and try again.`);
2837
- }
2838
- callbacks.onAuth({
2839
- url: result.devAuth.verificationUriComplete,
2840
- instructions: `Code: ${result.devAuth.userCode}
2841
- Complete authorization within 10 minutes.`
2842
- });
2843
- callbacks.onProgress?.("Waiting for browser authorization (up to 10 minutes)…");
2844
- const tok = await pollForToken(result.oidcEndpoint, result.clientId, result.clientSecret, result.devAuth, callbacks.signal);
2845
- if (!tok.accessToken || !tok.refreshToken) {
2846
- throw new Error("Authorization completed but no tokens returned");
2847
- }
2848
- try {
2849
- const apiRegion = resolveApiRegion(detectedRegion);
2850
- const apiModels = await fetchAvailableModels(tok.accessToken, apiRegion);
2851
- setCachedDynamicModels(buildModelsFromApi(apiModels));
2852
- log.info(`Fetched and cached ${apiModels.length} models after login`);
2853
- } catch (err) {
2854
- log.warn(`Failed to fetch models after login, falling back: ${err}`);
2855
- }
2856
- return {
2857
- refresh: `${tok.refreshToken}|${result.clientId}|${result.clientSecret}|${authMethod}`,
2858
- access: tok.accessToken,
2859
- expires: Date.now() + (tok.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS,
2860
- clientId: result.clientId,
2861
- clientSecret: result.clientSecret,
2862
- region: detectedRegion,
2863
- authMethod
2864
- };
2865
- }
2866
- async function syncBackToKiroCli(result) {
2867
- if (result.kiroSyncSource !== "kiro-cli-db" || !result.kiroSyncTokenKey) {
2868
- log.debug("Credential sync-back skipped: credential did not originate from kiro-cli DB");
2869
- return;
2870
- }
2871
- try {
2872
- const { saveKiroCliCredentials: saveKiroCliCredentials2 } = await Promise.resolve().then(() => (init_kiro_cli_sync(), exports_kiro_cli_sync));
2873
- const synced = await saveKiroCliCredentials2({
2874
- accessToken: result.access,
2875
- refreshToken: result.refresh.split("|")[0] ?? "",
2876
- region: result.region,
2877
- authMethod: result.authMethod === "builder-id" ? "idc" : result.authMethod,
2878
- source: result.kiroSyncSource,
2879
- tokenKey: result.kiroSyncTokenKey
2880
- });
2881
- if (synced)
2882
- log.info("Synced refreshed credentials back to Kiro CLI DB");
2883
- } catch (err) {
2884
- log.debug(`Credential sync-back skipped: ${err}`);
2885
- }
2886
- }
2887
- function kiroCredsFromCliImport(imported) {
2888
- const hasOidcCreds = !!imported.clientId && !!imported.clientSecret;
2889
- const authMethod = hasOidcCreds && imported.authMethod === "idc" ? "idc" : "desktop";
2890
- const refreshPacked = hasOidcCreds ? `${imported.refreshToken}|${imported.clientId}|${imported.clientSecret ?? ""}|${authMethod}` : `${imported.refreshToken}|||desktop`;
2891
- return {
2892
- refresh: refreshPacked,
2893
- access: imported.accessToken,
2894
- expires: Date.now() + 3600000 - EXPIRES_BUFFER_MS,
2895
- clientId: imported.clientId ?? "",
2896
- clientSecret: imported.clientSecret ?? "",
2897
- region: imported.region,
2898
- authMethod,
2899
- kiroSyncSource: imported.source,
2900
- kiroSyncTokenKey: imported.tokenKey
2901
- };
2245
+ const contentIndex = output.content.length;
2246
+ const toolCall = { type: "toolCall", id: state.toolUseId, name: state.name, arguments: args };
2247
+ output.content.push(toolCall);
2248
+ stream.push({ type: "toolcall_start", contentIndex, partial: output });
2249
+ stream.push({ type: "toolcall_delta", contentIndex, delta: state.input, partial: output });
2250
+ stream.push({ type: "toolcall_end", contentIndex, toolCall, partial: output });
2251
+ return true;
2902
2252
  }
2903
- async function refreshTokenInner(credentials) {
2904
- const parts = credentials.refresh.split("|");
2905
- const refreshToken = parts[0] ?? "";
2906
- const clientId = parts[1] ?? credentials.clientId ?? "";
2907
- const clientSecret = parts[2] ?? credentials.clientSecret ?? "";
2908
- const region = credentials.region;
2909
- const authMethod = credentials.authMethod;
2910
- if (!refreshToken || !region) {
2911
- throw new Error("Refresh token is missing region — re-login required");
2912
- }
2913
- if (authMethod !== "desktop" && (!clientId || !clientSecret)) {
2914
- throw new Error("Refresh token is missing clientId/clientSecret — re-login required");
2915
- }
2916
- if (authMethod === "desktop") {
2917
- const desktopEndpoint = `https://prod.${region}.auth.desktop.kiro.dev/refreshToken`;
2918
- const resp2 = await fetch(desktopEndpoint, {
2919
- method: "POST",
2920
- headers: { "Content-Type": "application/json", "User-Agent": "pi-kiro" },
2921
- body: JSON.stringify({ refreshToken })
2922
- });
2923
- if (!resp2.ok) {
2924
- const body = await resp2.text().catch(() => "");
2925
- throw new Error(`Desktop token refresh failed: ${resp2.status} ${body}`);
2253
+ function streamKiro(model, context, options) {
2254
+ const stream = createAssistantMessageEventStream();
2255
+ (async () => {
2256
+ const output = {
2257
+ role: "assistant",
2258
+ content: [],
2259
+ api: model.api,
2260
+ provider: model.provider,
2261
+ model: model.id,
2262
+ usage: {
2263
+ input: 0,
2264
+ output: 0,
2265
+ cacheRead: 0,
2266
+ cacheWrite: 0,
2267
+ totalTokens: 0,
2268
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
2269
+ },
2270
+ stopReason: "stop",
2271
+ timestamp: Date.now()
2272
+ };
2273
+ let hiddenShimTimer = null;
2274
+ try {
2275
+ const accessToken = options?.apiKey;
2276
+ if (!accessToken) {
2277
+ throw new Error("Kiro credentials not set. Run /login kiro.");
2278
+ }
2279
+ const endpoint = model.baseUrl || "https://runtime.us-east-1.kiro.dev";
2280
+ const profileArn = getProfileArn();
2281
+ if (!profileArn) {
2282
+ throw new Error("profileArn not resolved. Re-run /login kiro to refresh credentials.");
2283
+ }
2284
+ const kiroModelId = resolveKiroModel(model.id);
2285
+ const thinkingEnabled = !!options?.reasoning || model.reasoning;
2286
+ const reasoningHidden = !!model.reasoningHidden;
2287
+ log.debug("request.init", {
2288
+ endpoint,
2289
+ model: model.id,
2290
+ kiroModelId,
2291
+ contextWindow: model.contextWindow,
2292
+ thinkingEnabled,
2293
+ reasoningHidden,
2294
+ reasoning: options?.reasoning,
2295
+ messageCount: context.messages.length,
2296
+ toolCount: context.tools?.length ?? 0,
2297
+ hasSystemPrompt: !!context.systemPrompt,
2298
+ profileArn,
2299
+ sessionId: options?.sessionId
2300
+ });
2301
+ let systemPrompt = context.systemPrompt ?? "";
2302
+ if (thinkingEnabled && !reasoningHidden) {
2303
+ const budget = options?.reasoning === "xhigh" ? 50000 : options?.reasoning === "high" ? 30000 : options?.reasoning === "medium" ? 20000 : 1e4;
2304
+ systemPrompt = `<thinking_mode>enabled</thinking_mode><max_thinking_length>${budget}</max_thinking_length>${systemPrompt ? `
2305
+ ${systemPrompt}` : ""}`;
2306
+ }
2307
+ const envState = {
2308
+ operatingSystem: resolveOS(),
2309
+ currentWorkingDirectory: process.cwd()
2310
+ };
2311
+ const conversationId = options?.sessionId ?? crypto.randomUUID();
2312
+ let retryCount = 0;
2313
+ while (retryCount <= MAX_RETRIES) {
2314
+ if (options?.signal?.aborted)
2315
+ throw options.signal.reason;
2316
+ const normalized = normalizeMessages(context.messages);
2317
+ const {
2318
+ history,
2319
+ systemPrepended,
2320
+ currentMsgStartIdx
2321
+ } = buildHistory(normalized, kiroModelId, systemPrompt);
2322
+ const seedInstruction = SYSTEM_SEED_INSTRUCTION.replace("{{modelId}}", kiroModelId);
2323
+ const seedPair = [
2324
+ { userInputMessage: { content: seedInstruction, origin: "KIRO_CLI" } },
2325
+ { assistantResponseMessage: { content: SYSTEM_SEED_ACK } }
2326
+ ];
2327
+ history.unshift(...seedPair);
2328
+ const currentMessages = normalized.slice(currentMsgStartIdx);
2329
+ const firstMsg = currentMessages[0];
2330
+ let currentContent = "";
2331
+ const currentToolResults = [];
2332
+ let currentImages;
2333
+ if (firstMsg?.role === "assistant") {
2334
+ const am = firstMsg;
2335
+ let armContent = "";
2336
+ const armToolUses = [];
2337
+ if (Array.isArray(am.content)) {
2338
+ for (const b of am.content) {
2339
+ if (b.type === "text") {
2340
+ armContent += b.text;
2341
+ } else if (b.type === "thinking") {
2342
+ armContent = `<thinking>${b.thinking}</thinking>
2343
+
2344
+ ${armContent}`;
2345
+ } else if (b.type === "toolCall") {
2346
+ const tc = b;
2347
+ armToolUses.push({
2348
+ name: tc.name,
2349
+ toolUseId: toKiroToolUseId(tc.id),
2350
+ input: parseToolArgs(tc.arguments)
2351
+ });
2352
+ }
2353
+ }
2354
+ }
2355
+ if (armContent || armToolUses.length > 0) {
2356
+ const last = history[history.length - 1];
2357
+ if (last && !last.userInputMessage && last.assistantResponseMessage) {
2358
+ last.assistantResponseMessage.content += `
2359
+
2360
+ ${armContent}`;
2361
+ if (armToolUses.length > 0) {
2362
+ last.assistantResponseMessage.toolUses = [
2363
+ ...last.assistantResponseMessage.toolUses ?? [],
2364
+ ...armToolUses
2365
+ ];
2366
+ }
2367
+ } else {
2368
+ history.push({
2369
+ assistantResponseMessage: {
2370
+ content: armContent,
2371
+ ...armToolUses.length > 0 ? { toolUses: armToolUses } : {}
2372
+ }
2373
+ });
2374
+ }
2375
+ }
2376
+ const toolResultImages = [];
2377
+ for (let i = 1;i < currentMessages.length; i++) {
2378
+ const m = currentMessages[i];
2379
+ if (m?.role === "toolResult") {
2380
+ const trm = m;
2381
+ currentToolResults.push({
2382
+ content: [{ text: truncate(getContentText(m), TOOL_RESULT_LIMIT) }],
2383
+ status: trm.isError ? "error" : "success",
2384
+ toolUseId: toKiroToolUseId(trm.toolCallId)
2385
+ });
2386
+ if (Array.isArray(trm.content)) {
2387
+ for (const c of trm.content) {
2388
+ if (c.type === "image")
2389
+ toolResultImages.push(c);
2390
+ }
2391
+ }
2392
+ }
2393
+ }
2394
+ if (toolResultImages.length > 0) {
2395
+ const { images: converted, omitted } = convertImagesToKiro(toolResultImages);
2396
+ if (omitted > 0)
2397
+ log.warn(`${omitted} tool-result image(s) omitted (size/count limit)`);
2398
+ currentImages = currentImages ? [...currentImages, ...converted] : converted;
2399
+ }
2400
+ currentContent = currentToolResults.length > 0 ? "Tool results provided." : "Please proceed with the task.";
2401
+ } else if (firstMsg?.role === "toolResult") {
2402
+ const toolResultImages = [];
2403
+ for (const m of currentMessages) {
2404
+ if (m?.role === "toolResult") {
2405
+ const trm = m;
2406
+ currentToolResults.push({
2407
+ content: [{ text: truncate(getContentText(m), TOOL_RESULT_LIMIT) }],
2408
+ status: trm.isError ? "error" : "success",
2409
+ toolUseId: toKiroToolUseId(trm.toolCallId)
2410
+ });
2411
+ if (Array.isArray(trm.content)) {
2412
+ for (const c of trm.content) {
2413
+ if (c.type === "image")
2414
+ toolResultImages.push(c);
2415
+ }
2416
+ }
2417
+ }
2418
+ }
2419
+ if (toolResultImages.length > 0) {
2420
+ const { images: converted, omitted } = convertImagesToKiro(toolResultImages);
2421
+ if (omitted > 0)
2422
+ log.warn(`${omitted} tool-result image(s) omitted (size/count limit)`);
2423
+ currentImages = currentImages ? [...currentImages, ...converted] : converted;
2424
+ }
2425
+ currentContent = "Tool results provided.";
2426
+ } else if (firstMsg?.role === "user") {
2427
+ currentContent = typeof firstMsg.content === "string" ? firstMsg.content : getContentText(firstMsg);
2428
+ if (systemPrompt && !systemPrepended) {
2429
+ currentContent = `${systemPrompt}
2430
+
2431
+ ${currentContent}`;
2432
+ }
2433
+ }
2434
+ const now = new Date;
2435
+ const tzOffset = -now.getTimezoneOffset();
2436
+ const tzSign = tzOffset >= 0 ? "+" : "-";
2437
+ const tzH = String(Math.floor(Math.abs(tzOffset) / 60)).padStart(2, "0");
2438
+ const tzM = String(Math.abs(tzOffset) % 60).padStart(2, "0");
2439
+ const isoLocal = now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0") + "-" + String(now.getDate()).padStart(2, "0") + "T" + String(now.getHours()).padStart(2, "0") + ":" + String(now.getMinutes()).padStart(2, "0") + ":" + String(now.getSeconds()).padStart(2, "0") + "." + String(now.getMilliseconds()).padStart(3, "0") + tzSign + tzH + ":" + tzM;
2440
+ const weekday = now.toLocaleDateString("en-US", { weekday: "long" });
2441
+ currentContent = `--- CONTEXT ENTRY BEGIN ---
2442
+ ` + `Current time: ${weekday}, ${isoLocal}
2443
+ ` + `--- CONTEXT ENTRY END ---
2444
+
2445
+ ` + `--- USER MESSAGE BEGIN ---
2446
+ ` + `${currentContent}
2447
+ ` + `--- USER MESSAGE END ---`;
2448
+ let uimc = {
2449
+ envState
2450
+ };
2451
+ if (currentToolResults.length > 0)
2452
+ uimc.toolResults = currentToolResults;
2453
+ if (context.tools?.length) {
2454
+ const deepCleanSchema = (obj) => {
2455
+ if (Array.isArray(obj)) {
2456
+ return obj.map(deepCleanSchema);
2457
+ } else if (obj !== null && typeof obj === "object") {
2458
+ const cleaned = {};
2459
+ for (const [k, v] of Object.entries(obj)) {
2460
+ if (k === "$schema")
2461
+ continue;
2462
+ if (k === "required" && Array.isArray(v) && v.length === 0)
2463
+ continue;
2464
+ cleaned[k] = deepCleanSchema(v);
2465
+ }
2466
+ return cleaned;
2467
+ }
2468
+ return obj;
2469
+ };
2470
+ uimc.tools = context.tools.map((t) => {
2471
+ const params = deepCleanSchema(t.parameters);
2472
+ if (params.properties) {
2473
+ params.properties.__tool_use_purpose = {
2474
+ type: "string",
2475
+ description: "A brief explanation why you are making this tool use."
2476
+ };
2477
+ }
2478
+ return {
2479
+ toolSpecification: {
2480
+ name: t.name,
2481
+ description: t.description || `Use ${t.name}`,
2482
+ inputSchema: { json: params }
2483
+ }
2484
+ };
2485
+ });
2486
+ }
2487
+ if (firstMsg?.role === "user") {
2488
+ const imgs = extractImages(firstMsg);
2489
+ if (imgs.length > 0) {
2490
+ const { images: converted, omitted } = convertImagesToKiro(imgs);
2491
+ if (omitted > 0)
2492
+ log.warn(`${omitted} user image(s) omitted (size/count limit)`);
2493
+ currentImages = converted;
2494
+ }
2495
+ }
2496
+ const request = {
2497
+ conversationState: {
2498
+ chatTriggerType: "MANUAL",
2499
+ agentTaskType: "vibe",
2500
+ conversationId,
2501
+ currentMessage: {
2502
+ userInputMessage: {
2503
+ content: currentContent,
2504
+ modelId: kiroModelId,
2505
+ origin: "KIRO_CLI",
2506
+ ...currentImages ? { images: currentImages } : {},
2507
+ userInputMessageContext: uimc
2508
+ }
2509
+ },
2510
+ ...history.length > 0 ? { history } : {}
2511
+ },
2512
+ profileArn,
2513
+ agentMode: "vibe"
2514
+ };
2515
+ const EFFORT_MAP = {
2516
+ minimal: "low",
2517
+ low: "medium",
2518
+ medium: "high",
2519
+ high: "xhigh",
2520
+ xhigh: "max"
2521
+ };
2522
+ const staticModel = kiroModels.find((m) => m.id === model.id);
2523
+ const dynamicModel = getCachedDynamicModels()?.find((m) => m.id === model.id);
2524
+ const supportedEfforts = staticModel?.supportedEfforts ?? dynamicModel?.supportedEfforts;
2525
+ const supportsThinkingConfig = staticModel?.supportsThinkingConfig ?? dynamicModel?.supportsThinkingConfig;
2526
+ if (supportedEfforts && supportedEfforts.length > 0 && options?.reasoning && typeof options.reasoning === "string") {
2527
+ const kiroEffort = EFFORT_MAP[options.reasoning];
2528
+ if (kiroEffort && supportedEfforts.includes(kiroEffort)) {
2529
+ request.additionalModelRequestFields = request.additionalModelRequestFields || {};
2530
+ request.additionalModelRequestFields.output_config = { effort: kiroEffort };
2531
+ log.debug("effort.set", { piReasoning: options.reasoning, kiroEffort, model: model.id });
2532
+ }
2533
+ }
2534
+ if (supportsThinkingConfig && thinkingEnabled) {
2535
+ request.additionalModelRequestFields = request.additionalModelRequestFields || {};
2536
+ request.additionalModelRequestFields.thinking = {
2537
+ type: "adaptive",
2538
+ display: "summarized"
2539
+ };
2540
+ log.debug("thinking.set", { type: "adaptive", display: "summarized", model: model.id });
2541
+ }
2542
+ stream.push({ type: "start", partial: output });
2543
+ if (reasoningHidden && thinkingEnabled && hiddenShimTimer === null) {
2544
+ hiddenShimTimer = setTimeout(() => {
2545
+ hiddenShimTimer = null;
2546
+ emitHiddenReasoningLate(output, stream);
2547
+ }, HIDDEN_REASONING_COUNTDOWN_MS);
2548
+ }
2549
+ let response;
2550
+ let capacityRetryCount = 0;
2551
+ let transientRetryCount = 0;
2552
+ let contextTruncationAttempt = 0;
2553
+ while (true) {
2554
+ const osName = resolveOS();
2555
+ 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`;
2556
+ 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`;
2557
+ const requestBody = JSON.stringify(request);
2558
+ log.debug("request.send", {
2559
+ attempt: retryCount,
2560
+ capacityAttempt: capacityRetryCount,
2561
+ historyLen: history.length,
2562
+ currentContentLen: currentContent.length,
2563
+ hasImages: !!currentImages,
2564
+ toolResultCount: currentToolResults.length,
2565
+ requestJsonChars: requestBody.length
2566
+ });
2567
+ response = await fetch(endpoint, {
2568
+ method: "POST",
2569
+ headers: {
2570
+ "Content-Type": "application/x-amz-json-1.0",
2571
+ Accept: "*/*",
2572
+ "Accept-Encoding": "gzip",
2573
+ Authorization: `Bearer ${accessToken}`,
2574
+ "X-Amz-Target": "AmazonCodeWhispererStreamingService.GenerateAssistantResponse",
2575
+ "x-amzn-codewhisperer-optout": "true",
2576
+ "amz-sdk-invocation-id": crypto.randomUUID(),
2577
+ "amz-sdk-request": "attempt=1; max=3",
2578
+ "user-agent": ua,
2579
+ "x-amz-user-agent": xAmzUa,
2580
+ Pragma: "no-cache",
2581
+ "Cache-Control": "no-cache"
2582
+ },
2583
+ body: requestBody,
2584
+ signal: options?.signal
2585
+ });
2586
+ if (response.ok)
2587
+ break;
2588
+ let errText = "";
2589
+ try {
2590
+ errText = await response.text();
2591
+ } catch {
2592
+ errText = "";
2593
+ }
2594
+ log.debug("response.error", {
2595
+ status: response.status,
2596
+ body: errText
2597
+ });
2598
+ if (isCapacityError(errText) && capacityRetryCount < CAPACITY_MAX_RETRIES) {
2599
+ capacityRetryCount++;
2600
+ const delayMs = exponentialBackoff(capacityRetryCount - 1, CAPACITY_BASE_DELAY_MS, CAPACITY_MAX_DELAY_MS);
2601
+ log.warn(`INSUFFICIENT_MODEL_CAPACITY — retrying in ${delayMs}ms (${capacityRetryCount}/${CAPACITY_MAX_RETRIES})`);
2602
+ await abortableDelay2(delayMs, options?.signal);
2603
+ continue;
2604
+ }
2605
+ if (isNonRetryableBodyError(errText) || isCapacityError(errText)) {
2606
+ throw new Error(`Kiro API error: ${errText || response.statusText}`);
2607
+ }
2608
+ if (isTooBigError(response.status, errText)) {
2609
+ if (contextTruncationAttempt < CONTEXT_TRUNCATION_MAX_RETRIES && history.length > 0) {
2610
+ contextTruncationAttempt++;
2611
+ const dropCount = Math.max(1, Math.floor(history.length * CONTEXT_TRUNCATION_DROP_RATIO));
2612
+ const before = history.length;
2613
+ history.splice(0, dropCount);
2614
+ log.warn(`context too large — truncated history from ${before} to ${history.length} entries ` + `(attempt ${contextTruncationAttempt}/${CONTEXT_TRUNCATION_MAX_RETRIES})`);
2615
+ request.conversationState.history = history.length > 0 ? history : undefined;
2616
+ continue;
2617
+ }
2618
+ throw new Error(`Kiro API error: context_length_exceeded (${response.status} ${errText})`);
2619
+ }
2620
+ if (isTransientError(response.status) && transientRetryCount < TRANSIENT_MAX_RETRIES) {
2621
+ transientRetryCount++;
2622
+ const jitter = Math.floor(Math.random() * 1000);
2623
+ const delayMs = exponentialBackoff(transientRetryCount - 1, TRANSIENT_BASE_DELAY_MS, TRANSIENT_MAX_DELAY_MS) + jitter;
2624
+ log.warn(`transient error ${response.status} — retrying in ${delayMs}ms ` + `(${transientRetryCount}/${TRANSIENT_MAX_RETRIES})`);
2625
+ await abortableDelay2(delayMs, options?.signal);
2626
+ continue;
2627
+ }
2628
+ if (response.status === 401) {
2629
+ const permanent = isPermanentError(errText);
2630
+ if (permanent) {
2631
+ throw new Error(`Kiro API error: credentials permanently invalid — run /login kiro to re-authenticate. ${errText}`);
2632
+ }
2633
+ }
2634
+ if (response.status === 403) {
2635
+ throw new Error(`Kiro API error: access token rejected (403) — run /login kiro to re-authenticate. ${errText}`);
2636
+ }
2637
+ throw new Error(`Kiro API error: ${response.status} ${response.statusText} ${errText}`);
2638
+ }
2639
+ if (capacityRetryCount > 0) {
2640
+ log.info(`recovered from capacity pressure after ${capacityRetryCount} retries`);
2641
+ }
2642
+ if (transientRetryCount > 0) {
2643
+ log.info(`recovered from transient error after ${transientRetryCount} retries`);
2644
+ }
2645
+ if (contextTruncationAttempt > 0) {
2646
+ log.info(`recovered after ${contextTruncationAttempt} context truncation(s)`);
2647
+ }
2648
+ const reader = response.body?.getReader();
2649
+ if (!reader)
2650
+ throw new Error("No response body");
2651
+ const decoder = new TextDecoder;
2652
+ let buffer = "";
2653
+ let totalContent = "";
2654
+ let lastContentData = "";
2655
+ let usageEvent = null;
2656
+ let receivedContextUsage = false;
2657
+ let chunkSeq = 0;
2658
+ let eventSeq = 0;
2659
+ const thinkingParser = thinkingEnabled ? new ThinkingTagParser(output, stream) : null;
2660
+ let textBlockIndex = null;
2661
+ let emittedToolCalls = 0;
2662
+ let sawAnyToolCalls = false;
2663
+ let currentToolCall = null;
2664
+ const flushToolCall = () => {
2665
+ if (!currentToolCall)
2666
+ return;
2667
+ if (emitToolCall(currentToolCall, output, stream))
2668
+ emittedToolCalls++;
2669
+ currentToolCall = null;
2670
+ };
2671
+ const cancelHiddenShim = () => {
2672
+ if (hiddenShimTimer) {
2673
+ clearTimeout(hiddenShimTimer);
2674
+ hiddenShimTimer = null;
2675
+ }
2676
+ };
2677
+ let idleTimer = null;
2678
+ let idleCancelled = false;
2679
+ const resetIdle = () => {
2680
+ if (idleTimer)
2681
+ clearTimeout(idleTimer);
2682
+ idleTimer = setTimeout(() => {
2683
+ idleCancelled = true;
2684
+ reader.cancel().catch(() => {});
2685
+ }, IDLE_TIMEOUT_MS);
2686
+ };
2687
+ let gotFirstToken = false;
2688
+ let firstTokenTimedOut = false;
2689
+ let streamError = null;
2690
+ const FIRST_TOKEN_SENTINEL = Symbol("firstTokenTimeout");
2691
+ while (true) {
2692
+ let readResult;
2693
+ if (!gotFirstToken) {
2694
+ const readPromise = reader.read();
2695
+ let firstTokenTimer = null;
2696
+ const result = await Promise.race([
2697
+ readPromise,
2698
+ new Promise((resolve2) => {
2699
+ firstTokenTimer = setTimeout(() => resolve2(FIRST_TOKEN_SENTINEL), firstTokenTimeoutForModel(model.id));
2700
+ })
2701
+ ]);
2702
+ if (firstTokenTimer)
2703
+ clearTimeout(firstTokenTimer);
2704
+ if (result === FIRST_TOKEN_SENTINEL) {
2705
+ readPromise.catch(() => {});
2706
+ reader.cancel().catch(() => {});
2707
+ firstTokenTimedOut = true;
2708
+ break;
2709
+ }
2710
+ readResult = result;
2711
+ gotFirstToken = true;
2712
+ resetIdle();
2713
+ } else {
2714
+ readResult = await reader.read();
2715
+ }
2716
+ const { done, value } = readResult;
2717
+ if (done)
2718
+ break;
2719
+ const decoded = decoder.decode(value, { stream: true });
2720
+ buffer += decoded;
2721
+ if (log.isDebug()) {
2722
+ log.debug("stream.chunk", {
2723
+ seq: chunkSeq++,
2724
+ bytes: value?.byteLength ?? 0,
2725
+ decodedLen: decoded.length,
2726
+ preview: previewChunk(decoded)
2727
+ });
2728
+ }
2729
+ const { events, remaining } = parseKiroEvents(buffer);
2730
+ buffer = remaining;
2731
+ if (events.length > 0)
2732
+ resetIdle();
2733
+ if (log.isDebug() && events.length > 0) {
2734
+ for (const ev of events) {
2735
+ log.debug("stream.event", { seq: eventSeq++, event: ev });
2736
+ }
2737
+ }
2738
+ for (const event of events) {
2739
+ switch (event.type) {
2740
+ case "contextUsage": {
2741
+ const pct = event.data.contextUsagePercentage;
2742
+ output.usage.input = pct >= COMPACTION_THRESHOLD_PCT ? model.contextWindow + 1 : Math.round(pct / 100 * model.contextWindow);
2743
+ receivedContextUsage = true;
2744
+ log.debug("contextUsage", { pct, threshold: COMPACTION_THRESHOLD_PCT, willCompact: pct >= COMPACTION_THRESHOLD_PCT });
2745
+ break;
2746
+ }
2747
+ case "reasoning": {
2748
+ cancelHiddenShim();
2749
+ if (output.content.length === 0 || output.content[output.content.length - 1]?.type !== "thinking") {
2750
+ output.content.push({ type: "thinking", thinking: "" });
2751
+ stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
2752
+ }
2753
+ const contentIndex = output.content.length - 1;
2754
+ const tc = output.content[contentIndex];
2755
+ if (event.data.text) {
2756
+ tc.thinking += event.data.text;
2757
+ stream.push({ type: "thinking_delta", contentIndex, delta: event.data.text, partial: output });
2758
+ }
2759
+ if (event.data.signature) {
2760
+ tc.thinkingSignature = event.data.signature;
2761
+ }
2762
+ break;
2763
+ }
2764
+ case "content": {
2765
+ if (event.data === lastContentData)
2766
+ continue;
2767
+ lastContentData = event.data;
2768
+ totalContent += event.data;
2769
+ cancelHiddenShim();
2770
+ if (thinkingParser) {
2771
+ thinkingParser.processChunk(event.data);
2772
+ } else {
2773
+ if (textBlockIndex === null) {
2774
+ textBlockIndex = output.content.length;
2775
+ output.content.push({ type: "text", text: "" });
2776
+ stream.push({ type: "text_start", contentIndex: textBlockIndex, partial: output });
2777
+ }
2778
+ const block = output.content[textBlockIndex];
2779
+ if (block) {
2780
+ block.text += event.data;
2781
+ stream.push({
2782
+ type: "text_delta",
2783
+ contentIndex: textBlockIndex,
2784
+ delta: event.data,
2785
+ partial: output
2786
+ });
2787
+ }
2788
+ }
2789
+ break;
2790
+ }
2791
+ case "toolUse": {
2792
+ const tc = event.data;
2793
+ cancelHiddenShim();
2794
+ sawAnyToolCalls = true;
2795
+ if (!currentToolCall || currentToolCall.toolUseId !== tc.toolUseId) {
2796
+ flushToolCall();
2797
+ currentToolCall = { toolUseId: tc.toolUseId, name: tc.name, input: "" };
2798
+ }
2799
+ currentToolCall.input += tc.input || "";
2800
+ if (tc.input)
2801
+ totalContent += tc.input;
2802
+ if (tc.stop)
2803
+ flushToolCall();
2804
+ break;
2805
+ }
2806
+ case "toolUseInput": {
2807
+ if (currentToolCall)
2808
+ currentToolCall.input += event.data.input || "";
2809
+ if (event.data.input)
2810
+ totalContent += event.data.input;
2811
+ break;
2812
+ }
2813
+ case "toolUseStop": {
2814
+ if (event.data.stop)
2815
+ flushToolCall();
2816
+ break;
2817
+ }
2818
+ case "usage": {
2819
+ usageEvent = event.data;
2820
+ break;
2821
+ }
2822
+ case "error": {
2823
+ streamError = event.data.message ? `${event.data.error}: ${event.data.message}` : event.data.error;
2824
+ reader.cancel().catch(() => {});
2825
+ break;
2826
+ }
2827
+ }
2828
+ if (streamError)
2829
+ break;
2830
+ }
2831
+ }
2832
+ if (idleTimer)
2833
+ clearTimeout(idleTimer);
2834
+ if (firstTokenTimedOut || idleCancelled || streamError) {
2835
+ if (retryCount < MAX_RETRIES) {
2836
+ retryCount++;
2837
+ const delayMs = exponentialBackoff(retryCount - 1, 1000, MAX_RETRY_DELAY_MS);
2838
+ log.warn(`stream ${firstTokenTimedOut ? "first-token timed out" : idleCancelled ? "idle timed out" : `error: ${streamError}`} — retrying (${retryCount}/${MAX_RETRIES})`);
2839
+ cancelHiddenShim();
2840
+ await abortableDelay2(delayMs, options?.signal);
2841
+ output.content = [];
2842
+ textBlockIndex = null;
2843
+ continue;
2844
+ }
2845
+ if (streamError)
2846
+ throw new Error(`Kiro API stream error after max retries: ${streamError}`);
2847
+ throw new Error(`Kiro API error: ${firstTokenTimedOut ? "first token" : "idle"} timeout after max retries`);
2848
+ }
2849
+ cancelHiddenShim();
2850
+ if (currentToolCall && emitToolCall(currentToolCall, output, stream))
2851
+ emittedToolCalls++;
2852
+ if (thinkingParser) {
2853
+ thinkingParser.finalize();
2854
+ textBlockIndex = thinkingParser.getTextBlockIndex();
2855
+ }
2856
+ if (textBlockIndex !== null) {
2857
+ const block = output.content[textBlockIndex];
2858
+ if (block) {
2859
+ stream.push({
2860
+ type: "text_end",
2861
+ contentIndex: textBlockIndex,
2862
+ content: block.text,
2863
+ partial: output
2864
+ });
2865
+ }
2866
+ }
2867
+ if (usageEvent?.inputTokens !== undefined)
2868
+ output.usage.input = usageEvent.inputTokens;
2869
+ output.usage.output = usageEvent?.outputTokens ?? countTokens(totalContent);
2870
+ output.usage.totalTokens = output.usage.input + output.usage.output;
2871
+ try {
2872
+ calculateCost(model, output.usage);
2873
+ } catch {
2874
+ output.usage.cost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
2875
+ }
2876
+ const textBlock = textBlockIndex !== null ? output.content[textBlockIndex] : undefined;
2877
+ const hasText = !!textBlock && textBlock.text.length > 0;
2878
+ if (!hasText && !sawAnyToolCalls) {
2879
+ if (retryCount < MAX_RETRIES) {
2880
+ retryCount++;
2881
+ const delayMs = exponentialBackoff(retryCount - 1, 1000, MAX_RETRY_DELAY_MS);
2882
+ log.warn(`empty response — retrying (${retryCount}/${MAX_RETRIES})`);
2883
+ cancelHiddenShim();
2884
+ output.content = [];
2885
+ textBlockIndex = null;
2886
+ await abortableDelay2(delayMs, options?.signal);
2887
+ continue;
2888
+ }
2889
+ log.warn(`empty response persisted after ${MAX_RETRIES} retries`);
2890
+ cancelHiddenShim();
2891
+ }
2892
+ if (!receivedContextUsage && emittedToolCalls === 0) {
2893
+ output.stopReason = "length";
2894
+ } else {
2895
+ output.stopReason = emittedToolCalls > 0 ? "toolUse" : "stop";
2896
+ }
2897
+ stream.push({
2898
+ type: "done",
2899
+ reason: output.stopReason,
2900
+ message: output
2901
+ });
2902
+ log.debug("response.done", {
2903
+ stopReason: output.stopReason,
2904
+ emittedToolCalls,
2905
+ sawAnyToolCalls,
2906
+ usage: output.usage
2907
+ });
2908
+ stream.end();
2909
+ return;
2910
+ }
2911
+ } catch (error) {
2912
+ output.stopReason = options?.signal?.aborted ? "aborted" : "error";
2913
+ output.errorMessage = error instanceof Error ? error.message : String(error);
2914
+ log.debug("response.caught", { stopReason: output.stopReason, error: output.errorMessage });
2915
+ if (hiddenShimTimer) {
2916
+ clearTimeout(hiddenShimTimer);
2917
+ hiddenShimTimer = null;
2918
+ }
2919
+ stream.push({ type: "error", reason: output.stopReason, error: output });
2920
+ stream.end();
2926
2921
  }
2927
- const data2 = await resp2.json();
2922
+ })().catch(() => {
2928
2923
  try {
2929
- const apiRegion = resolveApiRegion(region);
2930
- const apiModels = await fetchAvailableModels(data2.accessToken, apiRegion);
2931
- setCachedDynamicModels(buildModelsFromApi(apiModels));
2932
- log.info(`Fetched and cached ${apiModels.length} models after desktop token refresh`);
2933
- } catch (err) {
2934
- log.warn(`Failed to fetch models after desktop token refresh: ${err}`);
2935
- }
2936
- return {
2937
- refresh: `${data2.refreshToken}|||desktop`,
2938
- access: data2.accessToken,
2939
- expires: Date.now() + (data2.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS,
2940
- clientId: "",
2941
- clientSecret: "",
2942
- region,
2943
- authMethod: "desktop",
2944
- kiroSyncSource: credentials.kiroSyncSource,
2945
- kiroSyncTokenKey: credentials.kiroSyncTokenKey
2946
- };
2947
- }
2948
- const endpoint = `https://oidc.${region}.amazonaws.com/token`;
2949
- const resp = await fetch(endpoint, {
2950
- method: "POST",
2951
- headers: { "Content-Type": "application/json", "User-Agent": "pi-kiro" },
2952
- body: JSON.stringify({ clientId, clientSecret, refreshToken, grantType: "refresh_token" })
2924
+ stream.end();
2925
+ } catch {}
2953
2926
  });
2954
- if (!resp.ok) {
2955
- const body = await resp.text().catch(() => "");
2956
- throw new Error(`Token refresh failed: ${resp.status} ${body}`);
2957
- }
2958
- const data = await resp.json();
2959
- try {
2960
- const apiRegion = resolveApiRegion(region);
2961
- const apiModels = await fetchAvailableModels(data.accessToken, apiRegion);
2962
- setCachedDynamicModels(buildModelsFromApi(apiModels));
2963
- log.info(`Fetched and cached ${apiModels.length} models after token refresh`);
2964
- } catch (err) {
2965
- log.warn(`Failed to fetch models after token refresh, falling back: ${err}`);
2966
- }
2967
- return {
2968
- refresh: `${data.refreshToken}|${clientId}|${clientSecret}|${authMethod}`,
2969
- access: data.accessToken,
2970
- expires: Date.now() + (data.expiresIn ?? 3600) * 1000 - EXPIRES_BUFFER_MS,
2971
- clientId,
2972
- clientSecret,
2973
- region,
2974
- authMethod,
2975
- kiroSyncSource: credentials.kiroSyncSource,
2976
- kiroSyncTokenKey: credentials.kiroSyncTokenKey
2977
- };
2978
- }
2979
- async function refreshKiroToken(credentials) {
2980
- const inputMethod = credentials.authMethod;
2981
- const authMethod = inputMethod === "builder-id" || inputMethod === "idc" || inputMethod === "desktop" ? inputMethod : "idc";
2982
- if (inputMethod !== undefined && inputMethod !== "builder-id" && inputMethod !== "idc" && inputMethod !== "desktop") {
2983
- log.warn(`refreshKiroToken: unrecognized authMethod "${String(inputMethod)}" — defaulting to "idc"`);
2984
- }
2985
- const baseCreds = {
2986
- ...credentials,
2987
- clientId: credentials.clientId ?? credentials.refresh.split("|")[1] ?? "",
2988
- clientSecret: credentials.clientSecret ?? credentials.refresh.split("|")[2] ?? "",
2989
- region: credentials.region,
2990
- authMethod
2991
- };
2992
- const errors = [];
2993
- try {
2994
- log.debug("refresh.cascade: layer 1 — normal refresh");
2995
- const result = await refreshTokenInner(baseCreds);
2996
- syncBackToKiroCli(result);
2997
- return result;
2998
- } catch (err) {
2999
- const msg = err instanceof Error ? err.message : String(err);
3000
- errors.push(`L1(normal): ${msg}`);
3001
- log.warn(`refresh.cascade: layer 1 failed — ${msg}`);
3002
- }
3003
- let freshImport = null;
3004
- try {
3005
- log.debug("refresh.cascade: layer 2 — fresh kiro-cli import");
3006
- const { importFromKiroCli: importFromKiroCli2 } = await Promise.resolve().then(() => (init_kiro_cli_sync(), exports_kiro_cli_sync));
3007
- freshImport = await importFromKiroCli2();
3008
- if (freshImport?.accessToken) {
3009
- const result = kiroCredsFromCliImport(freshImport);
3010
- log.info("refresh.cascade: layer 2 succeeded — using fresh kiro-cli credentials");
3011
- return result;
3012
- }
3013
- errors.push("L2(fresh-import): no valid credentials found");
3014
- log.debug("refresh.cascade: layer 2 — no fresh credentials");
3015
- } catch (err) {
3016
- const msg = err instanceof Error ? err.message : String(err);
3017
- errors.push(`L2(fresh-import): ${msg}`);
3018
- log.warn(`refresh.cascade: layer 2 failed — ${msg}`);
3019
- }
3020
- if (freshImport?.refreshToken) {
3021
- try {
3022
- log.debug("refresh.cascade: layer 3 — refresh fresh kiro-cli creds");
3023
- const freshCreds = kiroCredsFromCliImport(freshImport);
3024
- const result = await refreshTokenInner(freshCreds);
3025
- syncBackToKiroCli(result);
3026
- log.info("refresh.cascade: layer 3 succeeded — refreshed fresh kiro-cli credentials");
3027
- return result;
3028
- } catch (err) {
3029
- const msg = err instanceof Error ? err.message : String(err);
3030
- errors.push(`L3(refresh-fresh): ${msg}`);
3031
- log.warn(`refresh.cascade: layer 3 failed — ${msg}`);
3032
- }
3033
- }
3034
- let expiredImport = null;
3035
- try {
3036
- log.debug("refresh.cascade: layer 4 — expired kiro-cli import");
3037
- const { getKiroCliCredentialsAllowExpired: getKiroCliCredentialsAllowExpired2 } = await Promise.resolve().then(() => (init_kiro_cli_sync(), exports_kiro_cli_sync));
3038
- expiredImport = await getKiroCliCredentialsAllowExpired2(freshImport);
3039
- if (expiredImport?.accessToken) {
3040
- const result = kiroCredsFromCliImport(expiredImport);
3041
- log.info("refresh.cascade: layer 4 succeeded — using expired kiro-cli credentials");
3042
- return result;
3043
- } else {
3044
- errors.push("L4(expired-import): no different expired credentials");
3045
- log.debug("refresh.cascade: layer 4 — no additional expired credentials");
3046
- }
3047
- } catch (err) {
3048
- const msg = err instanceof Error ? err.message : String(err);
3049
- errors.push(`L4(expired-import): ${msg}`);
3050
- log.warn(`refresh.cascade: layer 4 failed — ${msg}`);
3051
- }
3052
- if (expiredImport?.refreshToken) {
3053
- try {
3054
- log.debug("refresh.cascade: layer 5 — refresh expired kiro-cli creds");
3055
- const expiredCreds = kiroCredsFromCliImport(expiredImport);
3056
- const result = await refreshTokenInner(expiredCreds);
3057
- syncBackToKiroCli(result);
3058
- log.info("refresh.cascade: layer 5 succeeded — refreshed expired kiro-cli credentials");
3059
- return result;
3060
- } catch (err) {
3061
- const msg = err instanceof Error ? err.message : String(err);
3062
- errors.push(`L5(refresh-expired): ${msg}`);
3063
- log.warn(`refresh.cascade: layer 5 failed — ${msg}`);
3064
- }
3065
- }
3066
- throw new Error(`Kiro token refresh failed — all 5 cascade layers exhausted. ` + `Re-login required.
3067
- ${errors.join(`
3068
- `)}`);
2927
+ return stream;
3069
2928
  }
3070
2929
 
3071
2930
  // src/extension.ts
@@ -3114,21 +2973,64 @@ function readKiroCredentials() {
3114
2973
  log.warn(`Failed to self-heal auth.json: ${e}`);
3115
2974
  }
3116
2975
  }
2976
+ const metadata = kiro.metadata;
2977
+ const profileArn = [kiro.profileArn, metadata?.profileArn].find((v) => typeof v === "string");
3117
2978
  return {
3118
2979
  access: kiro.access,
3119
- region: kiro.region || "us-east-1"
2980
+ refresh: typeof kiro.refresh === "string" ? kiro.refresh : "",
2981
+ expires: typeof kiro.expires === "number" ? kiro.expires : 0,
2982
+ region: kiro.region || "us-east-1",
2983
+ profileArn,
2984
+ clientId: typeof kiro.clientId === "string" ? kiro.clientId : undefined,
2985
+ clientSecret: typeof kiro.clientSecret === "string" ? kiro.clientSecret : undefined,
2986
+ authMethod: typeof kiro.authMethod === "string" ? kiro.authMethod : undefined
3120
2987
  };
3121
2988
  } catch {
3122
2989
  return null;
3123
2990
  }
3124
2991
  }
2992
+ function writeKiroCredentials(refreshed) {
2993
+ try {
2994
+ const authPath = join2(homedir2(), ".pi", "agent", "auth.json");
2995
+ const raw = existsSync2(authPath) ? readFileSync2(authPath, "utf-8") : "{}";
2996
+ const data = JSON.parse(raw);
2997
+ const existing = data["kiro"] ?? {};
2998
+ data["kiro"] = {
2999
+ ...existing,
3000
+ type: "oauth",
3001
+ access: refreshed.access,
3002
+ refresh: refreshed.refresh,
3003
+ expires: refreshed.expires,
3004
+ clientId: refreshed.clientId,
3005
+ clientSecret: refreshed.clientSecret,
3006
+ region: refreshed.region,
3007
+ authMethod: refreshed.authMethod,
3008
+ profileArn: refreshed.profileArn
3009
+ };
3010
+ writeFileSync(authPath, JSON.stringify(data, null, 2), { mode: 384 });
3011
+ } catch (err) {
3012
+ log.warn(`Failed to persist refreshed credentials: ${err}`);
3013
+ }
3014
+ }
3125
3015
  async function extension_default(pi) {
3126
3016
  let modelDefs = toProviderModels(kiroModels);
3127
3017
  const creds = readKiroCredentials();
3128
- if (creds) {
3018
+ if (creds?.profileArn) {
3019
+ let accessToken = creds.access;
3020
+ if (creds.refresh) {
3021
+ try {
3022
+ log.info("Refreshing token at startup…");
3023
+ const refreshed = await refreshKiroToken(creds);
3024
+ accessToken = refreshed.access;
3025
+ writeKiroCredentials(refreshed);
3026
+ } catch (err) {
3027
+ log.warn(`Startup token refresh failed, trying with existing token: ${err}`);
3028
+ }
3029
+ }
3129
3030
  try {
3130
3031
  const apiRegion = resolveApiRegion(creds.region);
3131
- const apiModels = await fetchAvailableModels(creds.access, apiRegion);
3032
+ seedProfileArn(creds.profileArn);
3033
+ const apiModels = await fetchAvailableModels(accessToken, apiRegion, creds.profileArn);
3132
3034
  const dynamicDefs = buildModelsFromApi(apiModels);
3133
3035
  setCachedDynamicModels(dynamicDefs);
3134
3036
  modelDefs = toProviderModels(dynamicDefs);
@@ -3136,6 +3038,8 @@ async function extension_default(pi) {
3136
3038
  } catch (err) {
3137
3039
  log.warn(`Failed to fetch models at startup, using hardcoded fallback: ${err}`);
3138
3040
  }
3041
+ } else {
3042
+ 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.");
3139
3043
  }
3140
3044
  pi.registerProvider("kiro", {
3141
3045
  baseUrl: "https://runtime.us-east-1.kiro.dev",
@@ -3148,8 +3052,12 @@ async function extension_default(pi) {
3148
3052
  refreshToken: refreshKiroToken,
3149
3053
  getApiKey: (cred) => cred.access,
3150
3054
  modifyModels: (allModels, cred) => {
3151
- const apiRegion = resolveApiRegion(cred.region);
3055
+ const kc = cred;
3056
+ const apiRegion = resolveApiRegion(kc.region);
3152
3057
  const nonKiro = allModels.filter((m) => m.provider !== "kiro");
3058
+ if (kc.profileArn) {
3059
+ seedProfileArn(kc.profileArn);
3060
+ }
3153
3061
  const toKiroModel = (m) => ({
3154
3062
  ...m,
3155
3063
  api: "kiro-api",