2026-01-15 06:29:47 -05:00
import { BaseExecutor } from "./base.js" ;
2026-03-12 05:20:46 -04:00
import { PROVIDERS } from "../config/providers.js" ;
2026-07-29 08:25:45 -04:00
import {
KIRO _CODEWHISPERER _TARGET ,
KIRO _ENDPOINT _FALLBACK _STATUSES ,
resolveKiroModel ,
} from "../config/kiroConstants.js" ;
2026-01-15 06:29:47 -05:00
import { v4 as uuidv4 } from "uuid" ;
2026-01-16 00:39:03 -05:00
import { refreshKiroToken } from "../services/tokenRefresh.js" ;
2026-06-13 10:42:59 -04:00
import { SSE _DONE , SSE _HEADERS } from "../utils/sseConstants.js" ;
2026-06-26 00:17:48 -04:00
import { getCapabilitiesForModel } from "../providers/capabilities.js" ;
2026-07-19 23:52:27 -04:00
import { STREAM _FIRST _CHUNK _TIMEOUT _MS } from "../config/runtimeConfig.js" ;
const KIRO _REPAIR _BUFFER _MAX _BYTES = 8 * 1024 * 1024 ;
const KIRO _REPAIR _HEARTBEAT _MS = 10_000 ;
const KIRO _SHORT _FINAL _MAX _CHARS = 800 ;
const EVENTSTREAM _MAX _MESSAGE _BYTES = 24 * 1024 * 1024 ;
const EVENTSTREAM _MAX _HEADERS _BYTES = 128 * 1024 ;
const KIRO _EVENT _TYPES = new Set ( [
"assistantResponseEvent" ,
"reasoningContentEvent" ,
"codeEvent" ,
"toolUseEvent" ,
"messageStopEvent" ,
"metadataEvent" ,
"MetadataEvent" ,
"contextUsageEvent" ,
"meteringEvent" ,
"metricsEvent"
] ) ;
const encoder = new TextEncoder ( ) ;
const decoder = new TextDecoder ( ) ;
const CRC32 _TABLE = Uint32Array . from ( { length : 256 } , ( _ , index ) => {
let value = index ;
for ( let bit = 0 ; bit < 8 ; bit ++ ) {
value = ( value >>> 1 ) ^ ( ( value & 1 ) ? 0xedb88320 : 0 ) ;
}
return value >>> 0 ;
} ) ;
const REPAIR _INSTRUCTIONS = Object . freeze ( {
tool : "Retry the previous response because its Kiro tool_call wrapper was malformed. If you use the wrapper tool named tool_call, its input must contain a non-empty name and an arguments field." ,
ellipsis : "Retry the previous response because it ended with only an ellipsis. Return the complete final answer, not only ... or …." ,
short _final : "Retry the previous response because its final only announced a future action. Complete the check now and return the result or a concrete blocker."
} ) ;
const SHORT _FUTURE _ACTION = /^(?:(?:(?:現在|接著|接下來|下一步)[, ,:: \s]*(?:我(?:只)?(?:會|要|將|再)?\s*)?|我只再)(?:補|查|確認|驗證|追(?:查|蹤)?|繼續|檢查|測試)|我(?:會|要|將)(?:再|重新)?(?:補(?:齊|查)?|抓取|查(?:詢)?|確認|驗證|追(?:查|蹤)?|繼續|檢查|測試)|(?:(?:next|now|then)\b[\s,:-]*)?(?:i(?:'ll| will| am going to| need to)|let me)\s+(?:verify|check|confirm|validate|investigate|trace|continue|follow up|test)\b)/iu ;
// Keep this tied to the observed whole-response signature. Broader Chinese
// result/progress heuristics create false positives for completed findings.
const OBSERVED _TRAILING _FUTURE _ACTION = /^目前證據顯示[\s\S]{1,700}[。.!?; ;]\s*最後補查\s+504\s+access\s+log[, ,]\s*確認\s+host[/ /]路徑與是否為集中流量[。.!]?$/iu ;
const ENGLISH _FUTURE _ACTION = /^(?:(?:next|now|then)\b[\s,:-]*)?(?:i(?:'ll| will| am going to| need to)|let me)\s+(?:verify|check|confirm|validate|investigate|trace|continue|follow up|test)\b/iu ;
const ENGLISH _RESULT _CLAUSE = /(?:[:;\n]|[.!?]\s+\S|\b(?:status|checksum|response|deployment)\s+(?:is|are|was|were|matches?|equals?|returned)\b)/iu ;
const CHINESE _FUTURE _ACTION = /^(?:(?:現在|接著|接下來|下一步)[, ,:: \s]*(?:我(?:只)?(?:會|要|將|再)?\s*)?|我只再|我(?:會|要|將)(?:再|重新)?)(?:補|抓取|查|確認|驗證|追|繼續|檢查|測試)/u ;
const CHINESE _RESULT _CLAUSE = /(?:[。!?]\s*\S|(?:版本|狀態|回應|結果|部署|校驗碼)(?:是|為|等於|顯示))/u ;
const USER _WAIT = /(?:請(?:你|先)|你(?:先|需要|可以|提供|確認|批准|允許)|等待(?:你|使用者)|等你|核准|同意|授權|\b(?:after|when|once)\s+you\b|\byour\s+(?:approval|confirmation|permission|input)\b|\bwait(?:ing)?\s+for\s+you\b|\bplease\s+(?:approve|confirm|provide|send)\b)/iu ;
const COMPLETED _FINAL = /(?:已(?:經)?完成|完成(?:了|驗證|確認)|修復完成|確認無誤|驗證(?:完成|通過)|測試(?:均)?通過|結論|總結|\b(?:done|completed|fixed|verified|confirmed|passed|in conclusion|summary)\b|\b(?:is|are) complete\b)/iu ;
const RESULT _EVIDENCE = /(?:顯示|發現|因此|成功|失敗|正常|無錯誤|沒有錯誤|\b(?:found|shows?|showed|because|therefore|succeeded|failed|healthy|green|no errors?)\b)/iu ;
function crc32 ( bytes ) {
let crc = 0xffffffff ;
for ( const byte of bytes ) crc = CRC32 _TABLE [ ( crc ^ byte ) & 0xff ] ^ ( crc >>> 8 ) ;
return ( crc ^ 0xffffffff ) >>> 0 ;
}
function envPositiveInt ( name , fallback ) {
const parsed = Number . parseInt ( process . env ? . [ name ] || "" , 10 ) ;
return Number . isFinite ( parsed ) && parsed > 0 ? parsed : fallback ;
}
function concatChunks ( chunks , totalBytes ) {
const output = new Uint8Array ( totalBytes ) ;
let offset = 0 ;
for ( const chunk of chunks ) {
output . set ( chunk , offset ) ;
offset += chunk . byteLength ;
}
return output ;
}
function makeAbortError ( reason ) {
const error = new Error ( reason ? . message || reason || "Request aborted" ) ;
error . name = "AbortError" ;
return error ;
}
async function readWithTimeout ( reader , signal , timeoutMs , message ) {
if ( signal ? . aborted ) throw makeAbortError ( signal . reason ) ;
let timeout ;
let abortHandler ;
const timeoutPromise = new Promise ( ( _ , reject ) => {
timeout = setTimeout ( ( ) => reject ( new Error ( message ) ) , timeoutMs ) ;
} ) ;
const abortPromise = new Promise ( ( _ , reject ) => {
abortHandler = ( ) => reject ( makeAbortError ( signal . reason ) ) ;
signal ? . addEventListener ( "abort" , abortHandler , { once : true } ) ;
} ) ;
try {
return await Promise . race ( [ reader . read ( ) , timeoutPromise , abortPromise ] ) ;
} finally {
clearTimeout ( timeout ) ;
signal ? . removeEventListener ? . ( "abort" , abortHandler ) ;
}
}
async function readResponsePrefix ( response , signal , maxBytes , timeoutMs ) {
const reader = response ? . body ? . getReader ? . ( ) ;
if ( ! reader ) return "" ;
const chunks = [ ] ;
let totalBytes = 0 ;
try {
while ( totalBytes < maxBytes ) {
const { done , value } = await readWithTimeout (
reader ,
signal ,
timeoutMs ,
"Kiro retry error body stalled"
) ;
if ( done ) break ;
const remaining = maxBytes - totalBytes ;
const chunk = value . byteLength > remaining ? value . slice ( 0 , remaining ) : value ;
chunks . push ( chunk ) ;
totalBytes += chunk . byteLength ;
if ( value . byteLength > remaining ) break ;
}
} finally {
await reader . cancel ( "bounded Kiro retry error body" ) . catch ( ( ) => { } ) ;
}
return decoder . decode ( concatChunks ( chunks , totalBytes ) ) ;
}
function appendRepairInstruction ( body , kind ) {
const repaired = structuredClone ( body || { } ) ;
const instruction = REPAIR _INSTRUCTIONS [ kind ] || "Retry the previous incomplete Kiro response." ;
repaired . systemPrompt = repaired . systemPrompt
? ` ${ repaired . systemPrompt } \n \n ${ instruction } `
: instruction ;
return repaired ;
}
function normalizeStopReason ( value ) {
const reason = String ( value || "" ) . trim ( ) . replace ( /([a-z])([A-Z])/g , "$1_$2" ) . toLowerCase ( ) . replace ( /[\s-]+/g , "_" ) ;
if ( [ "endturn" , "end_turn" , "stop" , "stop_sequence" ] . includes ( reason ) ) return "end_turn" ;
if ( [ "tooluse" , "tool_use" , "tool_calls" ] . includes ( reason ) ) return "tool_use" ;
if ( [ "maxtokens" , "max_tokens" , "max_output_tokens" , "length" ] . includes ( reason ) ) return "max_tokens" ;
return reason || null ;
}
2026-08-13 00:32:09 -04:00
// Of the reasons stopDisposition() folds into "terminal_incomplete", only these
// mean "usable as far as it got, then the budget ran out" -- the case
// finish_reason "length" exists for. cancelled / pause_turn are abandoned turns
// whose partial content must stay private, so they are deliberately absent.
const KIRO _TRUNCATION _STOP _REASONS = new Set ( [ "model_context_window_exceeded" , "max_tokens" ] ) ;
2026-07-19 23:52:27 -04:00
function stopDisposition ( stopReason , hasToolCalls ) {
if ( [ "malformed_model_output" , "invalid_model_output" ] . includes ( stopReason ) ) return "retryable_protocol_failure" ;
if ( [ "cancelled" , "pause_turn" , "model_context_window_exceeded" ] . includes ( stopReason ) ) return "terminal_incomplete" ;
if ( stopReason === "refusal" || /(?:content.*filter|guardrail|safety|policy|blocked)/u . test ( stopReason ) ) return "terminal_refusal" ;
if ( stopReason === "max_tokens" ) return hasToolCalls ? "terminal_incomplete" : "length" ;
if ( stopReason && ! [ "end_turn" , "tool_use" ] . includes ( stopReason ) ) return "unknown_failure" ;
if ( hasToolCalls || stopReason === "tool_use" ) return "tool_use" ;
if ( ! stopReason || stopReason === "end_turn" ) return "complete" ;
return "unknown_failure" ;
}
function mergeStopReason ( current , incoming ) {
if ( ! incoming ) return current ;
if ( ! current ) return incoming ;
const severity = ( reason ) => {
const disposition = stopDisposition ( reason , false ) ;
if ( disposition === "terminal_refusal" ) return 6 ;
if ( disposition === "terminal_incomplete" ) return 5 ;
if ( disposition === "unknown_failure" ) return 4 ;
if ( disposition === "retryable_protocol_failure" ) return 3 ;
if ( disposition === "length" ) return 2 ;
return 1 ;
} ;
return severity ( incoming ) > severity ( current ) ? incoming : current ;
}
function isEllipsisOnly ( value ) {
return [ "..." , "…" ] . includes ( String ( value || "" ) . trim ( ) ) ;
}
function isShortFutureAction ( value ) {
const text = String ( value || "" ) . trim ( ) . replaceAll ( "’ " , "'" ) ;
if ( OBSERVED _TRAILING _FUTURE _ACTION . test ( text ) ) return true ;
if ( ENGLISH _FUTURE _ACTION . test ( text ) && ENGLISH _RESULT _CLAUSE . test ( text ) ) return false ;
if ( CHINESE _FUTURE _ACTION . test ( text ) && CHINESE _RESULT _CLAUSE . test ( text ) ) return false ;
return text . length > 0 && text . length <= KIRO _SHORT _FINAL _MAX _CHARS &&
SHORT _FUTURE _ACTION . test ( text ) && ! USER _WAIT . test ( text ) &&
! COMPLETED _FINAL . test ( text ) && ! RESULT _EVIDENCE . test ( text ) ;
}
function encodeSSEError ( code , message , details ) {
return encoder . encode ( ` data: ${ JSON . stringify ( { error : {
message ,
type : "upstream_error" ,
code ,
... ( details ? { details } : { } )
} } ) } \ n \ ndata : [ DONE ] \ n \ n ` );
}
function inspectSSEChunk ( chunk , state ) {
for ( const line of decoder . decode ( chunk ) . split ( "\n" ) ) {
if ( ! line . startsWith ( "data: " ) ) continue ;
const data = line . slice ( 6 ) . trim ( ) ;
if ( ! data || data === "[DONE]" ) continue ;
try {
const event = JSON . parse ( data ) ;
if ( event . error ) state . error = event . error ;
for ( const choice of event . choices || [ ] ) {
const delta = choice . delta || { } ;
if ( typeof delta . content === "string" ) state . content += delta . content ;
if ( typeof delta . reasoning _content === "string" ) state . reasoning += delta . reasoning _content ;
if ( delta . tool _calls ? . length ) state . hasToolCalls = true ;
}
} catch { /* a malformed SSE line is diagnosed by the transformer */ }
}
}
2026-01-15 06:29:47 -05:00
/ * *
* KiroExecutor - Executor for Kiro AI ( AWS CodeWhisperer )
* Uses AWS CodeWhisperer streaming API with AWS EventStream binary format
* /
export class KiroExecutor extends BaseExecutor {
constructor ( ) {
super ( "kiro" , PROVIDERS . kiro ) ;
}
2026-07-29 08:25:45 -04:00
buildHeaders ( credentials , stream = true , url = "" ) {
2026-01-15 06:29:47 -05:00
const headers = {
... this . config . headers ,
"Amz-Sdk-Request" : "attempt=1; max=3" ,
"Amz-Sdk-Invocation-Id" : uuidv4 ( )
} ;
2026-07-29 08:25:45 -04:00
if ( url . includes ( "://codewhisperer." ) ) {
headers [ "X-Amz-Target" ] = KIRO _CODEWHISPERER _TARGET ;
} else {
delete headers [ "X-Amz-Target" ] ;
}
2026-01-15 06:29:47 -05:00
2026-06-16 23:01:30 -04:00
// API-key auth: the key is stored as accessToken and sent as a bearer token
// exactly like an OAuth access token, but with an extra `tokentype: API_KEY`
// header so CodeWhisperer treats it as a long-lived API key rather than an
// OIDC/social access token. Mirrors the Kiro IDE headless-auth behavior.
2026-06-26 00:39:33 -04:00
// Enterprise / Microsoft Entra (external_idp) tokens are OAuth access tokens,
// but CodeWhisperer requires TokenType=EXTERNAL_IDP to bind them to profiles.
const authMethod = credentials ? . providerSpecificData ? . authMethod ;
const isApiKey = authMethod === "api_key" ;
const isExternalIdp = authMethod === "external_idp" ;
2026-06-16 23:01:30 -04:00
const apiKey = credentials ? . apiKey || ( isApiKey ? credentials ? . accessToken : null ) ;
if ( isApiKey && apiKey ) {
headers [ "Authorization" ] = ` Bearer ${ apiKey } ` ;
2026-07-29 08:25:45 -04:00
headers [ "TokenType" ] = "API_KEY" ;
} else if ( credentials ? . accessToken ) {
2026-01-15 06:29:47 -05:00
headers [ "Authorization" ] = ` Bearer ${ credentials . accessToken } ` ;
2026-06-26 00:39:33 -04:00
if ( isExternalIdp ) {
headers [ "TokenType" ] = "EXTERNAL_IDP" ;
}
2026-01-15 06:29:47 -05:00
}
return headers ;
}
2026-06-16 23:01:30 -04:00
/ * *
* Auth - aware endpoint ordering .
*
2026-07-29 08:25:45 -04:00
* API - key Kiro connections use the Amazon Q surface . The legacy
* codewhisperer . * GenerateAssistantResponse endpoint can authenticate the key
* but rejects the same valid payload with REQUEST _BODY _INVALID . Since a 400
* is terminal in BaseExecutor , putting CodeWhisperer first prevents the working
* q . * endpoint from ever being tried . Keep q . * first only for api _key accounts .
*
2026-06-16 23:01:30 -04:00
* The Kiro IDE gateway ( runtime . * . kiro . dev ) expects Kiro OIDC / social tokens
2026-07-29 08:25:45 -04:00
* and rejects TokenType = API _KEY . External IdP enterprise tokens instead
2026-06-26 00:39:33 -04:00
* use the CodeWhisperer surface , with the ` TokenType: EXTERNAL_IDP ` header .
* Other OAuth methods keep the default order ( kiro . dev first ) since their
* tokens are what that gateway accepts .
2026-06-16 23:01:30 -04:00
* /
getOrderedBaseUrls ( credentials ) {
const baseUrls = this . getBaseUrls ( ) ;
2026-06-26 00:39:33 -04:00
const authMethod = credentials ? . providerSpecificData ? . authMethod ;
2026-07-03 00:05:32 -04:00
// IAM Identity Center (idc) tokens are AWS SSO access tokens — the same
// family as external_idp/api_key. The kiro.dev gateway rejects them with
// 403 "bearer token invalid", so they must hit the CodeWhisperer
// *.amazonaws.com surface, and in the region the token was minted in
// (the baseUrls are hardcoded us-east-1).
const isCodeWhispererSurface =
authMethod === "api_key" || authMethod === "external_idp" || authMethod === "idc" ;
2026-06-26 00:39:33 -04:00
if ( ! isCodeWhispererSurface ) return baseUrls ;
2026-07-03 00:05:32 -04:00
const region = ( credentials ? . providerSpecificData ? . region || "us-east-1" ) . trim ( ) ;
const regionalize = ( u ) =>
region && region !== "us-east-1" && u . includes ( "amazonaws.com" )
? u . replace ( /([a-z]+)\.[a-z0-9-]+\.amazonaws\.com/ , ` $ 1. ${ region } .amazonaws.com ` )
: u ;
const amazon = baseUrls . filter ( ( u ) => u . includes ( "amazonaws.com" ) ) . map ( regionalize ) ;
2026-06-16 23:01:30 -04:00
const others = baseUrls . filter ( ( u ) => ! u . includes ( "amazonaws.com" ) ) ;
2026-07-29 08:25:45 -04:00
if ( authMethod === "api_key" ) {
const q = amazon . filter ( ( u ) => u . includes ( "://q." ) ) ;
const remaining = amazon . filter ( ( u ) => ! u . includes ( "://q." ) ) ;
return q . length > 0
? [ ... q , ... remaining , ... others ]
: [ ... amazon , ... others ] ;
}
2026-06-16 23:01:30 -04:00
return amazon . length > 0 ? [ ... amazon , ... others ] : baseUrls ;
}
buildUrl ( model , stream , urlIndex = 0 , credentials = null ) {
const baseUrls = this . getOrderedBaseUrls ( credentials ) ;
return baseUrls [ urlIndex ] || baseUrls [ 0 ] || this . config . baseUrl ;
}
2026-07-29 08:25:45 -04:00
// Retry only endpoint/auth-surface failures. Payload-invalid HTTP 400 must be
// terminal: sending the same malformed body to every surface cannot repair it.
shouldRetry ( status , urlIndex ) {
const hasFallback = urlIndex + 1 < this . getFallbackCount ( ) ;
return super . shouldRetry ( status , urlIndex )
|| ( hasFallback && KIRO _ENDPOINT _FALLBACK _STATUSES . has ( status ) ) ;
}
2026-01-15 06:29:47 -05:00
transformRequest ( model , body , stream , credentials ) {
return body ;
}
/ * *
2026-06-07 22:44:53 -04:00
* Kiro execute — delegate to BaseExecutor for endpoint fallback + retry , then
* transform the binary AWS EventStream into OpenAI - shaped SSE on success .
*
* BaseExecutor . execute ( ) walks config . baseUrls ( runtime . us - east - 1. kiro . dev →
* codewhisperer → q ) advancing to the next host on 429 ( shouldRetry ) and on
* network / 5 xx errors , while tryRetry handles in - place retries per ` retry: {429: 2} ` .
2026-06-16 23:01:30 -04:00
* Note : api - key connections reorder these so the * . amazonaws . com hosts come
* first — see getOrderedBaseUrls / buildUrl above .
2026-06-07 22:44:53 -04:00
* Note : the baseUrls are alternate surfaces of one regional service , so rotation
* is edge - level failover — it does not grant fresh 429 quota . Per - account 429
* spreading is handled upstream by account rotation in sse / handlers / chat . js .
*
* Errors are returned untransformed so the upstream handler can read the body ,
* classify the status , and trigger account fallback / cooldown .
2026-01-15 06:29:47 -05:00
* /
2026-06-07 22:44:53 -04:00
async execute ( args ) {
const result = await super . execute ( args ) ;
2026-07-19 23:52:27 -04:00
if ( result ? . response ? . ok ) this . attachIntegrityGate ( result , args ) ;
2026-06-07 22:44:53 -04:00
return result ;
2026-01-15 06:29:47 -05:00
}
2026-07-19 23:52:27 -04:00
attachIntegrityGate ( result , args ) {
const abortController = new AbortController ( ) ;
const maxBytes = envPositiveInt ( "KIRO_TOOL_CALL_REPAIR_BUFFER_MAX_BYTES" , KIRO _REPAIR _BUFFER _MAX _BYTES ) ;
const legacyTimeout = envPositiveInt ( "KIRO_TOOL_CALL_REPAIR_TIMEOUT_MS" , STREAM _FIRST _CHUNK _TIMEOUT _MS ) ;
const ttftTimeoutMs = envPositiveInt ( "KIRO_TOOL_CALL_REPAIR_TTFT_TIMEOUT_MS" , legacyTimeout ) ;
const stallTimeoutMs = envPositiveInt ( "KIRO_TOOL_CALL_REPAIR_STALL_TIMEOUT_MS" , legacyTimeout ) ;
const repairEnabled = args . credentials ? . providerSpecificData ? . kiroToolCallRepair !== false &&
process . env . KIRO _TOOL _CALL _REPAIR !== "false" ;
const forwardAbort = ( ) => abortController . abort ( args . signal ? . reason ) ;
args . signal ? . addEventListener ( "abort" , forwardAbort , { once : true } ) ;
let open = true ;
let heartbeatTimer ;
const stream = new ReadableStream ( {
start : async ( controller ) => {
const heartbeat = ( ) => {
if ( ! open ) return ;
try {
controller . enqueue ( encoder . encode ( ": kiro-validation\n\n" ) ) ;
} catch {
open = false ;
}
} ;
heartbeat ( ) ;
heartbeatTimer = setInterval ( heartbeat , KIRO _REPAIR _HEARTBEAT _MS ) ;
try {
const bytes = await this . runIntegrityRecovery ( result . response , args , {
signal : abortController . signal ,
maxBytes ,
ttftTimeoutMs ,
stallTimeoutMs ,
repairEnabled
} ) ;
if ( abortController . signal . aborted ) throw makeAbortError ( abortController . signal . reason ) ;
controller . enqueue ( bytes ) ;
controller . close ( ) ;
} catch ( error ) {
if ( open && error . name === "AbortError" ) {
controller . error ( error ) ;
} else if ( open && error . name !== "AbortError" ) {
controller . enqueue ( encodeSSEError (
"kiro_integrity_gate_failed" ,
error . message || "Kiro integrity validation failed"
) ) ;
controller . close ( ) ;
}
} finally {
open = false ;
clearInterval ( heartbeatTimer ) ;
args . signal ? . removeEventListener ? . ( "abort" , forwardAbort ) ;
}
} ,
cancel ( reason ) {
open = false ;
clearInterval ( heartbeatTimer ) ;
abortController . abort ( reason || "client cancelled" ) ;
}
} ) ;
result . response = new Response ( stream , {
status : result . response . status ,
statusText : result . response . statusText ,
headers : { ... SSE _HEADERS }
} ) ;
}
async runIntegrityRecovery ( rawResponse , args , options ) {
const first = await this . readRecoverableIntegrityAttempt (
rawResponse ,
args . model ,
options ,
"initial"
) ;
if ( first . kind === "complete" ) return first . bytes ;
if ( first . kind === "terminal_stop" || first . kind === "upstream_error" ) {
return this . integrityFailureSSE ( first ) ;
}
if ( first . kind === "invalid_tool" && ! options . repairEnabled ) {
return encodeSSEError ( "invalid_kiro_tool_call" , first . message , first . diagnostics ) ;
}
const repairKind = [ "ellipsis" , "short_final" , "invalid_tool" ] . includes ( first . kind )
? first . kind
: null ;
const repairBody = repairKind
? appendRepairInstruction ( args . body , repairKind === "invalid_tool" ? "tool" : repairKind )
: structuredClone ( args . body || { } ) ;
const retry = await BaseExecutor . prototype . execute . call ( this , {
... args ,
body : repairBody ,
signal : options . signal
} ) ;
if ( ! retry ? . response ? . ok ) {
let body = "" ;
try {
body = await readResponsePrefix (
retry ? . response ,
options . signal ,
Math . min ( options . maxBytes , 4096 ) ,
options . stallTimeoutMs
) ;
} catch ( error ) {
if ( error . name === "AbortError" ) throw error ;
}
return encodeSSEError (
"kiro_integrity_retry_upstream_error" ,
body || ` Kiro integrity retry failed with HTTP ${ retry ? . response ? . status || 502 } ` ,
{ status : retry ? . response ? . status || 502 }
) ;
}
const second = await this . readRecoverableIntegrityAttempt (
retry . response ,
args . model ,
options ,
"retry"
) ;
if ( second . kind === "complete" ) return second . bytes ;
if ( second . kind === "terminal_stop" || second . kind === "upstream_error" ) {
return this . integrityFailureSSE ( second ) ;
}
const code = second . kind === "ellipsis"
? "kiro_ellipsis_retry_failed"
: second . kind === "short_final"
? "kiro_short_final_retry_failed"
: second . kind === "invalid_tool"
? "kiro_tool_call_repair_retry_failed"
: "kiro_missing_terminal_retry_failed" ;
return encodeSSEError (
code ,
` Kiro integrity validation failed after one bounded retry: ${ second . message || second . kind } ` ,
{ attempts : [ first . diagnostics , second . diagnostics ] . filter ( Boolean ) }
) ;
}
integrityFailureSSE ( attempt ) {
const disposition = attempt . diagnostics ? . stop _disposition ;
const code = attempt . diagnostics ? . terminal _provenance === "integrity_buffer_exceeded"
? "kiro_integrity_buffer_exceeded"
: attempt . kind === "upstream_error"
? "kiro_upstream_eventstream_error"
: disposition === "terminal_refusal"
? "kiro_terminal_refusal"
: disposition === "terminal_incomplete"
? "kiro_terminal_incomplete"
: "kiro_unknown_stop_reason" ;
return encodeSSEError ( code , attempt . message || "Kiro stream ended with a terminal failure" , attempt . diagnostics ) ;
}
async readRecoverableIntegrityAttempt ( rawResponse , model , options , attempt ) {
try {
return await this . readIntegrityAttempt ( rawResponse , model , options , attempt ) ;
} catch ( error ) {
if ( error . name === "AbortError" ) throw error ;
return {
kind : "missing_terminal" ,
message : error . message || "Kiro transport read failed" ,
diagnostics : {
attempt ,
terminal _provenance : "transport_read_error" ,
transport _state : "upstream_error" ,
stop _reason : null ,
stop _disposition : "terminal_incomplete" ,
response _state : "no_semantic_output" ,
event _counts : { } ,
incomplete _frame _bytes : 0
}
} ;
}
}
async readIntegrityAttempt ( rawResponse , model , options , attempt ) {
let diagnostics ;
const transformed = this . transformEventStreamToSSE ( rawResponse , model , {
maxToolBytes : Math . max ( 1 , Math . floor ( options . maxBytes / 2 ) ) ,
onTerminalState : ( value ) => {
diagnostics = value ;
}
} ) ;
const reader = transformed . body . getReader ( ) ;
const chunks = [ ] ;
let totalBytes = 0 ;
let sawChunk = false ;
const output = { content : "" , reasoning : "" , hasToolCalls : false , error : null } ;
try {
while ( true ) {
const timeoutMs = sawChunk ? options . stallTimeoutMs : options . ttftTimeoutMs ;
const phase = sawChunk ? "stalled" : "timed out before first chunk" ;
const { done , value } = await readWithTimeout (
reader ,
options . signal ,
timeoutMs ,
` Kiro integrity validation ${ phase } `
) ;
if ( done ) break ;
sawChunk = true ;
totalBytes += value . byteLength ;
if ( totalBytes > options . maxBytes ) {
await reader . cancel ( "kiro_integrity_buffer_exceeded" ) . catch ( ( ) => { } ) ;
return {
kind : "terminal_stop" ,
message : ` Kiro integrity buffer exceeded ${ options . maxBytes } bytes ` ,
diagnostics : { terminal _provenance : "integrity_buffer_exceeded" }
} ;
}
chunks . push ( value ) ;
inspectSSEChunk ( value , output ) ;
}
} catch ( error ) {
await reader . cancel ( error . message ) . catch ( ( ) => { } ) ;
throw error ;
}
const safeDiagnostics = {
attempt ,
terminal _provenance : diagnostics ? . terminal _provenance || "missing_terminal_diagnostics" ,
transport _state : diagnostics ? . transport _state || "unknown" ,
stop _reason : diagnostics ? . stop _reason || null ,
stop _disposition : diagnostics ? . stop _disposition || "terminal_incomplete" ,
response _state : diagnostics ? . response _state || "no_semantic_output" ,
event _counts : diagnostics ? . event _counts || { } ,
incomplete _frame _bytes : diagnostics ? . incomplete _frame _bytes || 0
} ;
if ( safeDiagnostics . stop _disposition === "retryable_protocol_failure" ) {
const kind = safeDiagnostics . terminal _provenance === "invalid_tool_call"
? "invalid_tool"
: "retryable_stop" ;
return { kind , message : output . error ? . message , diagnostics : safeDiagnostics } ;
}
if ( safeDiagnostics . stop _disposition === "terminal_incomplete" ||
safeDiagnostics . stop _disposition === "terminal_refusal" ||
safeDiagnostics . stop _disposition === "unknown_failure" ) {
const kind = safeDiagnostics . terminal _provenance === "upstream_eventstream_error"
? "upstream_error"
: safeDiagnostics . terminal _provenance === "integrity_buffer_exceeded"
? "terminal_stop"
: [ "metadata_stop_reason" , "message_stop_event" ] . includes ( safeDiagnostics . terminal _provenance )
? "terminal_stop"
: "missing_terminal" ;
return { kind , message : output . error ? . message , diagnostics : safeDiagnostics } ;
}
if ( output . error ) {
return { kind : "missing_terminal" , message : output . error . message , diagnostics : safeDiagnostics } ;
}
if ( ! output . hasToolCalls ) {
if ( isEllipsisOnly ( output . content ) ||
( ! output . content . trim ( ) && isEllipsisOnly ( output . reasoning ) ) ) {
return { kind : "ellipsis" , diagnostics : safeDiagnostics } ;
}
if ( isShortFutureAction ( output . content ) ) {
return { kind : "short_final" , diagnostics : safeDiagnostics } ;
}
}
return { kind : "complete" , bytes : concatChunks ( chunks , totalBytes ) , diagnostics : safeDiagnostics } ;
}
transformEventStreamToSSE ( response , model , options = { } ) {
2026-01-15 06:29:47 -05:00
const responseId = ` chatcmpl- ${ Date . now ( ) } ` ;
const created = Math . floor ( Date . now ( ) / 1000 ) ;
2026-06-26 00:17:48 -04:00
const capabilityModel = resolveKiroModel ( model ) . upstream ;
const contextWindow = getCapabilitiesForModel ( "kiro" , capabilityModel ) . contextWindow || 200000 ;
2026-07-19 23:52:27 -04:00
const eventCounts = { } ;
2026-01-15 06:29:47 -05:00
const state = {
2026-07-19 23:52:27 -04:00
buffer : new Uint8Array ( 0 ) ,
chunkIndex : 0 ,
toolCounter : 0 ,
tools : new Map ( ) ,
bufferedToolBytes : 0 ,
hasText : false ,
hasReasoning : false ,
hasCode : false ,
2026-01-15 06:29:47 -05:00
hasToolCalls : false ,
2026-07-19 23:52:27 -04:00
sawToolUse : false ,
explicitStop : false ,
stopReason : null ,
terminalProvenance : null ,
transportState : "consuming_response" ,
totalContentLength : 0 ,
contextUsagePercentage : 0 ,
hasContextUsage : false ,
hasMetering : false ,
usage : null ,
inThinking : false ,
toolValidationError : null ,
validatedFrames : 0 ,
finished : false
2026-01-15 06:29:47 -05:00
} ;
2026-07-19 23:52:27 -04:00
const diagnostics = ( overrides = { } ) => ( {
terminal _provenance : state . terminalProvenance || "clean_eventstream_eof" ,
transport _state : state . transportState ,
stop _reason : state . stopReason ,
stop _disposition : stopDisposition ( state . stopReason , state . hasToolCalls ) ,
response _state : state . hasToolCalls
? "valid_tool"
: state . hasText || state . hasReasoning || state . hasCode
? "text_reasoning"
: state . explicitStop
? "explicit_stop"
: "no_semantic_output" ,
event _counts : { ... eventCounts } ,
incomplete _frame _bytes : state . buffer . byteLength ,
... overrides
} ) ;
const sseChunk = ( delta , finishReason = null , usage ) => encoder . encode ( ` data: ${ JSON . stringify ( {
id : responseId ,
object : "chat.completion.chunk" ,
created ,
model ,
choices : [ { index : 0 , delta , finish _reason : finishReason } ] ,
... ( usage ? { usage } : { } )
} ) } \ n \ n ` );
const emitDelta = ( controller , delta ) => {
if ( state . chunkIndex === 0 ) delta = { role : "assistant" , ... delta } ;
state . chunkIndex ++ ;
controller . enqueue ( sseChunk ( delta ) ) ;
} ;
const fail = ( controller , provenance , code , message , extra = { } ) => {
state . finished = true ;
state . terminalProvenance = provenance ;
state . transportState = extra . transport _state || "corrupt_frame" ;
const detail = diagnostics ( {
stop _disposition : extra . stop _disposition || "terminal_incomplete" ,
... extra
} ) ;
options . onTerminalState ? . ( detail ) ;
controller . enqueue ( encodeSSEError ( code , message , detail ) ) ;
} ;
const assertToolBufferBound = ( ) => {
if ( state . bufferedToolBytes <= ( options . maxToolBytes || KIRO _REPAIR _BUFFER _MAX _BYTES / 2 ) ) return ;
const error = new Error ( "Kiro buffered tool input exceeded the integrity memory bound" ) ;
error . code = "KIRO_BUFFER_EXCEEDED" ;
throw error ;
} ;
const appendToolInput = ( tool , input ) => {
if ( input === undefined ) return ;
if ( typeof input === "string" ) {
if ( tool . inputKind && tool . inputKind !== "string" ) throw new Error ( "Kiro tool input changed fragment type" ) ;
tool . inputKind = "string" ;
tool . inputChunks || = [ ] ;
tool . inputChunks . push ( input ) ;
state . bufferedToolBytes += encoder . encode ( input ) . byteLength ;
} else if ( input && typeof input === "object" && ! Array . isArray ( input ) ) {
if ( tool . inputKind && tool . inputKind !== "object" ) throw new Error ( "Kiro tool input changed fragment type" ) ;
tool . inputKind = "object" ;
state . bufferedToolBytes -= tool . inputBytes || 0 ;
tool . inputObject = input ;
tool . inputBytes = encoder . encode ( JSON . stringify ( input ) ) . byteLength ;
state . bufferedToolBytes += tool . inputBytes ;
} else {
throw new Error ( "Kiro tool input must be a JSON object" ) ;
}
assertToolBufferBound ( ) ;
} ;
const parsedToolInput = ( tool ) => {
if ( ! tool . inputKind ) throw new Error ( "Kiro tool call is missing input" ) ;
if ( tool . inputKind === "object" ) return tool . inputObject ;
try {
const input = JSON . parse ( tool . inputChunks . join ( "" ) ) ;
if ( ! input || typeof input !== "object" || Array . isArray ( input ) ) throw new Error ( "not an object" ) ;
return input ;
} catch ( error ) {
throw new Error ( ` Kiro tool input must be valid object JSON ( ${ error . message } ) ` ) ;
}
} ;
const emitTools = ( controller ) => {
for ( const tool of state . tools . values ( ) ) {
2026-08-13 00:32:09 -04:00
// Validate per tool, not per turn: one unusable fragment used to throw out
// of emitTools and take every other complete tool call in the same turn
// with it, which the client saw as a turn that answered nothing.
let input ;
try {
input = parsedToolInput ( tool ) ;
if ( tool . name === "tool_call" ) {
if ( typeof input . name !== "string" || ! input . name . trim ( ) ) {
throw new Error ( "Invalid Kiro tool_call payload: missing nested MCP tool name" ) ;
}
if ( ! Object . prototype . hasOwnProperty . call ( input , "arguments" ) ) {
throw new Error ( "Invalid Kiro tool_call payload: missing nested MCP tool arguments" ) ;
}
2026-05-16 00:20:08 -04:00
}
2026-08-13 00:32:09 -04:00
} catch ( error ) {
state . droppedTools = ( state . droppedTools || 0 ) + 1 ;
state . toolValidationError || = error . message ;
console . error ( ` [Kiro] dropping unusable tool call ${ tool . id } ( ${ tool . name } ): ${ error . message } ` ) ;
continue ;
2026-07-19 23:52:27 -04:00
}
const index = state . toolCounter ++ ;
emitDelta ( controller , {
tool _calls : [ {
index ,
id : tool . id ,
type : "function" ,
function : { name : tool . name , arguments : "" }
} ]
} ) ;
2026-08-13 00:32:09 -04:00
const serializedInput = JSON . stringify ( input ) ;
2026-07-19 23:52:27 -04:00
emitDelta ( controller , {
2026-08-13 00:32:09 -04:00
tool _calls : [ { index , function : { arguments : serializedInput } } ]
2026-07-19 23:52:27 -04:00
} ) ;
2026-08-13 00:32:09 -04:00
// Tool arguments are billed output like any other completion bytes. They
// were never added to totalContentLength, so the /4 estimator in finish()
// reported OUT 0 -- or the Math.max floor of 1 -- for every turn whose
// entire answer was a tool call.
state . totalContentLength += tool . name . length + serializedInput . length ;
2026-07-19 23:52:27 -04:00
state . hasToolCalls = true ;
}
state . tools . clear ( ) ;
state . bufferedToolBytes = 0 ;
2026-08-13 00:32:09 -04:00
// A declared tool turn that emitted no usable call is only fatal when the
// turn produced nothing else. Throwing unconditionally here escaped
// emitTools() with provenance "invalid_tool_call", which the integrity gate
// re-derived into a repair retry -- discarding text the client had already
// been promised.
if ( state . stopReason === "tool_use" && ! state . hasToolCalls &&
! state . hasText && ! state . hasReasoning && ! state . hasCode ) {
2026-07-19 23:52:27 -04:00
throw new Error ( "Kiro tool_use stop reason did not include a complete tool call" ) ;
}
} ;
const processEvent = ( event , controller ) => {
const messageType = event . headers [ ":message-type" ] ;
if ( messageType === "error" || messageType === "exception" ) {
fail (
controller ,
"upstream_eventstream_error" ,
"kiro_upstream_eventstream_error" ,
event . payload ? . message || ` Kiro upstream sent an EventStream ${ messageType } ` ,
{ transport _state : "upstream_error" }
) ;
return false ;
}
2026-05-16 00:20:08 -04:00
2026-07-19 23:52:27 -04:00
const eventType = event . headers [ ":event-type" ] || "" ;
const eventCountKey = KIRO _EVENT _TYPES . has ( eventType ) ? eventType : "other" ;
eventCounts [ eventCountKey ] = ( eventCounts [ eventCountKey ] || 0 ) + 1 ;
if ( eventType === "assistantResponseEvent" && typeof event . payload ? . content === "string" ) {
let content = event . payload . content ;
if ( state . inThinking ) {
const end = content . indexOf ( "</thinking>" ) ;
if ( end < 0 ) content = "" ;
else {
state . inThinking = false ;
content = content . slice ( end + 11 ) . replace ( /^\n/u , "" ) ;
2026-01-16 01:09:13 -05:00
}
2026-07-19 23:52:27 -04:00
} else {
const start = content . indexOf ( "<thinking>" ) ;
if ( start >= 0 ) {
const end = content . indexOf ( "</thinking>" , start + 10 ) ;
if ( end < 0 ) {
state . inThinking = true ;
content = content . slice ( 0 , start ) ;
} else {
content = content . slice ( 0 , start ) + content . slice ( end + 11 ) . replace ( /^\n/u , "" ) ;
2026-01-15 06:29:47 -05:00
}
2026-01-16 01:09:13 -05:00
}
2026-07-19 23:52:27 -04:00
}
if ( content || ! state . hasReasoning ) {
state . hasText || = content . length > 0 ;
state . totalContentLength += content . length ;
emitDelta ( controller , { content } ) ;
}
} else if ( eventType === "reasoningContentEvent" ) {
const value = event . payload ? . reasoningContentEvent || event . payload || { } ;
const content = typeof value === "string" ? value : value . text || value . content || "" ;
if ( content ) {
state . hasReasoning = true ;
state . totalContentLength += content . length ;
emitDelta ( controller , { reasoning _content : content } ) ;
}
} else if ( eventType === "codeEvent" && typeof event . payload ? . content === "string" ) {
state . hasCode = true ;
state . totalContentLength += event . payload . content . length ;
emitDelta ( controller , { content : event . payload . content } ) ;
} else if ( eventType === "toolUseEvent" ) {
state . sawToolUse = true ;
const values = Array . isArray ( event . payload ) ? event . payload : [ event . payload ] ;
if ( ! values [ 0 ] ) throw new Error ( "Kiro toolUseEvent is empty" ) ;
for ( const value of values ) {
const name = typeof value ? . name === "string" ? value . name . trim ( ) : "" ;
if ( ! name ) throw new Error ( "Kiro toolUseEvent is missing a tool name" ) ;
let id ;
if ( value . toolUseId == null ) {
id = ` call_ ${ created } _ ${ state . tools . size + 1 } ` ;
} else if ( typeof value . toolUseId !== "string" || ! value . toolUseId . trim ( ) ) {
throw new Error ( "Kiro toolUseEvent has an invalid toolUseId" ) ;
} else {
id = value . toolUseId ;
2026-02-02 12:25:23 -05:00
}
2026-07-19 23:52:27 -04:00
let tool = state . tools . get ( id ) ;
if ( ! tool ) {
tool = { id , name } ;
state . tools . set ( id , tool ) ;
state . bufferedToolBytes += encoder . encode ( id ) . byteLength + encoder . encode ( name ) . byteLength + 32 ;
assertToolBufferBound ( ) ;
} else if ( tool . name !== name ) {
throw new Error ( "Kiro tool name changed between fragments" ) ;
2026-02-02 12:25:23 -05:00
}
2026-07-19 23:52:27 -04:00
appendToolInput ( tool , value . input ) ;
}
} else if ( eventType === "messageStopEvent" ) {
state . explicitStop = true ;
const reason = normalizeStopReason (
event . payload ? . stopReason ? ? event . payload ? . stop _reason
) || ( state . sawToolUse ? "tool_use" : "end_turn" ) ;
const merged = mergeStopReason ( state . stopReason , reason ) ;
if ( merged !== state . stopReason ) state . terminalProvenance = "message_stop_event" ;
state . stopReason = merged ;
} else if ( eventType === "metadataEvent" || eventType === "MetadataEvent" ) {
const metadata = event . payload ? . metadataEvent || event . payload ? . metadata || event . payload ;
const reason = normalizeStopReason ( metadata ? . stopReason ? ? metadata ? . stop _reason ) ;
if ( reason ) {
state . explicitStop = true ;
const merged = mergeStopReason ( state . stopReason , reason ) ;
if ( merged !== state . stopReason ) state . terminalProvenance = "metadata_stop_reason" ;
state . stopReason = merged ;
}
} else if ( eventType === "contextUsageEvent" ) {
const percentage = Number ( event . payload ? . contextUsagePercentage ) ;
if ( Number . isFinite ( percentage ) ) {
state . contextUsagePercentage = percentage ;
state . hasContextUsage = true ;
}
} else if ( eventType === "meteringEvent" ) {
state . hasMetering = true ;
const metering = event . payload ? . meteringEvent || event . payload || { } ;
const credits = Number ( metering . usage ) ;
if ( Number . isFinite ( credits ) ) {
state . usage = {
... ( state . usage || { } ) ,
kiro _credits : credits ,
kiro _credit _unit : typeof metering . unit === "string" ? metering . unit : "credit"
} ;
}
} else if ( eventType === "metricsEvent" ) {
const metrics = event . payload ? . metricsEvent || event . payload || { } ;
const prompt = Number ( metrics . inputTokens ) || 0 ;
const completion = Number ( metrics . outputTokens ) || 0 ;
if ( prompt || completion ) {
state . usage = {
... ( state . usage || { } ) ,
prompt _tokens : prompt ,
completion _tokens : completion ,
total _tokens : prompt + completion
} ;
const cacheRead = Number ( metrics . cacheReadInputTokens || metrics . cache _read _input _tokens ) || 0 ;
const cacheCreate = Number ( metrics . cacheCreationInputTokens || metrics . cache _creation _input _tokens ) || 0 ;
if ( cacheRead ) state . usage . cache _read _input _tokens = cacheRead ;
if ( cacheCreate ) state . usage . cache _creation _input _tokens = cacheCreate ;
}
}
return true ;
} ;
const processBytes = ( chunk , controller ) => {
const combinedLength = state . buffer . byteLength + chunk . byteLength ;
if ( combinedLength > ( options . maxRawBytes || EVENTSTREAM _MAX _MESSAGE _BYTES ) ) {
fail (
controller ,
"corrupt_eventstream_frame" ,
"kiro_missing_terminal" ,
"Kiro EventStream buffered bytes exceed the protocol bound"
) ;
return false ;
}
if ( state . buffer . byteLength === 0 ) {
state . buffer = chunk ;
} else {
const joined = new Uint8Array ( combinedLength ) ;
joined . set ( state . buffer ) ;
joined . set ( chunk , state . buffer . byteLength ) ;
state . buffer = joined ;
}
2026-02-02 12:25:23 -05:00
2026-07-19 23:52:27 -04:00
while ( state . buffer . byteLength >= 12 ) {
const view = new DataView ( state . buffer . buffer , state . buffer . byteOffset ) ;
if ( view . getUint32 ( 8 , false ) !== crc32 ( state . buffer . subarray ( 0 , 8 ) ) ) {
fail ( controller , "corrupt_eventstream_frame" , "kiro_missing_terminal" , "Kiro EventStream prelude CRC mismatch" ) ;
return false ;
}
const totalLength = view . getUint32 ( 0 , false ) ;
const headersLength = view . getUint32 ( 4 , false ) ;
if ( totalLength < 16 || totalLength > EVENTSTREAM _MAX _MESSAGE _BYTES ||
headersLength > EVENTSTREAM _MAX _HEADERS _BYTES || headersLength > totalLength - 16 ) {
fail ( controller , "corrupt_eventstream_frame" , "kiro_missing_terminal" , "Kiro EventStream frame bounds are invalid" ) ;
return false ;
}
if ( state . buffer . byteLength < totalLength ) break ;
const frame = state . buffer . slice ( 0 , totalLength ) ;
state . buffer = state . buffer . slice ( totalLength ) ;
let event ;
try {
event = parseEventFrame ( frame ) ;
} catch ( error ) {
fail ( controller , "corrupt_eventstream_frame" , "kiro_missing_terminal" , error . message ) ;
return false ;
}
state . transportState = "valid_complete_frame" ;
state . validatedFrames ++ ;
try {
if ( ! processEvent ( event , controller ) ) return false ;
} catch ( error ) {
const bufferExceeded = error . code === "KIRO_BUFFER_EXCEEDED" ;
if ( ! bufferExceeded ) {
2026-08-13 00:32:09 -04:00
// Keep whatever is already buffered: the rejected fragment belongs to
// one tool, and clearing the map dropped the complete calls too.
2026-07-19 23:52:27 -04:00
state . toolValidationError || = error . message ;
2026-08-13 00:32:09 -04:00
console . error ( ` [Kiro] tool fragment rejected, keeping ${ state . tools . size } buffered tool(s): ${ error . message } ` ) ;
2026-07-19 23:52:27 -04:00
continue ;
2026-02-02 12:25:23 -05:00
}
2026-07-19 23:52:27 -04:00
fail (
controller ,
"integrity_buffer_exceeded" ,
"kiro_integrity_buffer_exceeded" ,
error . message ,
{
transport _state : state . transportState ,
stop _disposition : "terminal_incomplete"
2026-02-02 12:25:23 -05:00
}
2026-07-19 23:52:27 -04:00
) ;
return false ;
2026-01-15 06:29:47 -05:00
}
2026-07-19 23:52:27 -04:00
}
return true ;
} ;
const finish = ( controller ) => {
if ( state . finished ) return ;
if ( state . buffer . byteLength ) {
fail (
controller ,
"incomplete_eventstream_frame" ,
"kiro_missing_terminal" ,
"Kiro EventStream ended with a truncated frame" ,
{ transport _state : "incomplete_frame" }
) ;
return ;
}
state . transportState = "clean_eof" ;
const declaredDisposition = stopDisposition ( state . stopReason , state . sawToolUse ) ;
2026-08-13 00:32:09 -04:00
// model_context_window_exceeded / max_tokens map to terminal_incomplete. When
// they arrive after the model already streamed content, fail() threw away a
// complete-enough answer; a truncated turn is what finish_reason "length" is
// for. chunkIndex > 0 means at least one delta already reached the client.
const declaredTruncatedAfterOutput = declaredDisposition === "terminal_incomplete" &&
KIRO _TRUNCATION _STOP _REASONS . has ( state . stopReason ) && state . chunkIndex > 0 ;
if ( declaredTruncatedAfterOutput ) {
console . error ( ` [Kiro] truncated after ${ state . chunkIndex } chunk(s) (stop_reason= ${ state . stopReason } ); keeping output ` ) ;
}
if ( ! declaredTruncatedAfterOutput && [ "retryable_protocol_failure" , "terminal_incomplete" , "terminal_refusal" , "unknown_failure" ] . includes ( declaredDisposition ) ) {
2026-07-19 23:52:27 -04:00
const code = declaredDisposition === "retryable_protocol_failure"
? "kiro_retryable_protocol_failure"
: declaredDisposition === "terminal_refusal"
? "kiro_terminal_refusal"
: declaredDisposition === "terminal_incomplete"
? "kiro_terminal_incomplete"
: "kiro_unknown_stop_reason" ;
fail (
controller ,
state . terminalProvenance || "metadata_stop_reason" ,
code ,
` Kiro ended with non-success stop reason: ${ state . stopReason } ` ,
{ transport _state : state . transportState , stop _disposition : declaredDisposition }
) ;
return ;
}
2026-08-13 00:32:09 -04:00
try {
emitTools ( controller ) ;
} catch ( error ) {
2026-07-19 23:52:27 -04:00
fail (
controller ,
"invalid_tool_call" ,
"invalid_kiro_tool_call" ,
2026-08-13 00:32:09 -04:00
error . message ,
2026-07-19 23:52:27 -04:00
{ transport _state : state . transportState , stop _disposition : "retryable_protocol_failure" }
) ;
return ;
}
2026-08-13 00:32:09 -04:00
// Fail only when the turn has nothing usable left. emitTools() validates
// per tool and drops just the unusable ones, so this has to run AFTER it:
// before, the rejected tool was still buffered and tools.size was never 0.
// A turn that also produced text keeps that text -- the dropped call is
// logged, not fatal.
if ( state . toolValidationError && ! state . hasToolCalls &&
! state . hasText && ! state . hasReasoning && ! state . hasCode ) {
2026-07-19 23:52:27 -04:00
fail (
controller ,
"invalid_tool_call" ,
"invalid_kiro_tool_call" ,
2026-08-13 00:32:09 -04:00
state . toolValidationError ,
2026-07-19 23:52:27 -04:00
{ transport _state : state . transportState , stop _disposition : "retryable_protocol_failure" }
) ;
return ;
}
2026-06-16 23:01:30 -04:00
2026-07-19 23:52:27 -04:00
const hasOutput = state . hasText || state . hasReasoning || state . hasCode || state . hasToolCalls ;
if ( ! hasOutput && ! state . explicitStop ) {
fail (
controller ,
"empty_response_eof" ,
"kiro_missing_terminal" ,
"Kiro EventStream ended without model output" ,
{ transport _state : state . transportState }
) ;
return ;
}
2026-01-16 01:09:13 -05:00
2026-07-19 23:52:27 -04:00
const disposition = stopDisposition ( state . stopReason , state . hasToolCalls ) ;
2026-08-13 00:32:09 -04:00
// Same reasoning as declaredTruncatedAfterOutput above.
const truncatedAfterOutput = disposition === "terminal_incomplete" &&
KIRO _TRUNCATION _STOP _REASONS . has ( state . stopReason ) && state . chunkIndex > 0 ;
if ( truncatedAfterOutput ) {
console . error ( ` [Kiro] truncated after ${ state . chunkIndex } chunk(s) (stop_reason= ${ state . stopReason } ); closing as length ` ) ;
}
if ( ! truncatedAfterOutput && [ "retryable_protocol_failure" , "terminal_incomplete" , "terminal_refusal" , "unknown_failure" ] . includes ( disposition ) ) {
2026-07-19 23:52:27 -04:00
const code = disposition === "retryable_protocol_failure"
? "kiro_retryable_protocol_failure"
: disposition === "terminal_refusal"
? "kiro_terminal_refusal"
: disposition === "terminal_incomplete"
? "kiro_terminal_incomplete"
: "kiro_unknown_stop_reason" ;
fail (
controller ,
state . terminalProvenance || "metadata_stop_reason" ,
code ,
` Kiro ended with non-success stop reason: ${ state . stopReason } ` ,
{ transport _state : state . transportState , stop _disposition : disposition }
) ;
return ;
}
2026-01-16 01:09:13 -05:00
2026-07-19 23:52:27 -04:00
if ( state . hasMetering && state . hasContextUsage && ! state . usage ? . total _tokens ) {
const completion = state . totalContentLength
? Math . max ( 1 , Math . floor ( state . totalContentLength / 4 ) )
: 0 ;
const prompt = Math . floor ( state . contextUsagePercentage * contextWindow / 100 ) ;
state . usage = {
... ( state . usage || { } ) ,
prompt _tokens : prompt ,
completion _tokens : completion ,
total _tokens : prompt + completion
} ;
2026-01-15 06:29:47 -05:00
}
2026-08-13 00:32:09 -04:00
const finishReason = truncatedAfterOutput
? "length"
: state . hasToolCalls
? "tool_calls"
: disposition === "length"
? "length"
: "stop" ;
2026-07-19 23:52:27 -04:00
controller . enqueue ( sseChunk ( { } , finishReason , state . usage ) ) ;
controller . enqueue ( encoder . encode ( SSE _DONE ) ) ;
state . finished = true ;
options . onTerminalState ? . ( diagnostics ( {
terminal _provenance : state . terminalProvenance || "clean_eventstream_eof" ,
transport _state : state . transportState ,
2026-08-13 00:32:09 -04:00
// Report what this exit actually did, not the raw disposition. The
// integrity gate re-derives its verdict from stop_disposition, so
// reporting "terminal_incomplete" for a turn we deliberately kept made
// it discard the very bytes we just released to the client.
stop _disposition : truncatedAfterOutput ? "length" : disposition
2026-07-19 23:52:27 -04:00
} ) ) ;
} ;
2026-01-15 06:29:47 -05:00
2026-03-13 22:37:29 -04:00
if ( ! response . body ) {
2026-07-19 23:52:27 -04:00
const detail = diagnostics ( {
terminal _provenance : "missing_response_body" ,
transport _state : "missing_body" ,
stop _disposition : "terminal_incomplete"
} ) ;
options . onTerminalState ? . ( detail ) ;
return new Response ( encodeSSEError (
"kiro_missing_terminal" ,
"Kiro response did not include an EventStream body" ,
detail
) , { status : response . status , headers : { ... SSE _HEADERS } } ) ;
2026-03-13 22:37:29 -04:00
}
2026-01-16 01:09:13 -05:00
2026-07-19 23:52:27 -04:00
const reader = response . body . getReader ( ) ;
const stream = new ReadableStream ( {
start : async ( controller ) => {
try {
while ( ! state . finished ) {
const { done , value } = await reader . read ( ) ;
if ( done ) break ;
const chunksBefore = state . chunkIndex ;
const framesBefore = state . validatedFrames ;
if ( ! processBytes ( value , controller ) ) {
await reader . cancel ( "invalid Kiro EventStream" ) . catch ( ( ) => { } ) ;
break ;
}
if ( state . validatedFrames > framesBefore && state . chunkIndex === chunksBefore ) {
controller . enqueue ( encoder . encode ( ": kiro-upstream\n\n" ) ) ;
}
}
finish ( controller ) ;
controller . close ( ) ;
} catch ( error ) {
if ( ! state . finished ) {
fail (
controller ,
"upstream_read_error" ,
"kiro_missing_terminal" ,
error . message || "Kiro EventStream read failed" ,
{ transport _state : "upstream_error" }
) ;
}
controller . close ( ) ;
}
} ,
cancel ( reason ) {
return reader . cancel ( reason ) ;
}
} ) ;
return new Response ( stream , {
2026-01-15 06:29:47 -05:00
status : response . status ,
statusText : response . statusText ,
2026-06-13 10:42:59 -04:00
headers : { ... SSE _HEADERS }
2026-01-15 06:29:47 -05:00
} ) ;
}
2026-04-28 06:28:57 -04:00
async refreshCredentials ( credentials , log , proxyOptions = null ) {
2026-01-15 06:29:47 -05:00
if ( ! credentials . refreshToken ) return null ;
try {
2026-01-16 00:39:03 -05:00
// Use centralized refreshKiroToken function (handles both AWS SSO OIDC and Social Auth)
const result = await refreshKiroToken (
credentials . refreshToken ,
credentials . providerSpecificData ,
2026-04-28 06:28:57 -04:00
log ,
proxyOptions
2026-01-16 00:39:03 -05:00
) ;
2026-01-15 06:29:47 -05:00
return result ;
} catch ( error ) {
log ? . error ? . ( "TOKEN" , ` Kiro refresh error: ${ error . message } ` ) ;
return null ;
}
}
}
/ * *
* Parse AWS EventStream frame
* /
2026-07-19 23:52:27 -04:00
function parseEventFrame ( data ) {
if ( ! ( data instanceof Uint8Array ) || data . byteLength < 16 ) {
throw new Error ( "AWS EventStream frame is shorter than 16 bytes" ) ;
}
const view = new DataView ( data . buffer , data . byteOffset , data . byteLength ) ;
const totalLength = view . getUint32 ( 0 , false ) ;
const headersLength = view . getUint32 ( 4 , false ) ;
if ( totalLength !== data . byteLength ) {
throw new Error ( "AWS EventStream frame length does not match its prelude" ) ;
}
if ( totalLength > EVENTSTREAM _MAX _MESSAGE _BYTES ||
headersLength > EVENTSTREAM _MAX _HEADERS _BYTES ||
headersLength > totalLength - 16 ) {
throw new Error ( "AWS EventStream frame bounds are invalid" ) ;
}
if ( view . getUint32 ( 8 , false ) !== crc32 ( data . subarray ( 0 , 8 ) ) ) {
throw new Error ( "AWS EventStream prelude CRC mismatch" ) ;
}
if ( view . getUint32 ( totalLength - 4 , false ) !== crc32 ( data . subarray ( 0 , totalLength - 4 ) ) ) {
throw new Error ( "AWS EventStream message CRC mismatch" ) ;
}
2026-01-16 01:09:13 -05:00
2026-07-19 23:52:27 -04:00
const headers = Object . create ( null ) ;
const names = new Set ( ) ;
let offset = 12 ;
const headerEnd = offset + headersLength ;
const requireBytes = ( count ) => {
if ( offset + count > headerEnd ) {
throw new Error ( "AWS EventStream header exceeds its declared bounds" ) ;
2026-01-15 06:29:47 -05:00
}
2026-07-19 23:52:27 -04:00
} ;
while ( offset < headerEnd ) {
requireBytes ( 1 ) ;
const nameLength = data [ offset ++ ] ;
requireBytes ( nameLength + 1 ) ;
const name = decoder . decode ( data . subarray ( offset , offset + nameLength ) ) ;
offset += nameLength ;
if ( names . has ( name ) ) throw new Error ( ` AWS EventStream contains duplicate header: ${ name } ` ) ;
names . add ( name ) ;
const type = data [ offset ++ ] ;
if ( type === 0 || type === 1 ) {
headers [ name ] = type === 0 ;
} else if ( type === 2 ) {
requireBytes ( 1 ) ;
headers [ name ] = view . getInt8 ( offset ) ;
offset += 1 ;
} else if ( type === 3 ) {
requireBytes ( 2 ) ;
headers [ name ] = view . getInt16 ( offset , false ) ;
offset += 2 ;
} else if ( type === 4 ) {
requireBytes ( 4 ) ;
headers [ name ] = view . getInt32 ( offset , false ) ;
offset += 4 ;
} else if ( type === 5 || type === 8 ) {
requireBytes ( 8 ) ;
offset += 8 ;
} else if ( type === 6 || type === 7 ) {
requireBytes ( 2 ) ;
const valueLength = view . getUint16 ( offset , false ) ;
offset += 2 ;
requireBytes ( valueLength ) ;
const bytes = data . subarray ( offset , offset + valueLength ) ;
headers [ name ] = type === 7 ? decoder . decode ( bytes ) : bytes ;
offset += valueLength ;
} else if ( type === 9 ) {
requireBytes ( 16 ) ;
offset += 16 ;
} else {
throw new Error ( ` AWS EventStream header ${ name } has unknown type ${ type } ` ) ;
2026-01-15 06:29:47 -05:00
}
2026-07-19 23:52:27 -04:00
}
2026-01-15 06:29:47 -05:00
2026-07-19 23:52:27 -04:00
const payloadBytes = data . subarray ( headerEnd , totalLength - 4 ) ;
if ( payloadBytes . byteLength === 0 ) return { headers , payload : null } ;
const payloadText = decoder . decode ( payloadBytes ) ;
if ( ! payloadText . trim ( ) ) return { headers , payload : null } ;
try {
return { headers , payload : JSON . parse ( payloadText ) } ;
} catch ( error ) {
throw new Error ( ` AWS EventStream payload is not valid JSON ( ${ error . message } ) ` ) ;
2026-01-15 06:29:47 -05:00
}
}
export default KiroExecutor ;