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:
@@ -195,7 +195,19 @@ export const PROVIDERS = {
|
|||||||
clientId: "Iv1.b507a08c87ecfe98"
|
clientId: "Iv1.b507a08c87ecfe98"
|
||||||
},
|
},
|
||||||
kiro: {
|
kiro: {
|
||||||
baseUrl: "https://codewhisperer.us-east-1.amazonaws.com/generateAssistantResponse",
|
// All three hosts resolve to the same regional CodeWhisperer streaming service
|
||||||
|
// (GenerateAssistantResponse). They are alternate DNS surfaces, NOT separate quota
|
||||||
|
// buckets — AWS throttles per authenticated identity (token + profileArn), not per
|
||||||
|
// hostname. Listing them enables edge-level failover (5xx / connect timeout / a
|
||||||
|
// degraded surface); it does NOT multiply 429 headroom. To actually spread 429 load,
|
||||||
|
// add multiple Kiro accounts — account rotation in sse/handlers/chat.js handles that.
|
||||||
|
// Order: newest Kiro IDE endpoint first, legacy AWS domains as fallback.
|
||||||
|
baseUrl: "https://runtime.us-east-1.kiro.dev/generateAssistantResponse",
|
||||||
|
baseUrls: [
|
||||||
|
"https://runtime.us-east-1.kiro.dev/generateAssistantResponse",
|
||||||
|
"https://codewhisperer.us-east-1.amazonaws.com/generateAssistantResponse",
|
||||||
|
"https://q.us-east-1.amazonaws.com/generateAssistantResponse",
|
||||||
|
],
|
||||||
format: "kiro",
|
format: "kiro",
|
||||||
retry: { 429: 2 },
|
retry: { 429: 2 },
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import { BaseExecutor } from "./base.js";
|
|||||||
import { PROVIDERS } from "../config/providers.js";
|
import { PROVIDERS } from "../config/providers.js";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { refreshKiroToken } from "../services/tokenRefresh.js";
|
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)
|
* 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 }) {
|
async execute(args) {
|
||||||
const url = this.buildUrl(model, stream, 0);
|
const result = await super.execute(args);
|
||||||
const transformedBody = this.transformRequest(model, body, stream, credentials);
|
if (result?.response?.ok) {
|
||||||
|
result.response = this.transformEventStreamToSSE(result.response, args.model);
|
||||||
// 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 };
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user