@bitkyc08/opencodex 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/bin/ocx.mjs +32 -4
- package/gui/dist/assets/index-CHwf3tTD.css +1 -0
- package/gui/dist/assets/index-u5eFOv2y.js +67 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-normalize.ts +114 -20
- package/src/adapters/anthropic.ts +126 -10
- package/src/adapters/azure.ts +3 -3
- package/src/adapters/base.ts +7 -3
- package/src/adapters/cursor/discovery.ts +14 -4
- package/src/adapters/cursor/effort-map.ts +18 -6
- package/src/adapters/cursor/framing.ts +102 -27
- package/src/adapters/cursor/kv-store.ts +30 -3
- package/src/adapters/cursor/live-models.ts +22 -2
- package/src/adapters/cursor/live-transport.ts +245 -49
- package/src/adapters/cursor/mcp-manager.ts +105 -8
- package/src/adapters/cursor/native-exec-mcp.ts +5 -3
- package/src/adapters/cursor/native-exec-shell.ts +296 -14
- package/src/adapters/cursor/native-exec.ts +381 -33
- package/src/adapters/cursor/protobuf-events.ts +28 -1
- package/src/adapters/cursor/protobuf-request.ts +71 -39
- package/src/adapters/cursor/request-builder.ts +2 -2
- package/src/adapters/cursor/transport.ts +2 -0
- package/src/adapters/cursor.ts +13 -2
- package/src/adapters/google-antigravity-replay.ts +184 -17
- package/src/adapters/google.ts +58 -8
- package/src/adapters/kiro-thinking.ts +23 -9
- package/src/adapters/kiro-tools.ts +49 -18
- package/src/adapters/kiro.ts +377 -133
- package/src/adapters/mimo-free.ts +36 -4
- package/src/adapters/openai-chat.ts +143 -17
- package/src/adapters/openai-responses.ts +130 -14
- package/src/adapters/run-turn-queue.ts +7 -1
- package/src/bridge.ts +466 -69
- package/src/chat/outbound.ts +144 -38
- package/src/claude/inbound-debug.ts +53 -8
- package/src/claude/outbound.ts +224 -38
- package/src/cli/agent-driven.ts +34 -1
- package/src/cli/catalog-prewarm.ts +5 -2
- package/src/cli/claude-desktop.ts +2 -2
- package/src/cli/doctor.ts +12 -0
- package/src/cli/export-command.ts +187 -0
- package/src/cli/help.ts +11 -0
- package/src/cli/index.ts +13 -3
- package/src/cli/init.ts +129 -102
- package/src/cli/opencode.ts +36 -151
- package/src/cli/star-prompt.ts +13 -4
- package/src/cli/status-oauth.ts +12 -2
- package/src/clients/config-export.ts +377 -0
- package/src/codex/account-runtime-state.ts +19 -1
- package/src/codex/account-store.ts +162 -82
- package/src/codex/auth-api.ts +467 -159
- package/src/codex/auth-context.ts +15 -2
- package/src/codex/catalog/aggregation.ts +15 -0
- package/src/codex/catalog/effort.ts +16 -6
- package/src/codex/catalog/metadata.ts +6 -0
- package/src/codex/catalog/parsing.ts +3 -1
- package/src/codex/catalog/provider-fetch.ts +29 -0
- package/src/codex/catalog/sync.ts +64 -7
- package/src/codex/catalog.ts +2 -2
- package/src/codex/inject.ts +5 -5
- package/src/codex/main-account-cache.ts +8 -1
- package/src/codex/model-cache.ts +81 -2
- package/src/codex/pool-rotation.ts +39 -0
- package/src/codex/project-config-warnings.ts +12 -1
- package/src/codex/quota.ts +35 -3
- package/src/codex/routing.ts +46 -1
- package/src/codex/shim.ts +10 -4
- package/src/codex/subagent-model-fallback.ts +12 -0
- package/src/codex/websocket-registry.ts +27 -0
- package/src/combos/failover.ts +31 -1
- package/src/combos/request.ts +9 -0
- package/src/combos/resolve.ts +60 -4
- package/src/combos/types.ts +12 -0
- package/src/config.ts +510 -55
- package/src/github/star-state.ts +13 -1
- package/src/images/fulfill.ts +39 -1
- package/src/images/loop.ts +52 -12
- package/src/lib/admission.ts +83 -0
- package/src/lib/app-owned-memory-stores.ts +173 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bun-stream-caps.ts +31 -7
- package/src/lib/config-ownership.ts +33 -0
- package/src/lib/crash-guard.ts +65 -5
- package/src/lib/debug-log-buffer.ts +47 -6
- package/src/lib/destination-policy.ts +12 -1
- package/src/lib/errors.ts +3 -0
- package/src/lib/gcp-adc.ts +40 -2
- package/src/lib/injection-debug-log.ts +26 -2
- package/src/lib/provider-outbound.ts +3 -0
- package/src/lib/sidecar-tracker.ts +5 -2
- package/src/lib/sse-decoder.ts +257 -37
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/translator-budget.ts +356 -0
- package/src/lib/windows-secret-acl.ts +33 -12
- package/src/lib/winsw.ts +14 -1
- package/src/oauth/anthropic-routing.ts +31 -7
- package/src/oauth/google-antigravity.ts +2 -1
- package/src/oauth/health.ts +30 -12
- package/src/oauth/index.ts +127 -23
- package/src/oauth/kiro-credentials.ts +72 -1
- package/src/oauth/kiro.ts +23 -4
- package/src/oauth/store.ts +165 -18
- package/src/oauth/token-guardian.ts +43 -4
- package/src/oauth/types.ts +2 -1
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/derive.ts +12 -0
- package/src/providers/free-directory.ts +4 -1
- package/src/providers/key-failover.ts +12 -0
- package/src/providers/openai-sidecar.ts +4 -1
- package/src/providers/quota.ts +68 -7
- package/src/providers/registry.ts +279 -3
- package/src/responses/parser.ts +5 -1
- package/src/responses/spill-store.ts +394 -0
- package/src/responses/state.ts +520 -102
- package/src/router.ts +18 -1
- package/src/server/adapter-resolve.ts +20 -3
- package/src/server/auth-cors.ts +121 -28
- package/src/server/chat-completions.ts +57 -12
- package/src/server/claude-messages.ts +85 -13
- package/src/server/index.ts +242 -100
- package/src/server/lifecycle.ts +155 -25
- package/src/server/management/agent-settings-routes.ts +79 -36
- package/src/server/management/api-key-usage.ts +167 -0
- package/src/server/management/body.ts +35 -0
- package/src/server/management/combo-routes.ts +5 -1
- package/src/server/management/config-routes.ts +42 -12
- package/src/server/management/logs-usage-routes.ts +41 -21
- package/src/server/management/model-routes.ts +188 -54
- package/src/server/management/oauth-account-routes.ts +115 -26
- package/src/server/management/provider-routes.ts +56 -6
- package/src/server/management/shared.ts +16 -3
- package/src/server/management/sidebar-routes.ts +50 -1
- package/src/server/management/system-restart.ts +13 -6
- package/src/server/management/system-routes.ts +15 -3
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +39 -5
- package/src/server/management-auth.ts +65 -14
- package/src/server/port-reclaim.ts +58 -12
- package/src/server/ports.ts +2 -0
- package/src/server/proxy-liveness.ts +60 -14
- package/src/server/relay-eager.ts +20 -4
- package/src/server/relay.ts +548 -154
- package/src/server/request-decompress.ts +51 -4
- package/src/server/request-log.ts +134 -15
- package/src/server/responses/collaboration.ts +15 -4
- package/src/server/responses/compact.ts +3 -0
- package/src/server/responses/core.ts +241 -66
- package/src/server/responses-image-gen-repair.ts +19 -5
- package/src/server/responses-item-id-repair.ts +23 -5
- package/src/server/sse-payload-rewrite.ts +71 -12
- package/src/server/startup-health-cache.ts +14 -1
- package/src/server/system-env.ts +8 -1
- package/src/server/windows-tcp-drop.ts +15 -5
- package/src/server/ws-bridge.ts +25 -0
- package/src/service.ts +179 -13
- package/src/storage/policy-job.ts +93 -23
- package/src/storage/policy-worker.ts +6 -0
- package/src/storage/restore-job.ts +62 -16
- package/src/storage/restore-worker.ts +6 -0
- package/src/storage/storage-mutation-coordinator.ts +36 -6
- package/src/storage/worker-lifecycle.ts +181 -47
- package/src/tray/windows.ts +97 -25
- package/src/types.ts +39 -6
- package/src/update/index.ts +24 -5
- package/src/update/job.ts +598 -73
- package/src/usage/log.ts +115 -17
- package/src/usage/summary.ts +67 -2
- package/src/vision/index.ts +112 -22
- package/src/web-search/loop.ts +38 -6
- package/src/web-search/progress-stream.ts +14 -3
- package/gui/dist/assets/index-BDjpkcRN.js +0 -67
- package/gui/dist/assets/index-BHsKRFh9.css +0 -1
package/src/adapters/kiro.ts
CHANGED
|
@@ -17,6 +17,10 @@ import { KiroThinkingParser } from "./kiro-thinking";
|
|
|
17
17
|
import { isCompleteKiroToolInput, kiroTruncationErrorMessage } from "./kiro-truncation";
|
|
18
18
|
import { createKiroToolNameRegistry, fallbackToolUseId, fingerprint, invocationId, isValidKiroConversationId, mapModelId, normalizeToolId, osTag, stableConversationId } from "./kiro-wire";
|
|
19
19
|
import { namespacedToolName } from "../types";
|
|
20
|
+
import {
|
|
21
|
+
isTranslatorBudgetExceededError,
|
|
22
|
+
type TranslatorBudget,
|
|
23
|
+
} from "../lib/translator-budget";
|
|
20
24
|
import type {
|
|
21
25
|
AdapterEvent,
|
|
22
26
|
OcxAssistantMessage,
|
|
@@ -52,6 +56,7 @@ const AMZ_TARGET = "AmazonCodeWhispererStreamingService.GenerateAssistantRespons
|
|
|
52
56
|
const SDK_VERSION = "1.0.27";
|
|
53
57
|
const NODE_VERSION = "22.21.1";
|
|
54
58
|
const KIRO_IDE_VERSION = "1.0.0";
|
|
59
|
+
const KIRO_FALLBACK_SERIALIZATION_ENVELOPE_BYTES = 64 * 1024;
|
|
55
60
|
type KiroWireClient = "ide" | "cli";
|
|
56
61
|
|
|
57
62
|
function kiroCliPlatform(): "linux" | "macos" | "windows" {
|
|
@@ -429,6 +434,10 @@ export function buildKiroPayload(
|
|
|
429
434
|
// Neutralize Codex's GPT-5 identity line so a routed Kiro model never misreports as GPT-5/OpenAI
|
|
430
435
|
// and the proxy identity never leaks upstream.
|
|
431
436
|
if (parsed.context.systemPrompt?.length) systemParts.push(neutralizeIdentity(parsed.context.systemPrompt.join("\n\n")));
|
|
437
|
+
for (const addition of toolContext.systemAdditions) {
|
|
438
|
+
const boundedAddition = boundedInjectedInstruction(addition, injectedChars);
|
|
439
|
+
if (boundedAddition) systemParts.push(boundedAddition);
|
|
440
|
+
}
|
|
432
441
|
const toolCatalogNudge = buildNonOpenAIToolCatalogNudgeFromNames(kiroToolWireNames(kiroTools));
|
|
433
442
|
const boundedNudge = toolCatalogNudge ? boundedInjectedInstruction(toolCatalogNudge, injectedChars) : undefined;
|
|
434
443
|
if (boundedNudge) systemParts.push(boundedNudge);
|
|
@@ -600,7 +609,7 @@ export function buildKiroPayload(
|
|
|
600
609
|
// CodeWhisperer GenerateAssistantResponse ALWAYS returns an AWS eventstream body (there is no
|
|
601
610
|
// non-streaming mode), so both the streaming bridge and the non-streaming web-search sidecar loop
|
|
602
611
|
// decode the same way — parseResponse just collects what parseStream yields.
|
|
603
|
-
interface
|
|
612
|
+
interface KiroAttemptParseResult {
|
|
604
613
|
terminal?: AdapterEvent;
|
|
605
614
|
needsFallback?: boolean;
|
|
606
615
|
usage?: OcxUsage;
|
|
@@ -609,12 +618,93 @@ interface KiroAttemptResult {
|
|
|
609
618
|
sawReasoning: boolean;
|
|
610
619
|
}
|
|
611
620
|
|
|
621
|
+
interface KiroAttemptResult extends KiroAttemptParseResult {
|
|
622
|
+
releaseRetained(): void;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
interface KiroAttemptRetention {
|
|
626
|
+
trackReplacement(previousBytes: number, nextBytes: number): void;
|
|
627
|
+
retainEvent(event: AdapterEvent, bytes: number): void;
|
|
628
|
+
releaseEvent(event: AdapterEvent): void;
|
|
629
|
+
releaseAll(): void;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function createKiroAttemptRetention(budget: TranslatorBudget): KiroAttemptRetention {
|
|
633
|
+
let retainedBytes = 0;
|
|
634
|
+
const eventBytes = new Map<AdapterEvent, number>();
|
|
635
|
+
return {
|
|
636
|
+
trackReplacement(previousBytes, nextBytes) {
|
|
637
|
+
retainedBytes = Math.max(0, retainedBytes - previousBytes) + nextBytes;
|
|
638
|
+
},
|
|
639
|
+
retainEvent(event, bytes) {
|
|
640
|
+
retainedBytes += bytes;
|
|
641
|
+
eventBytes.set(event, bytes);
|
|
642
|
+
},
|
|
643
|
+
releaseEvent(event) {
|
|
644
|
+
const bytes = eventBytes.get(event);
|
|
645
|
+
if (bytes === undefined) return;
|
|
646
|
+
eventBytes.delete(event);
|
|
647
|
+
retainedBytes = Math.max(0, retainedBytes - bytes);
|
|
648
|
+
budget.releaseRetained(bytes, { kind: "retained_collectors" });
|
|
649
|
+
},
|
|
650
|
+
releaseAll() {
|
|
651
|
+
if (retainedBytes > 0) budget.releaseRetained(retainedBytes, { kind: "retained_collectors" });
|
|
652
|
+
retainedBytes = 0;
|
|
653
|
+
eventBytes.clear();
|
|
654
|
+
},
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
612
658
|
interface KiroFallbackAttempt {
|
|
613
659
|
response: Response;
|
|
614
660
|
inputTokens: number;
|
|
615
661
|
contextInputEstimate: number;
|
|
616
662
|
nameMap: Map<string, string>;
|
|
617
663
|
conversationId: string;
|
|
664
|
+
releaseRequestBody?: () => void;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function appendedUtf8Bytes(previous: string, previousBytes: number, fragment: string): number {
|
|
668
|
+
let nextBytes = previousBytes + Buffer.byteLength(fragment);
|
|
669
|
+
const previousLast = previous.charCodeAt(previous.length - 1);
|
|
670
|
+
const fragmentFirst = fragment.charCodeAt(0);
|
|
671
|
+
if (previousLast >= 0xd800 && previousLast <= 0xdbff
|
|
672
|
+
&& fragmentFirst >= 0xdc00 && fragmentFirst <= 0xdfff) {
|
|
673
|
+
nextBytes -= 2;
|
|
674
|
+
}
|
|
675
|
+
return nextBytes;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/** Exact UTF-8 size JSON.stringify() will use for a string, without materializing that copy. */
|
|
679
|
+
function jsonStringSerializedUtf8Bytes(value: string): number {
|
|
680
|
+
let bytes = 2; // Opening and closing quotes.
|
|
681
|
+
for (let index = 0; index < value.length; index++) {
|
|
682
|
+
const code = value.charCodeAt(index);
|
|
683
|
+
if (code === 0x22 || code === 0x5c) {
|
|
684
|
+
bytes += 2;
|
|
685
|
+
} else if (code === 0x08 || code === 0x09 || code === 0x0a || code === 0x0c || code === 0x0d) {
|
|
686
|
+
bytes += 2;
|
|
687
|
+
} else if (code < 0x20) {
|
|
688
|
+
bytes += 6;
|
|
689
|
+
} else if (code <= 0x7f) {
|
|
690
|
+
bytes += 1;
|
|
691
|
+
} else if (code <= 0x7ff) {
|
|
692
|
+
bytes += 2;
|
|
693
|
+
} else if (code >= 0xd800 && code <= 0xdbff) {
|
|
694
|
+
const next = value.charCodeAt(index + 1);
|
|
695
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
696
|
+
bytes += 4;
|
|
697
|
+
index++;
|
|
698
|
+
} else {
|
|
699
|
+
bytes += 6;
|
|
700
|
+
}
|
|
701
|
+
} else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
702
|
+
bytes += 6;
|
|
703
|
+
} else {
|
|
704
|
+
bytes += 3;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return bytes;
|
|
618
708
|
}
|
|
619
709
|
|
|
620
710
|
interface KiroContextWindowState {
|
|
@@ -625,6 +715,7 @@ type KiroFallbackFactory = (
|
|
|
625
715
|
conversationId: string | undefined,
|
|
626
716
|
assistantText: string,
|
|
627
717
|
sawReasoning: boolean,
|
|
718
|
+
budget: TranslatorBudget,
|
|
628
719
|
) => Promise<KiroFallbackAttempt>;
|
|
629
720
|
|
|
630
721
|
function mergeKiroUsage(
|
|
@@ -708,6 +799,7 @@ const KIRO_END_TURN_STOP_REASON = "END_TURN";
|
|
|
708
799
|
|
|
709
800
|
async function* parseKiroAttempt(
|
|
710
801
|
response: Response,
|
|
802
|
+
budget: TranslatorBudget,
|
|
711
803
|
mode: KiroCompletionMode,
|
|
712
804
|
modelId: string | undefined,
|
|
713
805
|
inputTokens: number,
|
|
@@ -721,8 +813,10 @@ async function* parseKiroAttempt(
|
|
|
721
813
|
// `required` mode holds staged commentary until a real tool call or terminal metadata identifies
|
|
722
814
|
// the attempt boundary. Anything the inner parser leaves behind is flushed before the terminal.
|
|
723
815
|
const deferred: AdapterEvent[] = [];
|
|
816
|
+
const retention = createKiroAttemptRetention(budget);
|
|
724
817
|
const attempt = parseKiroAttemptEvents(
|
|
725
818
|
response,
|
|
819
|
+
budget,
|
|
726
820
|
mode,
|
|
727
821
|
modelId,
|
|
728
822
|
inputTokens,
|
|
@@ -730,20 +824,30 @@ async function* parseKiroAttempt(
|
|
|
730
824
|
nameMap,
|
|
731
825
|
conversationId,
|
|
732
826
|
deferred,
|
|
827
|
+
retention,
|
|
733
828
|
contextInputEstimate,
|
|
734
829
|
priorEmittedOutput,
|
|
735
830
|
);
|
|
736
|
-
let
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
next
|
|
831
|
+
let handedOff = false;
|
|
832
|
+
try {
|
|
833
|
+
let next = await attempt.next();
|
|
834
|
+
while (!next.done) {
|
|
835
|
+
yield next.value;
|
|
836
|
+
next = await attempt.next();
|
|
837
|
+
}
|
|
838
|
+
for (const event of deferred.splice(0)) {
|
|
839
|
+
try { yield event; } finally { retention.releaseEvent(event); }
|
|
840
|
+
}
|
|
841
|
+
handedOff = true;
|
|
842
|
+
return { ...next.value, releaseRetained: () => retention.releaseAll() };
|
|
843
|
+
} finally {
|
|
844
|
+
if (!handedOff) retention.releaseAll();
|
|
740
845
|
}
|
|
741
|
-
for (const event of deferred.splice(0)) yield event;
|
|
742
|
-
return next.value;
|
|
743
846
|
}
|
|
744
847
|
|
|
745
848
|
async function* parseKiroAttemptEvents(
|
|
746
849
|
response: Response,
|
|
850
|
+
budget: TranslatorBudget,
|
|
747
851
|
mode: KiroCompletionMode,
|
|
748
852
|
modelId: string | undefined,
|
|
749
853
|
inputTokens: number,
|
|
@@ -751,10 +855,11 @@ async function* parseKiroAttemptEvents(
|
|
|
751
855
|
nameMap: Map<string, string> | undefined,
|
|
752
856
|
conversationId: string | undefined,
|
|
753
857
|
deferred: AdapterEvent[],
|
|
858
|
+
retention: KiroAttemptRetention,
|
|
754
859
|
contextInputEstimate?: number,
|
|
755
860
|
priorEmittedOutput = false,
|
|
756
|
-
): AsyncGenerator<AdapterEvent,
|
|
757
|
-
const emptyResult = ():
|
|
861
|
+
): AsyncGenerator<AdapterEvent, KiroAttemptParseResult> {
|
|
862
|
+
const emptyResult = (): KiroAttemptParseResult => ({ assistantText: "", sawReasoning: false });
|
|
758
863
|
if (!response.body) {
|
|
759
864
|
return {
|
|
760
865
|
...emptyResult(),
|
|
@@ -764,9 +869,11 @@ async function* parseKiroAttemptEvents(
|
|
|
764
869
|
|
|
765
870
|
let open: { id: string; name: string; chunks: string[]; completion: boolean } | null = null;
|
|
766
871
|
let outputChars = "";
|
|
872
|
+
let outputCharsBytes = 0;
|
|
767
873
|
let contextUsagePercentage: number | undefined;
|
|
768
874
|
let returnedConversationId = conversationId;
|
|
769
875
|
let assistantText = "";
|
|
876
|
+
let assistantTextBytes = 0;
|
|
770
877
|
let sawText = false;
|
|
771
878
|
let sawReasoning = false;
|
|
772
879
|
let sawRealTool = false;
|
|
@@ -775,7 +882,19 @@ async function* parseKiroAttemptEvents(
|
|
|
775
882
|
let authoritativeUsage: OcxUsage | undefined;
|
|
776
883
|
let stopReason: string | undefined;
|
|
777
884
|
const fallbackEvents: AdapterEvent[] = [];
|
|
778
|
-
const thinking = new KiroThinkingParser();
|
|
885
|
+
const thinking = new KiroThinkingParser(budget);
|
|
886
|
+
|
|
887
|
+
const retainedEventBytes = (event: AdapterEvent): number => Buffer.byteLength(JSON.stringify(event));
|
|
888
|
+
const retainEvent = (event: AdapterEvent): void => {
|
|
889
|
+
const bytes = retainedEventBytes(event);
|
|
890
|
+
budget.chargeRetained(bytes, { kind: "retained_collectors" });
|
|
891
|
+
retention.retainEvent(event, bytes);
|
|
892
|
+
};
|
|
893
|
+
const emitRetained = async function* (events: Iterable<AdapterEvent>): AsyncGenerator<AdapterEvent> {
|
|
894
|
+
for (const event of events) {
|
|
895
|
+
try { yield event; } finally { retention.releaseEvent(event); }
|
|
896
|
+
}
|
|
897
|
+
};
|
|
779
898
|
|
|
780
899
|
const providerState = (): { kiro: { conversationId: string } } | undefined =>
|
|
781
900
|
returnedConversationId ? { kiro: { conversationId: returnedConversationId } } : undefined;
|
|
@@ -889,19 +1008,33 @@ async function* parseKiroAttemptEvents(
|
|
|
889
1008
|
if (sawRealTool) return [...deferred.splice(0), event];
|
|
890
1009
|
if (event.type !== "text_delta" && deferred.length === 0) return [event];
|
|
891
1010
|
deferred.push(event);
|
|
1011
|
+
retainEvent(event);
|
|
892
1012
|
return [{ type: "heartbeat" }];
|
|
893
1013
|
};
|
|
894
1014
|
|
|
895
1015
|
const stage = (event: AdapterEvent): AdapterEvent[] => {
|
|
896
1016
|
if (event.type === "text_delta") {
|
|
1017
|
+
const nextAssistantTextBytes = appendedUtf8Bytes(assistantText, assistantTextBytes, event.text);
|
|
1018
|
+
const assistantReservation = budget.reserveTransient(nextAssistantTextBytes, { kind: "retained_collectors" });
|
|
897
1019
|
assistantText += event.text;
|
|
1020
|
+
assistantReservation.commitRetained();
|
|
1021
|
+
budget.releaseRetained(assistantTextBytes, { kind: "retained_collectors" });
|
|
1022
|
+
retention.trackReplacement(assistantTextBytes, nextAssistantTextBytes);
|
|
1023
|
+
assistantTextBytes = nextAssistantTextBytes;
|
|
898
1024
|
if (event.text.trim()) sawText = true;
|
|
1025
|
+
const nextOutputCharsBytes = appendedUtf8Bytes(outputChars, outputCharsBytes, event.text);
|
|
1026
|
+
const outputReservation = budget.reserveTransient(nextOutputCharsBytes, { kind: "retained_collectors" });
|
|
899
1027
|
outputChars += event.text;
|
|
1028
|
+
outputReservation.commitRetained();
|
|
1029
|
+
budget.releaseRetained(outputCharsBytes, { kind: "retained_collectors" });
|
|
1030
|
+
retention.trackReplacement(outputCharsBytes, nextOutputCharsBytes);
|
|
1031
|
+
outputCharsBytes = nextOutputCharsBytes;
|
|
900
1032
|
const phased = mode === "disabled"
|
|
901
1033
|
? event
|
|
902
1034
|
: { ...event, phase: "commentary" as const };
|
|
903
1035
|
if (mode === "text_fallback") {
|
|
904
1036
|
fallbackEvents.push(phased);
|
|
1037
|
+
retainEvent(phased);
|
|
905
1038
|
return [];
|
|
906
1039
|
}
|
|
907
1040
|
return mode === "required" ? defer(phased) : [phased];
|
|
@@ -909,10 +1042,17 @@ async function* parseKiroAttemptEvents(
|
|
|
909
1042
|
if (event.type === "reasoning_raw_delta" || event.type === "thinking_delta") {
|
|
910
1043
|
const text = event.type === "reasoning_raw_delta" ? event.text : event.thinking;
|
|
911
1044
|
if (text.trim()) sawReasoning = true;
|
|
1045
|
+
const nextOutputCharsBytes = appendedUtf8Bytes(outputChars, outputCharsBytes, text);
|
|
1046
|
+
const reasoningReservation = budget.reserveTransient(nextOutputCharsBytes, { kind: "retained_collectors" });
|
|
912
1047
|
outputChars += text;
|
|
1048
|
+
reasoningReservation.commitRetained();
|
|
1049
|
+
budget.releaseRetained(outputCharsBytes, { kind: "retained_collectors" });
|
|
1050
|
+
retention.trackReplacement(outputCharsBytes, nextOutputCharsBytes);
|
|
1051
|
+
outputCharsBytes = nextOutputCharsBytes;
|
|
913
1052
|
}
|
|
914
1053
|
if (mode === "text_fallback" && event.type !== "heartbeat") {
|
|
915
1054
|
fallbackEvents.push(event);
|
|
1055
|
+
retainEvent(event);
|
|
916
1056
|
return [];
|
|
917
1057
|
}
|
|
918
1058
|
return mode === "required" ? defer(event) : [event];
|
|
@@ -940,6 +1080,7 @@ async function* parseKiroAttemptEvents(
|
|
|
940
1080
|
if (!open) return { events: [] };
|
|
941
1081
|
const tool = open;
|
|
942
1082
|
open = null;
|
|
1083
|
+
budget.closeCall(tool.id);
|
|
943
1084
|
const input = tool.chunks.join("");
|
|
944
1085
|
if (!isCompleteKiroToolInput(input)) {
|
|
945
1086
|
return { events: [], terminal: protocolTerminal(kiroTruncationErrorMessage("incomplete tool input JSON"), tool.completion) };
|
|
@@ -1018,21 +1159,21 @@ async function* parseKiroAttemptEvents(
|
|
|
1018
1159
|
}
|
|
1019
1160
|
if (ev.data) {
|
|
1020
1161
|
for (const contentEvent of thinking.feed(ev.data)) {
|
|
1021
|
-
|
|
1162
|
+
yield* emitRetained(stage(contentEvent));
|
|
1022
1163
|
}
|
|
1023
1164
|
}
|
|
1024
1165
|
break;
|
|
1025
1166
|
case "reasoning":
|
|
1026
1167
|
for (const contentEvent of thinking.flush()) {
|
|
1027
|
-
|
|
1168
|
+
yield* emitRetained(stage(contentEvent));
|
|
1028
1169
|
}
|
|
1029
1170
|
if (ev.data) {
|
|
1030
|
-
|
|
1171
|
+
yield* emitRetained(stage({ type: "reasoning_raw_delta", text: ev.data }));
|
|
1031
1172
|
}
|
|
1032
1173
|
break;
|
|
1033
1174
|
case "tool": {
|
|
1034
1175
|
for (const contentEvent of thinking.flush()) {
|
|
1035
|
-
|
|
1176
|
+
yield* emitRetained(stage(contentEvent));
|
|
1036
1177
|
}
|
|
1037
1178
|
if (!open) {
|
|
1038
1179
|
if (ev.stop === true) {
|
|
@@ -1044,10 +1185,12 @@ async function* parseKiroAttemptEvents(
|
|
|
1044
1185
|
const started = beginTool(ev.toolUseId, ev.name);
|
|
1045
1186
|
if (started.terminal) return { assistantText, sawReasoning, terminal: started.terminal };
|
|
1046
1187
|
open = started.tool!;
|
|
1188
|
+
budget.openCall(open.id);
|
|
1047
1189
|
} else if (
|
|
1048
1190
|
(ev.toolUseId && ev.toolUseId !== open.id)
|
|
1049
1191
|
|| (ev.name && open.name !== "unknown" && ev.name !== open.name)
|
|
1050
1192
|
) {
|
|
1193
|
+
budget.closeCall(open.id);
|
|
1051
1194
|
open = null;
|
|
1052
1195
|
return { assistantText, sawReasoning, terminal: protocolTerminal(kiroTruncationErrorMessage("tool input changed identity before stop")) };
|
|
1053
1196
|
}
|
|
@@ -1060,14 +1203,25 @@ async function* parseKiroAttemptEvents(
|
|
|
1060
1203
|
}
|
|
1061
1204
|
}
|
|
1062
1205
|
if (open && ev.input !== undefined) {
|
|
1206
|
+
const previousCallBytes = open.chunks.reduce((total, chunk) => total + Buffer.byteLength(chunk), 0);
|
|
1207
|
+
const nextCallBytes = previousCallBytes + Buffer.byteLength(ev.input);
|
|
1208
|
+
const callReservation = budget.reserveTransient(nextCallBytes, { kind: "tool_args", callId: open.id });
|
|
1063
1209
|
open.chunks.push(ev.input);
|
|
1210
|
+
callReservation.commitRetained();
|
|
1211
|
+
budget.releaseRetained(previousCallBytes, { kind: "tool_args", callId: open.id });
|
|
1212
|
+
const nextOutputCharsBytes = appendedUtf8Bytes(outputChars, outputCharsBytes, ev.input);
|
|
1213
|
+
const toolOutputReservation = budget.reserveTransient(nextOutputCharsBytes, { kind: "retained_collectors" });
|
|
1064
1214
|
outputChars += ev.input;
|
|
1215
|
+
toolOutputReservation.commitRetained();
|
|
1216
|
+
budget.releaseRetained(outputCharsBytes, { kind: "retained_collectors" });
|
|
1217
|
+
retention.trackReplacement(outputCharsBytes, nextOutputCharsBytes);
|
|
1218
|
+
outputCharsBytes = nextOutputCharsBytes;
|
|
1065
1219
|
}
|
|
1066
1220
|
if (ev.stop === true) {
|
|
1067
1221
|
const flushed = flushOpen();
|
|
1068
1222
|
if (flushed.terminal) return { assistantText, sawReasoning, terminal: flushed.terminal };
|
|
1069
1223
|
for (const event of flushed.events) {
|
|
1070
|
-
|
|
1224
|
+
yield* emitRetained(stage(event));
|
|
1071
1225
|
}
|
|
1072
1226
|
} else {
|
|
1073
1227
|
yield { type: "heartbeat" };
|
|
@@ -1087,7 +1241,7 @@ async function* parseKiroAttemptEvents(
|
|
|
1087
1241
|
}
|
|
1088
1242
|
|
|
1089
1243
|
for (const contentEvent of thinking.flush()) {
|
|
1090
|
-
|
|
1244
|
+
yield* emitRetained(stage(contentEvent));
|
|
1091
1245
|
}
|
|
1092
1246
|
if (open) {
|
|
1093
1247
|
const input = open.chunks.join("");
|
|
@@ -1103,7 +1257,7 @@ async function* parseKiroAttemptEvents(
|
|
|
1103
1257
|
const flushed = flushOpen();
|
|
1104
1258
|
if (flushed.terminal) return { assistantText, sawReasoning, terminal: flushed.terminal };
|
|
1105
1259
|
for (const event of flushed.events) {
|
|
1106
|
-
|
|
1260
|
+
yield* emitRetained(stage(event));
|
|
1107
1261
|
}
|
|
1108
1262
|
}
|
|
1109
1263
|
|
|
@@ -1138,12 +1292,12 @@ async function* parseKiroAttemptEvents(
|
|
|
1138
1292
|
});
|
|
1139
1293
|
|
|
1140
1294
|
if (mode === "required") {
|
|
1141
|
-
|
|
1295
|
+
yield* emitRetained(deferred.splice(0));
|
|
1142
1296
|
}
|
|
1143
1297
|
|
|
1144
1298
|
if (mode === "text_fallback") {
|
|
1145
1299
|
if (completionAnswer !== undefined) {
|
|
1146
|
-
|
|
1300
|
+
yield* emitRetained(fallbackEvents);
|
|
1147
1301
|
yield { type: "text_delta", text: completionAnswer, phase: "final_answer" };
|
|
1148
1302
|
return {
|
|
1149
1303
|
assistantText,
|
|
@@ -1152,7 +1306,7 @@ async function* parseKiroAttemptEvents(
|
|
|
1152
1306
|
};
|
|
1153
1307
|
}
|
|
1154
1308
|
if (sawRealTool) {
|
|
1155
|
-
|
|
1309
|
+
yield* emitRetained(fallbackEvents);
|
|
1156
1310
|
return {
|
|
1157
1311
|
assistantText,
|
|
1158
1312
|
sawReasoning,
|
|
@@ -1161,8 +1315,12 @@ async function* parseKiroAttemptEvents(
|
|
|
1161
1315
|
}
|
|
1162
1316
|
if (sawText) {
|
|
1163
1317
|
for (const event of fallbackEvents) {
|
|
1164
|
-
|
|
1165
|
-
|
|
1318
|
+
try {
|
|
1319
|
+
if (event.type !== "text_delta") yield event;
|
|
1320
|
+
else yield { ...event, phase: "final_answer" };
|
|
1321
|
+
} finally {
|
|
1322
|
+
retention.releaseEvent(event);
|
|
1323
|
+
}
|
|
1166
1324
|
}
|
|
1167
1325
|
return {
|
|
1168
1326
|
assistantText,
|
|
@@ -1170,7 +1328,7 @@ async function* parseKiroAttemptEvents(
|
|
|
1170
1328
|
terminal: { type: "done", usage: finalUsage, endTurn: true, ...(finalProviderState ? { providerState: finalProviderState } : {}) },
|
|
1171
1329
|
};
|
|
1172
1330
|
}
|
|
1173
|
-
|
|
1331
|
+
yield* emitRetained(fallbackEvents);
|
|
1174
1332
|
return {
|
|
1175
1333
|
assistantText,
|
|
1176
1334
|
sawReasoning,
|
|
@@ -1295,6 +1453,20 @@ async function* parseKiroAttemptEvents(
|
|
|
1295
1453
|
},
|
|
1296
1454
|
};
|
|
1297
1455
|
} catch (err) {
|
|
1456
|
+
if (isTranslatorBudgetExceededError(err)) {
|
|
1457
|
+
if (open) budget.closeCall(open.id);
|
|
1458
|
+
return {
|
|
1459
|
+
assistantText,
|
|
1460
|
+
sawReasoning,
|
|
1461
|
+
terminal: {
|
|
1462
|
+
type: "error",
|
|
1463
|
+
status: 502,
|
|
1464
|
+
errorType: "upstream_error",
|
|
1465
|
+
code: "translation_buffer_limit",
|
|
1466
|
+
message: "upstream translation buffer exceeded the safe limit",
|
|
1467
|
+
},
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1298
1470
|
// Mid-stream socket closes after response.created / heartbeats only must stay retryable:
|
|
1299
1471
|
// nothing was relayed to the client, so a string-body replay is safe (see #519 / cursor's
|
|
1300
1472
|
// emittedOutput gate). Once any assistant text, reasoning, tool, or deferred content exists
|
|
@@ -1329,6 +1501,7 @@ async function* parseKiroAttemptEvents(
|
|
|
1329
1501
|
|
|
1330
1502
|
export async function* parseKiroStream(
|
|
1331
1503
|
response: Response,
|
|
1504
|
+
budget: TranslatorBudget,
|
|
1332
1505
|
modelId?: string,
|
|
1333
1506
|
inputTokens = 0,
|
|
1334
1507
|
contextWindow?: number,
|
|
@@ -1341,6 +1514,7 @@ export async function* parseKiroStream(
|
|
|
1341
1514
|
const contextWindowState: KiroContextWindowState = { value: contextWindow };
|
|
1342
1515
|
const first = parseKiroAttempt(
|
|
1343
1516
|
response,
|
|
1517
|
+
budget,
|
|
1344
1518
|
completionMode,
|
|
1345
1519
|
modelId,
|
|
1346
1520
|
inputTokens,
|
|
@@ -1348,6 +1522,7 @@ export async function* parseKiroStream(
|
|
|
1348
1522
|
nameMap,
|
|
1349
1523
|
conversationId,
|
|
1350
1524
|
contextInputEstimate,
|
|
1525
|
+
false,
|
|
1351
1526
|
);
|
|
1352
1527
|
let firstNext = await first.next();
|
|
1353
1528
|
while (!firstNext.done) {
|
|
@@ -1355,106 +1530,139 @@ export async function* parseKiroStream(
|
|
|
1355
1530
|
firstNext = await first.next();
|
|
1356
1531
|
}
|
|
1357
1532
|
const firstResult = firstNext.value;
|
|
1358
|
-
if (!firstResult.needsFallback) {
|
|
1359
|
-
if (firstResult.terminal) yield firstResult.terminal;
|
|
1360
|
-
return;
|
|
1361
|
-
}
|
|
1362
|
-
if (!fallbackFactory) {
|
|
1363
|
-
yield retryableKiroIncomplete(
|
|
1364
|
-
"uncompleted_kiro_response",
|
|
1365
|
-
"Kiro produced progress without an explicit final answer and no bounded retry transport was available",
|
|
1366
|
-
firstResult.usage ?? { inputTokens, outputTokens: 0, estimated: true },
|
|
1367
|
-
firstResult.providerState,
|
|
1368
|
-
);
|
|
1369
|
-
return;
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
yield { type: "heartbeat" };
|
|
1373
|
-
// First attempt already flushed deferred progress before this point. Gate fallback
|
|
1374
|
-
// setup/HTTP failures the same way as the second-stream catch so a replay cannot
|
|
1375
|
-
// duplicate visible commentary (#520).
|
|
1376
|
-
const priorEmittedOutput = Boolean(firstResult.assistantText.trim()) || firstResult.sawReasoning;
|
|
1377
|
-
let fallback: KiroFallbackAttempt;
|
|
1378
1533
|
try {
|
|
1379
|
-
|
|
1380
|
-
firstResult.
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
)
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
};
|
|
1393
|
-
return;
|
|
1394
|
-
}
|
|
1395
|
-
if (!fallback.response.ok) {
|
|
1396
|
-
const payload = await fallback.response.text().catch(() => "");
|
|
1397
|
-
const failure = classifyKiroHttpError(fallback.response.status, fallback.response.headers, payload);
|
|
1398
|
-
yield {
|
|
1399
|
-
type: "error",
|
|
1400
|
-
message: failure.message,
|
|
1401
|
-
status: failure.status,
|
|
1402
|
-
errorType: failure.errorType,
|
|
1403
|
-
code: failure.code,
|
|
1404
|
-
retryable: priorEmittedOutput ? false : failure.retryable,
|
|
1405
|
-
usage: firstResult.usage,
|
|
1406
|
-
};
|
|
1407
|
-
return;
|
|
1408
|
-
}
|
|
1534
|
+
if (!firstResult.needsFallback) {
|
|
1535
|
+
if (firstResult.terminal) yield firstResult.terminal;
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
if (!fallbackFactory) {
|
|
1539
|
+
yield retryableKiroIncomplete(
|
|
1540
|
+
"uncompleted_kiro_response",
|
|
1541
|
+
"Kiro produced progress without an explicit final answer and no bounded retry transport was available",
|
|
1542
|
+
firstResult.usage ?? { inputTokens, outputTokens: 0, estimated: true },
|
|
1543
|
+
firstResult.providerState,
|
|
1544
|
+
);
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1409
1547
|
|
|
1410
|
-
|
|
1411
|
-
fallback
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
fallback
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1548
|
+
yield { type: "heartbeat" };
|
|
1549
|
+
// First attempt already flushed deferred progress before this point. Gate fallback
|
|
1550
|
+
// setup/HTTP failures the same way as the second-stream catch so a replay cannot
|
|
1551
|
+
// duplicate visible commentary (#520).
|
|
1552
|
+
const priorEmittedOutput = Boolean(firstResult.assistantText.trim()) || firstResult.sawReasoning;
|
|
1553
|
+
let firstAssistantText = firstResult.assistantText;
|
|
1554
|
+
const firstHadAssistantText = firstAssistantText.length > 0;
|
|
1555
|
+
let fallback: KiroFallbackAttempt;
|
|
1556
|
+
try {
|
|
1557
|
+
fallback = await fallbackFactory(
|
|
1558
|
+
firstResult.providerState?.kiro.conversationId ?? conversationId,
|
|
1559
|
+
firstAssistantText,
|
|
1560
|
+
firstResult.sawReasoning,
|
|
1561
|
+
budget,
|
|
1562
|
+
);
|
|
1563
|
+
} catch (err) {
|
|
1564
|
+
firstAssistantText = "";
|
|
1565
|
+
firstResult.assistantText = "";
|
|
1566
|
+
firstResult.releaseRetained();
|
|
1567
|
+
if (isTranslatorBudgetExceededError(err)) {
|
|
1568
|
+
yield {
|
|
1569
|
+
type: "error",
|
|
1570
|
+
message: "upstream translation buffer exceeded the safe limit",
|
|
1571
|
+
status: 502,
|
|
1572
|
+
errorType: "upstream_error",
|
|
1573
|
+
code: "translation_buffer_limit",
|
|
1574
|
+
usage: firstResult.usage,
|
|
1575
|
+
};
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
yield {
|
|
1579
|
+
type: "error",
|
|
1580
|
+
message: safeKiroErrorMessage({}, err instanceof Error ? err.message : String(err)),
|
|
1581
|
+
status: err instanceof Error && err.name === "TimeoutError" ? 504 : 502,
|
|
1582
|
+
errorType: "upstream_error",
|
|
1583
|
+
retryable: !priorEmittedOutput,
|
|
1584
|
+
usage: firstResult.usage,
|
|
1585
|
+
};
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
// The factory has finished using the live first-attempt alias and has retained its own retry
|
|
1589
|
+
// serialization through the fetch boundary. The discarded parser collectors can now release
|
|
1590
|
+
// before the second attempt begins on the same turn budget.
|
|
1591
|
+
firstAssistantText = "";
|
|
1592
|
+
firstResult.assistantText = "";
|
|
1593
|
+
firstResult.releaseRetained();
|
|
1594
|
+
fallback.releaseRequestBody?.();
|
|
1595
|
+
if (!fallback.response.ok) {
|
|
1596
|
+
const payload = await fallback.response.text().catch(() => "");
|
|
1597
|
+
const failure = classifyKiroHttpError(fallback.response.status, fallback.response.headers, payload);
|
|
1598
|
+
yield {
|
|
1599
|
+
type: "error",
|
|
1600
|
+
message: failure.message,
|
|
1601
|
+
status: failure.status,
|
|
1602
|
+
errorType: failure.errorType,
|
|
1603
|
+
code: failure.code,
|
|
1604
|
+
retryable: priorEmittedOutput ? false : failure.retryable,
|
|
1605
|
+
usage: firstResult.usage,
|
|
1606
|
+
};
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
const second = parseKiroAttempt(
|
|
1611
|
+
fallback.response,
|
|
1612
|
+
budget,
|
|
1613
|
+
"text_fallback",
|
|
1614
|
+
modelId,
|
|
1615
|
+
fallback.inputTokens,
|
|
1616
|
+
contextWindowState,
|
|
1617
|
+
fallback.nameMap,
|
|
1618
|
+
fallback.conversationId,
|
|
1619
|
+
fallback.contextInputEstimate,
|
|
1620
|
+
// First attempt already flushed deferred progress to the client before this fallback.
|
|
1621
|
+
// A zero-output transport failure here must stay non-retryable to avoid duplicating that text.
|
|
1622
|
+
priorEmittedOutput,
|
|
1437
1623
|
);
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1624
|
+
let secondNext = await second.next();
|
|
1625
|
+
while (!secondNext.done) {
|
|
1626
|
+
yield secondNext.value;
|
|
1627
|
+
secondNext = await second.next();
|
|
1628
|
+
}
|
|
1629
|
+
const secondResult = secondNext.value;
|
|
1630
|
+
try {
|
|
1631
|
+
if (!secondResult.terminal) {
|
|
1632
|
+
yield retryableKiroIncomplete(
|
|
1633
|
+
"empty_kiro_fallback",
|
|
1634
|
+
"Kiro's bounded completion retry ended without a terminal result",
|
|
1635
|
+
mergeKiroUsage(firstResult.usage, secondResult.usage, firstHadAssistantText)
|
|
1636
|
+
?? { inputTokens, outputTokens: 0, estimated: true },
|
|
1637
|
+
secondResult.providerState ?? firstResult.providerState,
|
|
1638
|
+
!priorEmittedOutput,
|
|
1639
|
+
);
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
if (secondResult.terminal.type === "done" || secondResult.terminal.type === "incomplete") {
|
|
1643
|
+
yield {
|
|
1644
|
+
...secondResult.terminal,
|
|
1645
|
+
// Belt-and-suspenders: never advertise a replay-safe incomplete after flushed progress.
|
|
1646
|
+
...(secondResult.terminal.type === "incomplete" && priorEmittedOutput
|
|
1647
|
+
? { retryable: false as const }
|
|
1648
|
+
: {}),
|
|
1649
|
+
usage: mergeKiroUsage(firstResult.usage, secondResult.terminal.usage, firstHadAssistantText),
|
|
1650
|
+
providerState: secondResult.terminal.providerState ?? firstResult.providerState,
|
|
1651
|
+
};
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
yield {
|
|
1655
|
+
...secondResult.terminal,
|
|
1656
|
+
...(secondResult.terminal.type === "error"
|
|
1657
|
+
? { usage: mergeKiroUsage(firstResult.usage, secondResult.terminal.usage, firstHadAssistantText) }
|
|
1658
|
+
: {}),
|
|
1659
|
+
};
|
|
1660
|
+
} finally {
|
|
1661
|
+
secondResult.releaseRetained();
|
|
1662
|
+
}
|
|
1663
|
+
} finally {
|
|
1664
|
+
firstResult.releaseRetained();
|
|
1451
1665
|
}
|
|
1452
|
-
yield {
|
|
1453
|
-
...secondResult.terminal,
|
|
1454
|
-
...(secondResult.terminal.type === "error"
|
|
1455
|
-
? { usage: mergeKiroUsage(firstResult.usage, secondResult.terminal.usage, Boolean(firstResult.assistantText)) }
|
|
1456
|
-
: {}),
|
|
1457
|
-
};
|
|
1458
1666
|
}
|
|
1459
1667
|
|
|
1460
1668
|
// Adapter
|
|
@@ -1469,6 +1677,7 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
1469
1677
|
let conversationId: string | undefined;
|
|
1470
1678
|
let completionMode: KiroCompletionMode = "disabled";
|
|
1471
1679
|
let requestSnapshot: OcxParsedRequest | undefined;
|
|
1680
|
+
let firstRequestBodyBytes = 0;
|
|
1472
1681
|
let requestAbortSignal: AbortSignal | undefined;
|
|
1473
1682
|
|
|
1474
1683
|
const build = async (
|
|
@@ -1551,6 +1760,7 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
1551
1760
|
returnedConversationId,
|
|
1552
1761
|
assistantText,
|
|
1553
1762
|
_sawReasoning,
|
|
1763
|
+
budget,
|
|
1554
1764
|
) => {
|
|
1555
1765
|
if (!requestSnapshot) throw new Error("Kiro completion retry lost its request state");
|
|
1556
1766
|
if (requestAbortSignal?.aborted) {
|
|
@@ -1574,19 +1784,50 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
1574
1784
|
timestamp: Date.now(),
|
|
1575
1785
|
});
|
|
1576
1786
|
}
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1787
|
+
// The retry starts from the already measured first wire body, adds one JSON-escaped replay
|
|
1788
|
+
// string, and only changes bounded Kiro-owned fields (completion prompt/tool, history wrapper,
|
|
1789
|
+
// and <=256-byte conversation id). 64 KiB is a conservative envelope for those fixed fields.
|
|
1790
|
+
// Reserve that complete upper bound while the first-attempt collectors are still charged so a
|
|
1791
|
+
// near-cap turn fails before build() can materialize the retry payload or serialized body.
|
|
1792
|
+
const retryBodyUpperBound = firstRequestBodyBytes
|
|
1793
|
+
+ jsonStringSerializedUtf8Bytes(assistantText)
|
|
1794
|
+
+ KIRO_FALLBACK_SERIALIZATION_ENVELOPE_BYTES;
|
|
1795
|
+
const retryBodyReservation = budget.reserveTransient(retryBodyUpperBound, { kind: "request_copies" });
|
|
1796
|
+
let retryBodyBytes = 0;
|
|
1797
|
+
let retryBodyRetained = false;
|
|
1798
|
+
let requestBodyReleased = false;
|
|
1799
|
+
const releaseRequestBody = () => {
|
|
1800
|
+
if (requestBodyReleased) return;
|
|
1801
|
+
requestBodyReleased = true;
|
|
1802
|
+
if (retryBodyRetained) budget.releaseRetained(retryBodyBytes, { kind: "request_copies" });
|
|
1803
|
+
else retryBodyReservation.release();
|
|
1589
1804
|
};
|
|
1805
|
+
try {
|
|
1806
|
+
const retry = await build(retryParsed, "text_fallback");
|
|
1807
|
+
retryBodyBytes = Buffer.byteLength(retry.request.body);
|
|
1808
|
+
if (retryBodyBytes > retryBodyUpperBound) {
|
|
1809
|
+
throw new Error("Kiro retry serialization exceeded its pre-admitted upper bound");
|
|
1810
|
+
}
|
|
1811
|
+
retryBodyReservation.commitRetained();
|
|
1812
|
+
retryBodyRetained = true;
|
|
1813
|
+
budget.releaseRetained(retryBodyUpperBound - retryBodyBytes, { kind: "request_copies" });
|
|
1814
|
+
const response = await fetchKiroWithRetry(retry.request, {
|
|
1815
|
+
abortSignal: requestAbortSignal,
|
|
1816
|
+
returnRawErrors: true,
|
|
1817
|
+
stream: true,
|
|
1818
|
+
});
|
|
1819
|
+
return {
|
|
1820
|
+
response,
|
|
1821
|
+
inputTokens: retry.inputTokens,
|
|
1822
|
+
contextInputEstimate: retry.contextInputEstimate,
|
|
1823
|
+
nameMap: retry.nameMap,
|
|
1824
|
+
conversationId: retry.conversationId,
|
|
1825
|
+
releaseRequestBody,
|
|
1826
|
+
};
|
|
1827
|
+
} catch (error) {
|
|
1828
|
+
releaseRequestBody();
|
|
1829
|
+
throw error;
|
|
1830
|
+
}
|
|
1590
1831
|
};
|
|
1591
1832
|
|
|
1592
1833
|
return {
|
|
@@ -1601,13 +1842,15 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
1601
1842
|
conversationId = built.conversationId;
|
|
1602
1843
|
completionMode = built.completionMode;
|
|
1603
1844
|
requestSnapshot = structuredClone(parsed);
|
|
1845
|
+
firstRequestBodyBytes = Buffer.byteLength(built.request.body);
|
|
1604
1846
|
requestAbortSignal = incoming?.abortSignal;
|
|
1605
1847
|
return built.request;
|
|
1606
1848
|
},
|
|
1607
1849
|
|
|
1608
|
-
parseStream(response: Response): AsyncGenerator<AdapterEvent> {
|
|
1850
|
+
parseStream(response: Response, budget: TranslatorBudget): AsyncGenerator<AdapterEvent> {
|
|
1609
1851
|
return parseKiroStream(
|
|
1610
1852
|
response,
|
|
1853
|
+
budget,
|
|
1611
1854
|
modelId,
|
|
1612
1855
|
inputTokens,
|
|
1613
1856
|
contextWindow,
|
|
@@ -1635,10 +1878,11 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
1635
1878
|
// non-streamed so it can inspect tool calls). CW only ever event-streams, so we drain the
|
|
1636
1879
|
// same decoder into an array. Without this, any Codex request that includes the web_search
|
|
1637
1880
|
// tool failed with "web-search sidecar requires a non-streaming adapter" (kiro-only).
|
|
1638
|
-
async parseResponse(response: Response): Promise<AdapterEvent[]> {
|
|
1881
|
+
async parseResponse(response: Response, budget: TranslatorBudget): Promise<AdapterEvent[]> {
|
|
1639
1882
|
const events: AdapterEvent[] = [];
|
|
1640
1883
|
for await (const e of parseKiroStream(
|
|
1641
1884
|
response,
|
|
1885
|
+
budget,
|
|
1642
1886
|
modelId,
|
|
1643
1887
|
inputTokens,
|
|
1644
1888
|
contextWindow,
|