fix(kiro): 增强401错误处理并支持Gemini格式请求体转换,修复健康KIRO健康检查错误。
1. 在callApi和callApiStream方法中添加contents到messages格式的自动转换 2. 401错误时先刷新UUID再刷新token,提高认证恢复成功率 3. 新增_refreshUuid方法用于生成新的UUID标识 4. ProviderPoolManager新增refreshProviderUuid方法支持UUID刷新 5. 移除Kiro OAuth的contents格式备用请求,统一使用messages格式
This commit is contained in:
parent
a97f8b4b5a
commit
b14ae21917
2 changed files with 109 additions and 16 deletions
|
|
@ -1192,7 +1192,21 @@ async initializeAuth(forceRefresh = false) {
|
|||
const maxRetries = this.config.REQUEST_MAX_RETRIES || 3;
|
||||
const baseDelay = this.config.REQUEST_BASE_DELAY || 1000; // 1 second base delay
|
||||
|
||||
const requestData = this.buildCodewhispererRequest(body.messages, model, body.tools, body.system, body.thinking);
|
||||
// 处理不同格式的请求体(messages 或 contents)
|
||||
let messages = body.messages;
|
||||
if (!messages && body.contents) {
|
||||
// 将 Gemini 格式的 contents 转换为 messages 格式
|
||||
messages = body.contents.map(content => ({
|
||||
role: content.role || 'user',
|
||||
content: content.parts?.map(part => part.text).join('') || ''
|
||||
}));
|
||||
}
|
||||
|
||||
if (!messages || !Array.isArray(messages) || messages.length === 0) {
|
||||
throw new Error('No messages found in request body');
|
||||
}
|
||||
|
||||
const requestData = this.buildCodewhispererRequest(messages, model, body.tools, body.system, body.thinking);
|
||||
|
||||
try {
|
||||
const token = this.accessToken; // Use the already initialized token
|
||||
|
|
@ -1213,16 +1227,25 @@ async initializeAuth(forceRefresh = false) {
|
|||
// 检查是否为可重试的网络错误
|
||||
const isNetworkError = isRetryableNetworkError(error);
|
||||
|
||||
// Handle 401 (Unauthorized) - try to refresh token first
|
||||
// Handle 401 (Unauthorized) - refresh UUID first, then try to refresh token
|
||||
if (status === 401 && !isRetry) {
|
||||
console.log('[Kiro] Received 401. Attempting token refresh...');
|
||||
console.log('[Kiro] Received 401. Refreshing UUID and attempting token refresh...');
|
||||
|
||||
// 1. 先刷新 UUID
|
||||
const newUuid = this._refreshUuid();
|
||||
if (newUuid) {
|
||||
console.log(`[Kiro] UUID refreshed: ${this.uuid} -> ${newUuid}`);
|
||||
this.uuid = newUuid;
|
||||
}
|
||||
|
||||
// 2. 尝试刷新 token
|
||||
try {
|
||||
await this.initializeAuth(true); // Force refresh token
|
||||
console.log('[Kiro] Token refresh successful after 401, retrying request...');
|
||||
console.log('[Kiro] Token refresh successful after 401, retrying request with new UUID...');
|
||||
return this.callApi(method, model, body, true, retryCount);
|
||||
} catch (refreshError) {
|
||||
console.error('[Kiro] Token refresh failed during 401 retry:', refreshError.message);
|
||||
// Mark credential as unhealthy immediately and attach marker to error
|
||||
// 3. 刷新失败,标记凭证不健康,让上层切换到其他凭证
|
||||
this._markCredentialUnhealthy('401 Unauthorized - Token refresh failed', refreshError);
|
||||
throw refreshError;
|
||||
}
|
||||
|
|
@ -1265,6 +1288,25 @@ async initializeAuth(forceRefresh = false) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to refresh the current credential's UUID
|
||||
* Used when encountering 401 errors to get a fresh identity
|
||||
* @returns {string|null} - The new UUID, or null if refresh failed
|
||||
* @private
|
||||
*/
|
||||
_refreshUuid() {
|
||||
const poolManager = getProviderPoolManager();
|
||||
if (poolManager && this.uuid) {
|
||||
const newUuid = poolManager.refreshProviderUuid(MODEL_PROVIDER.KIRO_API, {
|
||||
uuid: this.uuid
|
||||
});
|
||||
return newUuid;
|
||||
} else {
|
||||
console.warn(`[Kiro] Cannot refresh UUID: poolManager=${!!poolManager}, uuid=${this.uuid}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to mark the current credential as unhealthy
|
||||
* @param {string} reason - The reason for marking unhealthy
|
||||
|
|
@ -1512,7 +1554,21 @@ async initializeAuth(forceRefresh = false) {
|
|||
const maxRetries = this.config.REQUEST_MAX_RETRIES || 3;
|
||||
const baseDelay = this.config.REQUEST_BASE_DELAY || 1000;
|
||||
|
||||
const requestData = this.buildCodewhispererRequest(body.messages, model, body.tools, body.system, body.thinking);
|
||||
// 处理不同格式的请求体(messages 或 contents)
|
||||
let messages = body.messages;
|
||||
if (!messages && body.contents) {
|
||||
// 将 Gemini 格式的 contents 转换为 messages 格式
|
||||
messages = body.contents.map(content => ({
|
||||
role: content.role || 'user',
|
||||
content: content.parts?.map(part => part.text).join('') || ''
|
||||
}));
|
||||
}
|
||||
|
||||
if (!messages || !Array.isArray(messages) || messages.length === 0) {
|
||||
throw new Error('No messages found in request body');
|
||||
}
|
||||
|
||||
const requestData = this.buildCodewhispererRequest(messages, model, body.tools, body.system, body.thinking);
|
||||
|
||||
const token = this.accessToken;
|
||||
const headers = {
|
||||
|
|
|
|||
|
|
@ -556,6 +556,52 @@ export class ProviderPoolManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新指定提供商的 UUID
|
||||
* 用于在认证错误(如 401)时更换 UUID,以便重新尝试
|
||||
* @param {string} providerType - 提供商类型
|
||||
* @param {object} providerConfig - 提供商配置(包含当前 uuid)
|
||||
* @returns {string|null} 新的 UUID,如果失败则返回 null
|
||||
*/
|
||||
refreshProviderUuid(providerType, providerConfig) {
|
||||
if (!providerConfig?.uuid) {
|
||||
this._log('error', 'Invalid providerConfig in refreshProviderUuid');
|
||||
return null;
|
||||
}
|
||||
|
||||
const provider = this._findProvider(providerType, providerConfig.uuid);
|
||||
if (provider) {
|
||||
const oldUuid = provider.config.uuid;
|
||||
// 生成新的 UUID
|
||||
const newUuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
const r = Math.random() * 16 | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
|
||||
// 更新 provider 的 UUID
|
||||
provider.uuid = newUuid;
|
||||
provider.config.uuid = newUuid;
|
||||
|
||||
// 同时更新 providerPools 中的原始数据
|
||||
const poolArray = this.providerPools[providerType];
|
||||
if (poolArray) {
|
||||
const originalProvider = poolArray.find(p => p.uuid === oldUuid);
|
||||
if (originalProvider) {
|
||||
originalProvider.uuid = newUuid;
|
||||
}
|
||||
}
|
||||
|
||||
this._log('info', `Refreshed provider UUID: ${oldUuid} -> ${newUuid} for type ${providerType}`);
|
||||
this._debouncedSave(providerType);
|
||||
|
||||
return newUuid;
|
||||
}
|
||||
|
||||
this._log('warn', `Provider not found for UUID refresh: ${providerConfig.uuid} in ${providerType}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs health checks on all providers in the pool.
|
||||
* This method would typically be called periodically (e.g., via cron job).
|
||||
|
|
@ -638,22 +684,13 @@ export class ProviderPoolManager {
|
|||
return requests;
|
||||
}
|
||||
|
||||
// Kiro OAuth 同时支持 messages 和 contents 格式
|
||||
// Kiro OAuth 只支持 messages 格式
|
||||
if (providerType.startsWith('claude-kiro')) {
|
||||
// 优先使用 messages 格式
|
||||
requests.push({
|
||||
messages: [baseMessage],
|
||||
model: modelName,
|
||||
max_tokens: 1
|
||||
});
|
||||
// 备用 contents 格式
|
||||
requests.push({
|
||||
contents: [{
|
||||
role: 'user',
|
||||
parts: [{ text: baseMessage.content }]
|
||||
}],
|
||||
max_tokens: 1
|
||||
});
|
||||
return requests;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue