feat(kiro): enable multi-endpoint failover for GenerateAssistantResponse

Wire the Kiro provider into BaseExecutor baseUrls fallback so a request
advances across the three CodeWhisperer surfaces (runtime kiro.dev,
codewhisperer, q) on 429 and network/5xx errors.

- providers.js: add baseUrls[] (newest endpoint first); keep baseUrl as default
- kiro.js: replace the hand-rolled single-endpoint loop with super.execute()
  delegation plus EventStream to SSE transform on success

Note: the three hosts are alternate DNS surfaces of one regional service. AWS
throttles per authenticated identity (token plus profileArn), not per hostname,
so this is edge-level failover, not extra 429 quota.

Co-authored-by: thienpv <pvtcwd@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
thienpv
2026-06-08 09:44:53 +07:00
committed by decolua
co-authored by Cursor
parent dd5c575c65
commit c24efe80f0
2 changed files with 30 additions and 40 deletions
+17 -39
View File
@@ -2,8 +2,6 @@ import { BaseExecutor } from "./base.js";
import { PROVIDERS } from "../config/providers.js";
import { v4 as uuidv4 } from "uuid";
import { refreshKiroToken } from "../services/tokenRefresh.js";
import { proxyAwareFetch } from "../utils/proxyFetch.js";
import { HTTP_STATUS, RETRY_CONFIG, DEFAULT_RETRY_CONFIG, resolveRetryEntry } from "../config/runtimeConfig.js";
/**
* KiroExecutor - Executor for Kiro AI (AWS CodeWhisperer)
@@ -33,45 +31,25 @@ export class KiroExecutor extends BaseExecutor {
}
/**
* Custom execute for Kiro - handles AWS EventStream binary response with retry support
* 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/5xx errors, while tryRetry handles in-place retries per `retry: {429: 2}`.
* 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.
*/
async execute({ model, body, stream, credentials, signal, log, proxyOptions = null }) {
const url = this.buildUrl(model, stream, 0);
const transformedBody = this.transformRequest(model, body, stream, credentials);
// Merge default retry config with provider-specific config
const retryConfig = { ...DEFAULT_RETRY_CONFIG, ...this.config.retry };
let retryAttempts = 0;
while (true) {
const headers = this.buildHeaders(credentials, stream);
const response = await proxyAwareFetch(url, {
method: "POST",
headers,
body: JSON.stringify(transformedBody),
signal
}, proxyOptions);
// Check if should retry based on status code
const { attempts: maxRetries, delayMs } = resolveRetryEntry(retryConfig[response.status]);
if (!response.ok && maxRetries > 0 && retryAttempts < maxRetries) {
retryAttempts++;
log?.debug?.("RETRY", `${response.status} retry ${retryAttempts}/${maxRetries} after ${delayMs / 1000}s`);
await new Promise(resolve => setTimeout(resolve, delayMs));
continue;
}
if (!response.ok) {
return { response, url, headers, transformedBody };
}
// Success - transform and return
// For Kiro, we need to transform the binary EventStream to SSE
// Create a TransformStream to convert binary to SSE text
const transformedResponse = this.transformEventStreamToSSE(response, model);
return { response: transformedResponse, url, headers, transformedBody };
async execute(args) {
const result = await super.execute(args);
if (result?.response?.ok) {
result.response = this.transformEventStreamToSSE(result.response, args.model);
}
return result;
}
/**