@coseung2/opencodex 2.8.0-cs.16 → 2.8.0-cs.17
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/gui/dist/assets/{index-BZGMtkmp.js → index-Ch-99jy3.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/base.ts +12 -0
- package/src/adapters/kiro-calibration.ts +83 -0
- package/src/adapters/kiro-constants.ts +11 -2
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +12 -3
- package/src/adapters/kiro.ts +300 -78
- package/src/adapters/openai-chat.ts +1 -42
- package/src/adapters/openai-responses.ts +93 -14
- package/src/adapters/xai-schema-analysis.ts +78 -0
- package/src/adapters/xai-tool-schema.ts +274 -0
- package/src/adapters/xai-web-search.ts +138 -0
- package/src/bridge.ts +61 -6
- package/src/codex/catalog/effort.ts +4 -2
- package/src/codex/catalog/metadata.ts +38 -9
- package/src/codex/catalog/parsing.ts +17 -2
- package/src/codex/catalog/provider-fetch.ts +9 -3
- package/src/codex/catalog/sync.ts +8 -5
- package/src/codex/data/upstream-models.json +169 -0
- package/src/grok/inject.ts +1 -1
- package/src/lib/token-estimate.ts +42 -38
- package/src/lib/translator-budget.ts +34 -0
- package/src/oauth/index.ts +10 -4
- package/src/oauth/kiro.ts +71 -6
- package/src/oauth/store.ts +3 -1
- package/src/oauth/types.ts +4 -0
- package/src/providers/derive.ts +7 -5
- package/src/providers/opencode-go-transport.ts +18 -0
- package/src/providers/registry.ts +34 -10
- package/src/providers/xai-transport.ts +10 -0
- package/src/responses/compaction.ts +8 -1
- package/src/responses/namespace-aliases.ts +56 -0
- package/src/responses/parser.ts +12 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/snapshot-policy.ts +108 -0
- package/src/responses/state.ts +23 -10
- package/src/responses/turn-termination.ts +108 -0
- package/src/responses/xai-custom-tool-compat.ts +237 -0
- package/src/server/grok-responses-snapshot-repair.ts +338 -0
- package/src/server/index.ts +2 -1
- package/src/server/relay-eager.ts +1 -0
- package/src/server/responses/core.ts +173 -13
- package/src/server/responses-image-gen-repair.ts +2 -2
- package/src/server/sse-payload-rewrite.ts +20 -3
- package/src/types.ts +10 -1
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +7 -0
- package/vendor/ocx-notch/win32-x64/ocx-notch.exe +0 -0
package/src/adapters/kiro.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { decodeEventStream } from "../lib/eventstream-decoder";
|
|
2
2
|
import { estimateTokens } from "../lib/token-estimate";
|
|
3
3
|
import { debugProviderDiagnostic } from "../lib/debug";
|
|
4
|
-
import { resolveKiroApiRegion,
|
|
4
|
+
import { resolveKiroApiRegion, resolveKiroRequestProfile } from "../oauth/kiro";
|
|
5
5
|
import { KIRO_MODEL_CONTEXT_WINDOWS, normalizeKiroModelId } from "../providers/kiro-models";
|
|
6
6
|
import { modelRecordValue } from "../reasoning-effort";
|
|
7
7
|
import { parseKiroEvent } from "./kiro-events";
|
|
8
|
+
import { calibrateKiroEstimate, recordKiroCalibration, rekeyKiroCalibration } from "./kiro-calibration";
|
|
8
9
|
import {
|
|
9
10
|
classifyKiroEventError,
|
|
10
11
|
classifyKiroHttpError,
|
|
@@ -17,8 +18,11 @@ import { KiroThinkingParser } from "./kiro-thinking";
|
|
|
17
18
|
import { isCompleteKiroToolInput, kiroTruncationErrorMessage } from "./kiro-truncation";
|
|
18
19
|
import { createKiroToolNameRegistry, fallbackToolUseId, fingerprint, invocationId, isValidKiroConversationId, mapModelId, normalizeToolId, osTag, stableConversationId } from "./kiro-wire";
|
|
19
20
|
import { namespacedToolName } from "../types";
|
|
21
|
+
import { hasRecordedTrailingDeliveredFinalAnswer } from "../responses/turn-termination";
|
|
20
22
|
import {
|
|
21
23
|
isTranslatorBudgetExceededError,
|
|
24
|
+
releaseTranslatedEvent,
|
|
25
|
+
retainTranslatedEvent,
|
|
22
26
|
type TranslatorBudget,
|
|
23
27
|
} from "../lib/translator-budget";
|
|
24
28
|
import type {
|
|
@@ -42,6 +46,7 @@ import { convertKiroToolContext } from "./kiro-tools";
|
|
|
42
46
|
import { neutralizeIdentity } from "./identity";
|
|
43
47
|
import { buildNonOpenAIToolCatalogNudgeFromNames } from "./tool-catalog-nudge";
|
|
44
48
|
import {
|
|
49
|
+
KIRO_ANSWER_DELIVERED_MESSAGE,
|
|
45
50
|
KIRO_COMPLETION_INSTRUCTIONS,
|
|
46
51
|
KIRO_COMPLETION_RETRY_MESSAGE,
|
|
47
52
|
KIRO_COMPLETION_TOOL_NAME,
|
|
@@ -99,7 +104,11 @@ interface KiroUserInputMessage {
|
|
|
99
104
|
}
|
|
100
105
|
interface KiroHistoryEntry {
|
|
101
106
|
userInputMessage?: KiroUserInputMessage;
|
|
102
|
-
assistantResponseMessage?: {
|
|
107
|
+
assistantResponseMessage?: {
|
|
108
|
+
content: string;
|
|
109
|
+
toolUses?: KiroToolUse[];
|
|
110
|
+
reasoningContent?: { redactedContent: string };
|
|
111
|
+
};
|
|
103
112
|
}
|
|
104
113
|
|
|
105
114
|
function kiroToolWireNames(tools: readonly unknown[]): string[] {
|
|
@@ -176,6 +185,11 @@ function estimateKiroTokens(text: string, modelId?: string): number {
|
|
|
176
185
|
return estimateTokens(text, modelId ? `kiro/${modelId}` : "kiro");
|
|
177
186
|
}
|
|
178
187
|
|
|
188
|
+
// Per-entry JSON/role framing is invisible to the text walker but grows with conversation length.
|
|
189
|
+
const KIRO_ENTRY_FRAMING_TOKENS = 12;
|
|
190
|
+
// Newlines, quotes, tabs and backslashes expand when serialized onto the Kiro JSON wire.
|
|
191
|
+
const KIRO_JSON_ESCAPE_EXPANSION = 1.12;
|
|
192
|
+
|
|
179
193
|
function estimateKiroPayloadInputTokens(payload: Record<string, unknown>, modelId: string): number {
|
|
180
194
|
const conversationState = (payload as {
|
|
181
195
|
conversationState?: {
|
|
@@ -206,7 +220,9 @@ function estimateKiroPayloadInputTokens(payload: Record<string, unknown>, modelI
|
|
|
206
220
|
if (assistant.toolUses?.length) parts.push(serializeForUsage(assistant.toolUses));
|
|
207
221
|
}
|
|
208
222
|
}
|
|
209
|
-
return estimateKiroTokens(parts.join("\n"), modelId)
|
|
223
|
+
return Math.ceil(estimateKiroTokens(parts.join("\n"), modelId) * KIRO_JSON_ESCAPE_EXPANSION)
|
|
224
|
+
+ imageTokens
|
|
225
|
+
+ entries.length * KIRO_ENTRY_FRAMING_TOKENS;
|
|
210
226
|
}
|
|
211
227
|
|
|
212
228
|
function shouldCountStablePromptOverhead(parsed: OcxParsedRequest): boolean {
|
|
@@ -312,21 +328,61 @@ function validateKiroCapabilities(parsed: OcxParsedRequest): void {
|
|
|
312
328
|
if (choice !== undefined && choice !== "auto" && choice !== "none") {
|
|
313
329
|
throw new Error("Kiro supports only automatic tool choice or tool_choice:none");
|
|
314
330
|
}
|
|
315
|
-
if (parsed.options.parallelToolCalls === true) {
|
|
316
|
-
throw new Error("Kiro does not support parallel tool calls");
|
|
317
|
-
}
|
|
318
331
|
if (parsed.options.serviceTier !== undefined) {
|
|
319
332
|
throw new Error("Kiro does not support service tiers");
|
|
320
333
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
334
|
+
// Structured output is a real contract Kiro cannot honour: the wire has no
|
|
335
|
+
// schema-constrained response mode, so a caller expecting parseable JSON would receive
|
|
336
|
+
// prose and fail downstream. Refuse it.
|
|
337
|
+
//
|
|
338
|
+
// The rest of the Responses `text` object is not that. `text.verbosity` is a length
|
|
339
|
+
// preference and `text.format: {type:"text"}` is ordinary prose — the default output
|
|
340
|
+
// mode, which no capability flag governs and every correct client may send. Testing
|
|
341
|
+
// `_rawBody.text !== undefined` refused those turns for the mere PRESENCE of the key,
|
|
342
|
+
// the same mistake db040e70f removed one condition earlier where a permissive
|
|
343
|
+
// `parallel_tool_calls` hint was read as a requirement.
|
|
344
|
+
//
|
|
345
|
+
// Nothing needs stripping the way openai-responses strips a no-op verbosity:
|
|
346
|
+
// buildKiroPayload composes conversationState field by field from `parsed` and never
|
|
347
|
+
// spreads `_rawBody`, so a tolerated control is dropped by construction. The test
|
|
348
|
+
// asserts that absence so it stays true.
|
|
349
|
+
if (parsed._structuredOutput) {
|
|
350
|
+
throw new Error("Kiro does not support Responses structured output");
|
|
324
351
|
}
|
|
325
352
|
}
|
|
326
353
|
|
|
327
354
|
type KiroTurn =
|
|
328
|
-
| {
|
|
329
|
-
|
|
355
|
+
| {
|
|
356
|
+
kind: "user";
|
|
357
|
+
content: string;
|
|
358
|
+
images: KiroImage[];
|
|
359
|
+
toolResults: KiroToolResult[];
|
|
360
|
+
/** True only for the proxy-generated acknowledgement after a delivered final answer. */
|
|
361
|
+
answerDeliveredAck?: boolean;
|
|
362
|
+
}
|
|
363
|
+
| {
|
|
364
|
+
kind: "assistant";
|
|
365
|
+
content: string;
|
|
366
|
+
toolUses: KiroToolUse[];
|
|
367
|
+
redactedReasoning?: string;
|
|
368
|
+
/** A Responses final_answer already shown to the user; this turn must not be resumed. */
|
|
369
|
+
finalAnswer?: boolean;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
/** True only when no later user/tool-result work follows the delivered final answer. */
|
|
373
|
+
function hasTrailingDeliveredFinalAnswer(messages: readonly OcxMessage[], parsed?: OcxParsedRequest): boolean {
|
|
374
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
375
|
+
const message = messages[index];
|
|
376
|
+
if (message.role !== "assistant") return false;
|
|
377
|
+
const assistant = message as OcxAssistantMessage;
|
|
378
|
+
if ((assistant.content ?? []).some(part => part.type === "toolCall")) return false;
|
|
379
|
+
const hasText = (assistant.content ?? []).some(part => part.type === "text" && part.text.trim());
|
|
380
|
+
if (!hasText) continue;
|
|
381
|
+
return assistant.phase === "final_answer"
|
|
382
|
+
|| (parsed !== undefined && hasRecordedTrailingDeliveredFinalAnswer(parsed, messages));
|
|
383
|
+
}
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
330
386
|
|
|
331
387
|
function appendTurnText(target: string, next: string): string {
|
|
332
388
|
if (!next) return target;
|
|
@@ -381,23 +437,39 @@ function validateKiroConversationState(history: KiroHistoryEntry[], currentMessa
|
|
|
381
437
|
function boundedInjectedInstruction(text: string, used: { value: number }): string | undefined {
|
|
382
438
|
const remaining = MAX_KIRO_INJECTED_INSTRUCTION_CHARS - used.value;
|
|
383
439
|
if (remaining <= 0 || !text) return undefined;
|
|
384
|
-
|
|
440
|
+
let result = text.length <= remaining ? text : text.slice(0, remaining);
|
|
441
|
+
// Never end the slice on a lone high surrogate: encoding it substitutes
|
|
442
|
+
// U+FFFD into the injected instruction. One step back keeps a valid pair
|
|
443
|
+
// out instead of a broken half.
|
|
444
|
+
if (result.length > 0) {
|
|
445
|
+
const last = result.charCodeAt(result.length - 1);
|
|
446
|
+
if (last >= 0xd800 && last <= 0xdbff) result = result.slice(0, -1);
|
|
447
|
+
}
|
|
385
448
|
used.value += result.length;
|
|
386
|
-
return result;
|
|
449
|
+
return result.length > 0 ? result : undefined;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** Test-only: exercise the surrogate-safe instruction bound directly. */
|
|
453
|
+
export function boundedInjectedInstructionForTests(text: string, used: { value: number }): string | undefined {
|
|
454
|
+
return boundedInjectedInstruction(text, used);
|
|
387
455
|
}
|
|
388
456
|
|
|
389
457
|
function kiroCompletionTool(): Record<string, unknown> {
|
|
390
458
|
return {
|
|
391
459
|
toolSpecification: {
|
|
392
460
|
name: KIRO_COMPLETION_TOOL_NAME,
|
|
393
|
-
|
|
461
|
+
// The shared catalog nudge treats every listed tool as if a result must return. This one is
|
|
462
|
+
// different: a valid call is the terminal itself. State that at the schema surface where the
|
|
463
|
+
// model chooses tools, otherwise a finished model can keep working while waiting for a result
|
|
464
|
+
// that will never exist.
|
|
465
|
+
description: "Terminal completion channel, not an ordinary work tool. When the task is fully complete and no more work or tool calls are needed, you must call this tool exactly once instead of providing the final answer as ordinary assistant text. Call it the same way when you cannot continue until the user supplies a decision, information, or a clarification that only they can give: the question itself is the answer. Put the complete user-facing final answer in `answer`. The call is complete when issued: it ends the turn, returns no tool result, and no text or tool call may follow it.",
|
|
394
466
|
inputSchema: {
|
|
395
467
|
json: {
|
|
396
468
|
type: "object",
|
|
397
469
|
properties: {
|
|
398
470
|
answer: {
|
|
399
471
|
type: "string",
|
|
400
|
-
description: "The complete final answer to show the user.",
|
|
472
|
+
description: "The complete final answer to show the user, or the blocking question you need the user to answer before you can continue.",
|
|
401
473
|
},
|
|
402
474
|
},
|
|
403
475
|
required: ["answer"],
|
|
@@ -423,8 +495,12 @@ export function buildKiroPayload(
|
|
|
423
495
|
const registry = createKiroToolNameRegistry();
|
|
424
496
|
const toolContext = convertKiroToolContext(parsed, registry);
|
|
425
497
|
const ordinaryTools = toolContext.tools;
|
|
498
|
+
// A replay that already ends in a delivered final answer has nothing left to complete. Keeping
|
|
499
|
+
// the private completion tool enabled here reopens the closed task even if the trailing prompt is
|
|
500
|
+
// neutral, because the model is still instructed to produce another terminal answer.
|
|
501
|
+
const trailingDeliveredAnswer = hasTrailingDeliveredFinalAnswer(kiroPayloadMessages(parsed), parsed);
|
|
426
502
|
const completionMode: KiroCompletionMode = forcedCompletionMode
|
|
427
|
-
?? (ordinaryTools.length > 0 ? "required" : "disabled");
|
|
503
|
+
?? (ordinaryTools.length > 0 && !trailingDeliveredAnswer ? "required" : "disabled");
|
|
428
504
|
const kiroTools = completionMode === "disabled"
|
|
429
505
|
? ordinaryTools
|
|
430
506
|
: [...ordinaryTools, kiroCompletionTool()];
|
|
@@ -447,7 +523,7 @@ export function buildKiroPayload(
|
|
|
447
523
|
}
|
|
448
524
|
const systemPrefix = systemParts.length > 0 ? `${systemParts.join("\n\n")}\n\n` : "";
|
|
449
525
|
const turns: KiroTurn[] = [];
|
|
450
|
-
const priorCalls = new Map<string, { wireName: string }>();
|
|
526
|
+
const priorCalls = new Map<string, { wireName: string; rawId: string }>();
|
|
451
527
|
const pushUser = (content: string, images: KiroImage[] = [], toolResults: KiroToolResult[] = []): void => {
|
|
452
528
|
const last = turns.at(-1);
|
|
453
529
|
if (last?.kind === "user") {
|
|
@@ -458,14 +534,53 @@ export function buildKiroPayload(
|
|
|
458
534
|
turns.push({ kind: "user", content, images: [...images], toolResults: [...toolResults] });
|
|
459
535
|
}
|
|
460
536
|
};
|
|
461
|
-
const pushAssistant = (
|
|
537
|
+
const pushAssistant = (
|
|
538
|
+
content: string,
|
|
539
|
+
toolUses: KiroToolUse[],
|
|
540
|
+
redactedReasoning?: string,
|
|
541
|
+
finalAnswer?: boolean,
|
|
542
|
+
): void => {
|
|
462
543
|
const last = turns.at(-1);
|
|
463
544
|
if (last?.kind === "assistant") {
|
|
464
545
|
last.content = appendTurnText(last.content, content);
|
|
465
546
|
last.toolUses.push(...toolUses);
|
|
547
|
+
// Merged turns keep the newest blob: it covers the reasoning up to the merged turn's end.
|
|
548
|
+
if (redactedReasoning) last.redactedReasoning = redactedReasoning;
|
|
549
|
+
// Finality follows the LAST merged component. Commentary after a final answer means work
|
|
550
|
+
// continued and therefore reopens the turn legitimately.
|
|
551
|
+
last.finalAnswer = finalAnswer === true;
|
|
466
552
|
} else {
|
|
467
|
-
turns.push({
|
|
553
|
+
turns.push({
|
|
554
|
+
kind: "assistant",
|
|
555
|
+
content,
|
|
556
|
+
toolUses: [...toolUses],
|
|
557
|
+
...(redactedReasoning ? { redactedReasoning } : {}),
|
|
558
|
+
...(finalAnswer ? { finalAnswer: true } : {}),
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// Codex custom tools may emit several adjacent output items for one invocation (for example
|
|
564
|
+
// progress notifications followed by the final value). Kiro accepts one result per tool use, so
|
|
565
|
+
// coalesce only immediately adjacent outputs whose ORIGINAL ids are identical. The raw-id check
|
|
566
|
+
// is important: normalizeToolId is lossy (`|`, whitespace, truncation), and must never authorize a
|
|
567
|
+
// different result merely because two caller-controlled ids normalize to the same wire id.
|
|
568
|
+
let adjacentResult: {
|
|
569
|
+
rawId: string;
|
|
570
|
+
result: KiroToolResult;
|
|
571
|
+
texts: string[];
|
|
572
|
+
count: number;
|
|
573
|
+
hasImages: boolean;
|
|
574
|
+
} | undefined;
|
|
575
|
+
const finishAdjacentResult = (): void => {
|
|
576
|
+
if (adjacentResult && adjacentResult.count > 1) {
|
|
577
|
+
if (adjacentResult.texts.some(text => text.trim())) {
|
|
578
|
+
adjacentResult.result.content = adjacentResult.texts.map(text => ({ text }));
|
|
579
|
+
} else if (adjacentResult.hasImages || adjacentResult.result.status === "error") {
|
|
580
|
+
adjacentResult.result.content = [{ text: KIRO_EMPTY_TOOL_RESULT_MESSAGE }];
|
|
581
|
+
}
|
|
468
582
|
}
|
|
583
|
+
adjacentResult = undefined;
|
|
469
584
|
};
|
|
470
585
|
|
|
471
586
|
const payloadMessages = kiroPayloadMessages(parsed);
|
|
@@ -476,6 +591,9 @@ export function buildKiroPayload(
|
|
|
476
591
|
for (let messageIndex = 0; messageIndex < payloadMessages.length; messageIndex++) {
|
|
477
592
|
const msg = payloadMessages[messageIndex];
|
|
478
593
|
const isReplayedMessage = messageIndex < replayMessagePrefixLength;
|
|
594
|
+
// Preserve source-message adjacency even when the turn normalization below would collapse or
|
|
595
|
+
// skip a structural message.
|
|
596
|
+
if (msg.role !== "toolResult") finishAdjacentResult();
|
|
479
597
|
if (msg.role === "user" || msg.role === "developer") {
|
|
480
598
|
const text = userContentText((msg as { content: string | OcxContentPart[] }).content);
|
|
481
599
|
// Historical text/tool structure remains replayable, but image bytes are scoped to the turn
|
|
@@ -508,14 +626,19 @@ export function buildKiroPayload(
|
|
|
508
626
|
if (priorCalls.has(toolUseId)) throw new Error(`Kiro history contains duplicate tool call id ${JSON.stringify(tc.id)}`);
|
|
509
627
|
const wireName = namespacedToolName(tc.namespace, tc.name);
|
|
510
628
|
const name = registry.alias(wireName);
|
|
511
|
-
priorCalls.set(toolUseId, { wireName });
|
|
629
|
+
priorCalls.set(toolUseId, { wireName, rawId: tc.id });
|
|
512
630
|
return { name, input: (tc.arguments ?? {}) as Record<string, unknown>, toolUseId };
|
|
513
631
|
});
|
|
514
632
|
if (!text && toolUses.length === 0) {
|
|
515
633
|
const hasReasoning = aMsg.content.some(part => part.type === "thinking" && part.thinking.trim());
|
|
516
634
|
if (hasReasoning || aMsg.phase === "commentary") continue;
|
|
517
635
|
}
|
|
518
|
-
pushAssistant(
|
|
636
|
+
pushAssistant(
|
|
637
|
+
text,
|
|
638
|
+
toolUses,
|
|
639
|
+
aMsg.kiroRedactedReasoning,
|
|
640
|
+
aMsg.phase === "final_answer" && toolUses.length === 0,
|
|
641
|
+
);
|
|
519
642
|
} else if (msg.role === "toolResult") {
|
|
520
643
|
const tr = msg as OcxToolResultMessage;
|
|
521
644
|
if (tr.containsEncryptedContent) {
|
|
@@ -525,30 +648,57 @@ export function buildKiroPayload(
|
|
|
525
648
|
const resultText = text.trim() ? text : KIRO_EMPTY_TOOL_RESULT_MESSAGE;
|
|
526
649
|
const images = isReplayedMessage ? [] : extractKiroImages(tr.content);
|
|
527
650
|
const toolUseId = normalizeToolId(tr.toolCallId);
|
|
528
|
-
|
|
651
|
+
const call = priorCalls.get(toolUseId);
|
|
652
|
+
if (!call || call.rawId !== tr.toolCallId) {
|
|
529
653
|
throw new Error(`Kiro history contains an orphaned tool result for call ${JSON.stringify(tr.toolCallId)}`);
|
|
530
654
|
}
|
|
655
|
+
const last = turns.at(-1);
|
|
656
|
+
if (
|
|
657
|
+
adjacentResult?.rawId === tr.toolCallId
|
|
658
|
+
&& last?.kind === "user"
|
|
659
|
+
&& last.toolResults.at(-1) === adjacentResult.result
|
|
660
|
+
) {
|
|
661
|
+
adjacentResult.count += 1;
|
|
662
|
+
adjacentResult.hasImages ||= images.length > 0;
|
|
663
|
+
if (text.length > 0) adjacentResult.texts.push(text);
|
|
664
|
+
last.images.push(...images);
|
|
665
|
+
if (tr.isError) adjacentResult.result.status = "error";
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
finishAdjacentResult();
|
|
531
669
|
// Carrier text is a placeholder for an OTHERWISE EMPTY tool-result turn, not a prefix.
|
|
532
670
|
// Passing it here would push proxy filler AHEAD of a human instruction that Claude Code
|
|
533
671
|
// sends in the same turn (mid-turn steering / queued_command, issue #543), burying the
|
|
534
672
|
// newest user intent behind boilerplate. Backfill below only when nothing else speaks.
|
|
535
|
-
|
|
673
|
+
const result: KiroToolResult = {
|
|
536
674
|
content: [{ text: resultText }],
|
|
537
675
|
status: tr.isError ? "error" : "success",
|
|
538
676
|
toolUseId,
|
|
539
|
-
}
|
|
677
|
+
};
|
|
678
|
+
pushUser("", images, [result]);
|
|
679
|
+
adjacentResult = {
|
|
680
|
+
rawId: tr.toolCallId,
|
|
681
|
+
result,
|
|
682
|
+
texts: text.length > 0 ? [text] : [],
|
|
683
|
+
count: 1,
|
|
684
|
+
hasImages: images.length > 0,
|
|
685
|
+
};
|
|
540
686
|
}
|
|
541
687
|
}
|
|
688
|
+
finishAdjacentResult();
|
|
542
689
|
|
|
543
690
|
if (turns.length === 0 || turns[0].kind === "assistant") {
|
|
544
691
|
turns.unshift({ kind: "user", content: KIRO_CONTINUATION_MESSAGE, images: [], toolResults: [] });
|
|
545
692
|
}
|
|
546
|
-
|
|
693
|
+
const trailingTurn = turns.at(-1);
|
|
694
|
+
if (trailingTurn?.kind === "assistant") {
|
|
695
|
+
const resumeText = completionMode === "text_fallback" ? KIRO_COMPLETION_RETRY_MESSAGE : KIRO_CONTINUATION_MESSAGE;
|
|
547
696
|
turns.push({
|
|
548
697
|
kind: "user",
|
|
549
|
-
content:
|
|
698
|
+
content: trailingTurn.finalAnswer ? KIRO_ANSWER_DELIVERED_MESSAGE : resumeText,
|
|
550
699
|
images: [],
|
|
551
700
|
toolResults: [],
|
|
701
|
+
...(trailingTurn.finalAnswer ? { answerDeliveredAck: true } : {}),
|
|
552
702
|
});
|
|
553
703
|
}
|
|
554
704
|
|
|
@@ -564,11 +714,15 @@ export function buildKiroPayload(
|
|
|
564
714
|
|
|
565
715
|
const currentTurn = turns.pop();
|
|
566
716
|
if (!currentTurn || currentTurn.kind !== "user") throw new Error("Kiro request must end with a user turn");
|
|
717
|
+
// Keep internal acknowledgement state separate from its text: a real user may quote the same
|
|
718
|
+
// sentence and must still receive ordinary thinking/completion behavior.
|
|
719
|
+
const answerDeliveredAck = currentTurn.answerDeliveredAck === true;
|
|
567
720
|
const toEntry = (turn: KiroTurn): KiroHistoryEntry => turn.kind === "assistant"
|
|
568
721
|
? {
|
|
569
722
|
assistantResponseMessage: {
|
|
570
723
|
content: turn.content,
|
|
571
724
|
...(turn.toolUses.length > 0 ? { toolUses: turn.toolUses } : {}),
|
|
725
|
+
...(turn.redactedReasoning ? { reasoningContent: { redactedContent: turn.redactedReasoning } } : {}),
|
|
572
726
|
},
|
|
573
727
|
}
|
|
574
728
|
: {
|
|
@@ -593,10 +747,14 @@ export function buildKiroPayload(
|
|
|
593
747
|
currentUim.userInputMessageContext = { ...(currentUim.userInputMessageContext ?? {}), tools: kiroTools };
|
|
594
748
|
}
|
|
595
749
|
if (completionMode === "text_fallback") {
|
|
596
|
-
if (currentUim.content !== KIRO_COMPLETION_RETRY_MESSAGE) {
|
|
750
|
+
if (currentUim.content !== KIRO_COMPLETION_RETRY_MESSAGE && !answerDeliveredAck) {
|
|
597
751
|
currentUim.content = appendTurnText(currentUim.content, KIRO_COMPLETION_RETRY_MESSAGE);
|
|
598
752
|
}
|
|
599
|
-
} else if (
|
|
753
|
+
} else if (
|
|
754
|
+
!currentUim.userInputMessageContext?.toolResults
|
|
755
|
+
&& currentUim.content !== KIRO_CONTINUATION_MESSAGE
|
|
756
|
+
&& !answerDeliveredAck
|
|
757
|
+
) {
|
|
600
758
|
currentUim.content = injectKiroThinkingTags(currentUim.content, parsed);
|
|
601
759
|
}
|
|
602
760
|
|
|
@@ -628,8 +786,8 @@ export function buildKiroPayload(
|
|
|
628
786
|
|
|
629
787
|
// Stream parsing (shared by parseStream + parseResponse)
|
|
630
788
|
// CodeWhisperer GenerateAssistantResponse ALWAYS returns an AWS eventstream body (there is no
|
|
631
|
-
// non-streaming mode), so
|
|
632
|
-
//
|
|
789
|
+
// non-streaming wire mode), so the streaming bridge and non-streaming Responses path decode the
|
|
790
|
+
// same way — parseResponse just collects what parseStream yields.
|
|
633
791
|
interface KiroAttemptParseResult {
|
|
634
792
|
terminal?: AdapterEvent;
|
|
635
793
|
needsFallback?: boolean;
|
|
@@ -835,6 +993,9 @@ async function* parseKiroAttempt(
|
|
|
835
993
|
// the attempt boundary. Anything the inner parser leaves behind is flushed before the terminal.
|
|
836
994
|
const deferred: AdapterEvent[] = [];
|
|
837
995
|
const retention = createKiroAttemptRetention(budget);
|
|
996
|
+
// The inner parser can observe Kiro's authoritative context checkpoint, but only this wrapper
|
|
997
|
+
// knows whether the attempt is terminal or will be followed by the bounded completion retry.
|
|
998
|
+
const attemptCalibration: { value?: { conversationId: string; estimated: number; charged: number } } = {};
|
|
838
999
|
const attempt = parseKiroAttemptEvents(
|
|
839
1000
|
response,
|
|
840
1001
|
budget,
|
|
@@ -846,21 +1007,27 @@ async function* parseKiroAttempt(
|
|
|
846
1007
|
conversationId,
|
|
847
1008
|
deferred,
|
|
848
1009
|
retention,
|
|
1010
|
+
attemptCalibration,
|
|
849
1011
|
contextInputEstimate,
|
|
850
1012
|
priorEmittedOutput,
|
|
851
1013
|
);
|
|
852
1014
|
let handedOff = false;
|
|
853
1015
|
try {
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1016
|
+
const result = yield* attempt;
|
|
1017
|
+
const stagedCalibration = attemptCalibration.value;
|
|
1018
|
+
attemptCalibration.value = undefined;
|
|
1019
|
+
if (stagedCalibration && !result.needsFallback) {
|
|
1020
|
+
recordKiroCalibration(
|
|
1021
|
+
stagedCalibration.conversationId,
|
|
1022
|
+
stagedCalibration.estimated,
|
|
1023
|
+
stagedCalibration.charged,
|
|
1024
|
+
);
|
|
858
1025
|
}
|
|
859
1026
|
for (const event of deferred.splice(0)) {
|
|
860
1027
|
try { yield event; } finally { retention.releaseEvent(event); }
|
|
861
1028
|
}
|
|
862
1029
|
handedOff = true;
|
|
863
|
-
return { ...
|
|
1030
|
+
return { ...result, releaseRetained: () => retention.releaseAll() };
|
|
864
1031
|
} finally {
|
|
865
1032
|
if (!handedOff) retention.releaseAll();
|
|
866
1033
|
}
|
|
@@ -877,6 +1044,7 @@ async function* parseKiroAttemptEvents(
|
|
|
877
1044
|
conversationId: string | undefined,
|
|
878
1045
|
deferred: AdapterEvent[],
|
|
879
1046
|
retention: KiroAttemptRetention,
|
|
1047
|
+
attemptCalibration: { value?: { conversationId: string; estimated: number; charged: number } },
|
|
880
1048
|
contextInputEstimate?: number,
|
|
881
1049
|
priorEmittedOutput = false,
|
|
882
1050
|
): AsyncGenerator<AdapterEvent, KiroAttemptParseResult> {
|
|
@@ -889,6 +1057,12 @@ async function* parseKiroAttemptEvents(
|
|
|
889
1057
|
}
|
|
890
1058
|
|
|
891
1059
|
let open: { id: string; name: string; chunks: string[]; completion: boolean } | null = null;
|
|
1060
|
+
let openCallId: string | undefined;
|
|
1061
|
+
const closeOpenCall = () => {
|
|
1062
|
+
if (!openCallId) return;
|
|
1063
|
+
budget.closeCall(openCallId);
|
|
1064
|
+
openCallId = undefined;
|
|
1065
|
+
};
|
|
892
1066
|
let outputChars = "";
|
|
893
1067
|
let outputCharsBytes = 0;
|
|
894
1068
|
let contextUsagePercentage: number | undefined;
|
|
@@ -916,6 +1090,21 @@ async function* parseKiroAttemptEvents(
|
|
|
916
1090
|
try { yield event; } finally { retention.releaseEvent(event); }
|
|
917
1091
|
}
|
|
918
1092
|
};
|
|
1093
|
+
// A valid private completion supersedes prose staged during the SAME inference. Kiro sometimes
|
|
1094
|
+
// emits answer-shaped text and then calls the terminal tool; forwarding both makes Codex render
|
|
1095
|
+
// two near-identical assistant messages. Drop only staged text on this proven completion path,
|
|
1096
|
+
// preserve non-text events, and release every retained event either way.
|
|
1097
|
+
const consumeSupersededByCompletion = async function* (
|
|
1098
|
+
events: AdapterEvent[],
|
|
1099
|
+
): AsyncGenerator<AdapterEvent> {
|
|
1100
|
+
for (const event of events.splice(0)) {
|
|
1101
|
+
try {
|
|
1102
|
+
if (event.type !== "text_delta") yield event;
|
|
1103
|
+
} finally {
|
|
1104
|
+
retention.releaseEvent(event);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
919
1108
|
|
|
920
1109
|
const providerState = (): { kiro: { conversationId: string } } | undefined =>
|
|
921
1110
|
returnedConversationId ? { kiro: { conversationId: returnedConversationId } } : undefined;
|
|
@@ -1101,7 +1290,7 @@ async function* parseKiroAttemptEvents(
|
|
|
1101
1290
|
if (!open) return { events: [] };
|
|
1102
1291
|
const tool = open;
|
|
1103
1292
|
open = null;
|
|
1104
|
-
|
|
1293
|
+
closeOpenCall();
|
|
1105
1294
|
const input = tool.chunks.join("");
|
|
1106
1295
|
if (!isCompleteKiroToolInput(input)) {
|
|
1107
1296
|
return { events: [], terminal: protocolTerminal(kiroTruncationErrorMessage("incomplete tool input JSON"), tool.completion) };
|
|
@@ -1168,7 +1357,10 @@ async function* parseKiroAttemptEvents(
|
|
|
1168
1357
|
if (ev.stopReason !== undefined) stopReason = ev.stopReason;
|
|
1169
1358
|
break;
|
|
1170
1359
|
case "message_metadata":
|
|
1171
|
-
if (isValidKiroConversationId(ev.conversationId))
|
|
1360
|
+
if (isValidKiroConversationId(ev.conversationId)) {
|
|
1361
|
+
rekeyKiroCalibration(returnedConversationId, ev.conversationId);
|
|
1362
|
+
returnedConversationId = ev.conversationId;
|
|
1363
|
+
}
|
|
1172
1364
|
break;
|
|
1173
1365
|
case "content":
|
|
1174
1366
|
if (ev.modelId) {
|
|
@@ -1191,6 +1383,12 @@ async function* parseKiroAttemptEvents(
|
|
|
1191
1383
|
if (ev.data) {
|
|
1192
1384
|
yield* emitRetained(stage({ type: "reasoning_raw_delta", text: ev.data }));
|
|
1193
1385
|
}
|
|
1386
|
+
if (ev.redactedContent) {
|
|
1387
|
+
yield* emitRetained(stage({ type: "kiro_redacted_reasoning", data: ev.redactedContent }));
|
|
1388
|
+
}
|
|
1389
|
+
break;
|
|
1390
|
+
case "context_usage":
|
|
1391
|
+
if (ev.contextUsagePercentage > 0) contextUsagePercentage = ev.contextUsagePercentage;
|
|
1194
1392
|
break;
|
|
1195
1393
|
case "tool": {
|
|
1196
1394
|
for (const contentEvent of thinking.flush()) {
|
|
@@ -1207,11 +1405,12 @@ async function* parseKiroAttemptEvents(
|
|
|
1207
1405
|
if (started.terminal) return { assistantText, sawReasoning, terminal: started.terminal };
|
|
1208
1406
|
open = started.tool!;
|
|
1209
1407
|
budget.openCall(open.id);
|
|
1408
|
+
openCallId = open.id;
|
|
1210
1409
|
} else if (
|
|
1211
1410
|
(ev.toolUseId && ev.toolUseId !== open.id)
|
|
1212
1411
|
|| (ev.name && open.name !== "unknown" && ev.name !== open.name)
|
|
1213
1412
|
) {
|
|
1214
|
-
|
|
1413
|
+
closeOpenCall();
|
|
1215
1414
|
open = null;
|
|
1216
1415
|
return { assistantText, sawReasoning, terminal: protocolTerminal(kiroTruncationErrorMessage("tool input changed identity before stop")) };
|
|
1217
1416
|
}
|
|
@@ -1290,6 +1489,20 @@ async function* parseKiroAttemptEvents(
|
|
|
1290
1489
|
...(contextWindowState.value ? { upstreamContextWindow: contextWindowState.value } : {}),
|
|
1291
1490
|
});
|
|
1292
1491
|
}
|
|
1492
|
+
// The percentage is an absolute post-response checkpoint. Remove generated output before
|
|
1493
|
+
// comparing it with the request-only estimate, then stage the observation for the outer parser
|
|
1494
|
+
// to commit only if this attempt is terminal (not the first half of a bounded fallback).
|
|
1495
|
+
const chargedTotal = contextUsageTotalFloor();
|
|
1496
|
+
if (chargedTotal !== undefined && contextInputEstimate !== undefined) {
|
|
1497
|
+
const chargedInput = chargedTotal - finalUsage.outputTokens;
|
|
1498
|
+
if (chargedInput > 0 && returnedConversationId) {
|
|
1499
|
+
attemptCalibration.value = {
|
|
1500
|
+
conversationId: returnedConversationId,
|
|
1501
|
+
estimated: contextInputEstimate,
|
|
1502
|
+
charged: chargedInput,
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1293
1506
|
// Native stop metadata proves that this inference ended, but it does not prove that ordinary
|
|
1294
1507
|
// text is a final answer. Kiro has emitted END_TURN for progress prose, so tool-enabled turns
|
|
1295
1508
|
// still require the private completion call to distinguish commentary from completion (#531).
|
|
@@ -1313,12 +1526,13 @@ async function* parseKiroAttemptEvents(
|
|
|
1313
1526
|
});
|
|
1314
1527
|
|
|
1315
1528
|
if (mode === "required") {
|
|
1316
|
-
yield*
|
|
1529
|
+
if (completionAnswer !== undefined) yield* consumeSupersededByCompletion(deferred);
|
|
1530
|
+
else yield* emitRetained(deferred.splice(0));
|
|
1317
1531
|
}
|
|
1318
1532
|
|
|
1319
1533
|
if (mode === "text_fallback") {
|
|
1320
1534
|
if (completionAnswer !== undefined) {
|
|
1321
|
-
yield*
|
|
1535
|
+
yield* consumeSupersededByCompletion(fallbackEvents);
|
|
1322
1536
|
yield { type: "text_delta", text: completionAnswer, phase: "final_answer" };
|
|
1323
1537
|
return {
|
|
1324
1538
|
assistantText,
|
|
@@ -1475,7 +1689,7 @@ async function* parseKiroAttemptEvents(
|
|
|
1475
1689
|
};
|
|
1476
1690
|
} catch (err) {
|
|
1477
1691
|
if (isTranslatorBudgetExceededError(err)) {
|
|
1478
|
-
|
|
1692
|
+
closeOpenCall();
|
|
1479
1693
|
return {
|
|
1480
1694
|
assistantText,
|
|
1481
1695
|
sawReasoning,
|
|
@@ -1517,6 +1731,9 @@ async function* parseKiroAttemptEvents(
|
|
|
1517
1731
|
usage: usage(),
|
|
1518
1732
|
},
|
|
1519
1733
|
};
|
|
1734
|
+
} finally {
|
|
1735
|
+
thinking.dispose();
|
|
1736
|
+
closeOpenCall();
|
|
1520
1737
|
}
|
|
1521
1738
|
}
|
|
1522
1739
|
|
|
@@ -1533,7 +1750,7 @@ export async function* parseKiroStream(
|
|
|
1533
1750
|
contextInputEstimate?: number,
|
|
1534
1751
|
): AsyncGenerator<AdapterEvent> {
|
|
1535
1752
|
const contextWindowState: KiroContextWindowState = { value: contextWindow };
|
|
1536
|
-
const
|
|
1753
|
+
const firstResult = yield* parseKiroAttempt(
|
|
1537
1754
|
response,
|
|
1538
1755
|
budget,
|
|
1539
1756
|
completionMode,
|
|
@@ -1545,12 +1762,6 @@ export async function* parseKiroStream(
|
|
|
1545
1762
|
contextInputEstimate,
|
|
1546
1763
|
false,
|
|
1547
1764
|
);
|
|
1548
|
-
let firstNext = await first.next();
|
|
1549
|
-
while (!firstNext.done) {
|
|
1550
|
-
yield firstNext.value;
|
|
1551
|
-
firstNext = await first.next();
|
|
1552
|
-
}
|
|
1553
|
-
const firstResult = firstNext.value;
|
|
1554
1765
|
try {
|
|
1555
1766
|
if (!firstResult.needsFallback) {
|
|
1556
1767
|
if (firstResult.terminal) yield firstResult.terminal;
|
|
@@ -1628,7 +1839,7 @@ export async function* parseKiroStream(
|
|
|
1628
1839
|
return;
|
|
1629
1840
|
}
|
|
1630
1841
|
|
|
1631
|
-
const
|
|
1842
|
+
const secondResult = yield* parseKiroAttempt(
|
|
1632
1843
|
fallback.response,
|
|
1633
1844
|
budget,
|
|
1634
1845
|
"text_fallback",
|
|
@@ -1642,12 +1853,6 @@ export async function* parseKiroStream(
|
|
|
1642
1853
|
// A zero-output transport failure here must stay non-retryable to avoid duplicating that text.
|
|
1643
1854
|
priorEmittedOutput,
|
|
1644
1855
|
);
|
|
1645
|
-
let secondNext = await second.next();
|
|
1646
|
-
while (!secondNext.done) {
|
|
1647
|
-
yield secondNext.value;
|
|
1648
|
-
secondNext = await second.next();
|
|
1649
|
-
}
|
|
1650
|
-
const secondResult = secondNext.value;
|
|
1651
1856
|
try {
|
|
1652
1857
|
if (!secondResult.terminal) {
|
|
1653
1858
|
yield retryableKiroIncomplete(
|
|
@@ -1716,12 +1921,12 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
1716
1921
|
throw new Error("kiro token missing — run ocx login kiro");
|
|
1717
1922
|
}
|
|
1718
1923
|
const region = resolveKiroApiRegion(parsed._kiroAuthContext);
|
|
1719
|
-
const
|
|
1924
|
+
const requestProfile = resolveKiroRequestProfile(parsed._kiroAuthContext);
|
|
1720
1925
|
const isApiKey = provider.apiKey.trim().startsWith("ksk_");
|
|
1721
|
-
const profileArn = isApiKey ? undefined :
|
|
1722
|
-
// Builder ID
|
|
1723
|
-
//
|
|
1724
|
-
const wireClient: KiroWireClient = isApiKey || !profileArn ? "cli" : "ide";
|
|
1926
|
+
const profileArn = isApiKey ? undefined : requestProfile.profileArn;
|
|
1927
|
+
// A Builder ID service profile does not turn the account into an enterprise identity.
|
|
1928
|
+
// Use the same resolver verdict for the envelope, including legacy accountless calls.
|
|
1929
|
+
const wireClient: KiroWireClient = isApiKey || requestProfile.builderIdFallback || !profileArn ? "cli" : "ide";
|
|
1725
1930
|
const fp = fingerprint().slice(0, 64);
|
|
1726
1931
|
const headers: Record<string, string> = wireClient === "cli" ? {
|
|
1727
1932
|
authorization: `Bearer ${provider.apiKey}`,
|
|
@@ -1748,7 +1953,8 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
1748
1953
|
if (profileArn) headers["x-amzn-kiro-profile-arn"] = profileArn;
|
|
1749
1954
|
const built = buildKiroPayload(parsed, profileArn, forcedCompletionMode, wireClient);
|
|
1750
1955
|
await normalizeKiroImages(built.payload);
|
|
1751
|
-
const
|
|
1956
|
+
const rawContextInputEstimate = estimateKiroPayloadInputTokens(built.payload, parsed.modelId);
|
|
1957
|
+
const contextInputEstimate = calibrateKiroEstimate(built.conversationId, rawContextInputEstimate);
|
|
1752
1958
|
const body = JSON.stringify(built.payload);
|
|
1753
1959
|
debugProviderDiagnostic("kiro", "request", {
|
|
1754
1960
|
region,
|
|
@@ -1855,6 +2061,15 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
1855
2061
|
|
|
1856
2062
|
return {
|
|
1857
2063
|
name: "kiro",
|
|
2064
|
+
// Replayed history that already ends in the answer the user saw is not a new inference turn.
|
|
2065
|
+
// This hook lets the server terminate locally before build/send and, critically, before the
|
|
2066
|
+
// empty-completion guard can reinterpret an outputless terminal as something to retry.
|
|
2067
|
+
localTerminal(parsed: OcxParsedRequest) {
|
|
2068
|
+
return hasTrailingDeliveredFinalAnswer(kiroPayloadMessages(parsed), parsed)
|
|
2069
|
+
? { reason: "kiro_final_answer_already_delivered" }
|
|
2070
|
+
: undefined;
|
|
2071
|
+
},
|
|
2072
|
+
|
|
1858
2073
|
async buildRequest(parsed: OcxParsedRequest, incoming) {
|
|
1859
2074
|
const built = await build(parsed);
|
|
1860
2075
|
modelId = parsed.modelId;
|
|
@@ -1897,25 +2112,32 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
1897
2112
|
return safeKiroHttpErrorMessage(status, headers, payloadText);
|
|
1898
2113
|
},
|
|
1899
2114
|
|
|
1900
|
-
//
|
|
1901
|
-
//
|
|
1902
|
-
//
|
|
1903
|
-
// tool failed with "web-search sidecar requires a non-streaming adapter" (kiro-only).
|
|
2115
|
+
// Kiro always returns an event stream, including for non-streaming Responses requests. Drain
|
|
2116
|
+
// the decoder into a budget-owned batch so an upstream stream cannot grow this array without
|
|
2117
|
+
// bound while the caller waits for the complete JSON response.
|
|
1904
2118
|
async parseResponse(response: Response, budget: TranslatorBudget): Promise<AdapterEvent[]> {
|
|
1905
2119
|
const events: AdapterEvent[] = [];
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
2120
|
+
try {
|
|
2121
|
+
for await (const e of parseKiroStream(
|
|
2122
|
+
response,
|
|
2123
|
+
budget,
|
|
2124
|
+
modelId,
|
|
2125
|
+
inputTokens,
|
|
2126
|
+
contextWindow,
|
|
2127
|
+
toolNameMap,
|
|
2128
|
+
conversationId,
|
|
2129
|
+
completionMode,
|
|
2130
|
+
completionMode === "required" ? fallbackFactory : undefined,
|
|
2131
|
+
contextInputEstimate,
|
|
2132
|
+
)) {
|
|
2133
|
+
retainTranslatedEvent(e, budget, events.at(-1));
|
|
2134
|
+
events.push(e);
|
|
2135
|
+
}
|
|
2136
|
+
return events;
|
|
2137
|
+
} catch (error) {
|
|
2138
|
+
for (const event of events) releaseTranslatedEvent(event, budget);
|
|
2139
|
+
throw error;
|
|
2140
|
+
}
|
|
1919
2141
|
},
|
|
1920
2142
|
};
|
|
1921
2143
|
}
|