refactor(providers): 重构 Codex 认证刷新机制以支持后台异步刷新

- 将直接调用 refreshAccessToken 改为调用 initializeAuth(true) 以统一认证流程
- 新增 triggerBackgroundRefresh 方法,在 token 即将过期或收到 401 时异步触发刷新
- 移除多处重复的 PoolManager 标记逻辑,统一由 triggerBackgroundRefresh 处理
- 优化 initializeAuth 方法,仅在需要时加载凭证,避免不必要的阻塞
- 更新版本号至 2.11.0
This commit is contained in:
hex2077 2026-03-09 19:00:15 +08:00
parent 0631d0db05
commit f2f02b365f
3 changed files with 38 additions and 55 deletions

View file

@ -1 +1 @@
2.10.9.3
2.11.0

View file

@ -574,7 +574,7 @@ export class CodexApiServiceAdapter extends ApiServiceAdapter {
}
if (this.isExpiryDateNear()) {
logger.info(`[Codex] Expiry date is near, refreshing token...`);
await this.codexApiService.refreshAccessToken();
await this.codexApiService.initializeAuth(true);
}
return Promise.resolve();
}
@ -584,7 +584,7 @@ export class CodexApiServiceAdapter extends ApiServiceAdapter {
await this.codexApiService.initialize();
}
logger.info(`[Codex] Force refreshing token...`);
return this.codexApiService.refreshAccessToken();
return this.codexApiService.initializeAuth(true);
}
isExpiryDateNear() {

View file

@ -99,10 +99,9 @@ export class CodexApiService {
this.last_refresh = creds.last_refresh || this.last_refresh;
this.expiresAt = new Date(creds.expired); // 注意:字段名是 expired
// 检查 token 是否需要刷新
// 检查 token 是否需要刷新(异步触发,不阻塞加载)
if (this.isExpiryDateNear()) {
logger.info('[Codex] Token expiring soon, refreshing...');
await this.refreshAccessToken();
this.triggerBackgroundRefresh();
}
this.isInitialized = true;
@ -116,9 +115,6 @@ export class CodexApiService {
* 初始化认证并执行必要刷新
*/
async initializeAuth(forceRefresh = false) {
// 首先执行基础凭证加载
await this.loadCredentials();
// 检查 token 是否需要刷新
const needsRefresh = forceRefresh;
@ -126,7 +122,11 @@ export class CodexApiService {
return;
}
// 首先执行基础凭证加载
await this.loadCredentials();
// 只有在明确要求刷新,或者 AccessToken 缺失时,才执行刷新
// 注意:在 V2 架构下,此方法主要由 PoolManager 的后台队列调用
if (needsRefresh || !this.accessToken) {
if (!this.refreshToken) {
throw new Error('Codex credentials not found. Please authenticate first using OAuth.');
@ -136,6 +136,19 @@ export class CodexApiService {
}
}
/**
* 后台异步刷新 token不阻塞当前请求
*/
triggerBackgroundRefresh() {
const poolManager = getProviderPoolManager();
if (poolManager && this.uuid) {
logger.info(`[Codex] Token is near expiry, marking credential ${this.uuid} for background refresh`);
poolManager.markProviderNeedRefresh(MODEL_PROVIDER.CODEX_API, {
uuid: this.uuid
});
}
}
/**
* 生成内容非流式
*/
@ -160,15 +173,9 @@ export class CodexApiService {
delete requestBody._requestBaseUrl;
}
// 检查 token 是否即将过期,如果是则推送到刷新队列
// 检查 token 是否即将过期,如果是则触发后台异步刷新
if (this.isExpiryDateNear()) {
const poolManager = getProviderPoolManager();
if (poolManager && this.uuid) {
logger.info(`[Codex] Token is near expiry, marking credential ${this.uuid} for refresh`);
poolManager.markProviderNeedRefresh(MODEL_PROVIDER.CODEX_API, {
uuid: this.uuid
});
}
this.triggerBackgroundRefresh();
}
const url = `${this.baseUrl}/responses`;
@ -194,17 +201,11 @@ export class CodexApiService {
return this.parseNonStreamResponse(response.data);
} catch (error) {
if (error.response?.status === 401) {
logger.info('[Codex] Received 401. Triggering background refresh via PoolManager...');
logger.info('[Codex] Received 401. Triggering background refresh...');
// 标记当前凭证为不健康(会自动进入刷新队列)
const poolManager = getProviderPoolManager();
if (poolManager && this.uuid) {
logger.info(`[Codex] Marking credential ${this.uuid} as needs refresh. Reason: 401 Unauthorized`);
poolManager.markProviderNeedRefresh(MODEL_PROVIDER.CODEX_API, {
uuid: this.uuid
});
error.credentialMarkedUnhealthy = true;
}
// 触发后台异步刷新
this.triggerBackgroundRefresh();
error.credentialMarkedUnhealthy = true;
// Mark error for credential switch without recording error count
error.shouldSwitchCredential = true;
@ -241,15 +242,9 @@ export class CodexApiService {
delete requestBody._requestBaseUrl;
}
// 检查 token 是否即将过期,如果是则推送到刷新队列
// 检查 token 是否即将过期,如果是则触发后台异步刷新
if (this.isExpiryDateNear()) {
const poolManager = getProviderPoolManager();
if (poolManager && this.uuid) {
logger.info(`[Codex] Token is near expiry, marking credential ${this.uuid} for refresh`);
poolManager.markProviderNeedRefresh(MODEL_PROVIDER.CODEX_API, {
uuid: this.uuid
});
}
this.triggerBackgroundRefresh();
}
const url = `${this.baseUrl}/responses`;
@ -275,17 +270,11 @@ export class CodexApiService {
yield* this.parseSSEStream(response.data);
} catch (error) {
if (error.response?.status === 401) {
logger.info('[Codex] Received 401 during stream. Triggering background refresh via PoolManager...');
logger.info('[Codex] Received 401 during stream. Triggering background refresh...');
// 标记当前凭证为不健康
const poolManager = getProviderPoolManager();
if (poolManager && this.uuid) {
logger.info(`[Codex] Marking credential ${this.uuid} as needs refresh. Reason: 401 Unauthorized in stream`);
poolManager.markProviderNeedRefresh(MODEL_PROVIDER.CODEX_API, {
uuid: this.uuid
});
error.credentialMarkedUnhealthy = true;
}
// 触发后台异步刷新
this.triggerBackgroundRefresh();
error.credentialMarkedUnhealthy = true;
// Mark error for credential switch without recording error count
error.shouldSwitchCredential = true;
@ -731,17 +720,11 @@ export class CodexApiService {
return result;
} catch (error) {
if (error.response?.status === 401) {
logger.info('[Codex] Received 401 during getUsageLimits. Triggering background refresh via PoolManager...');
logger.info('[Codex] Received 401 during getUsageLimits. Triggering background refresh...');
// 标记当前凭证为不健康
const poolManager = getProviderPoolManager();
if (poolManager && this.uuid) {
logger.info(`[Codex] Marking credential ${this.uuid} as needs refresh. Reason: 401 Unauthorized in getUsageLimits`);
poolManager.markProviderNeedRefresh(MODEL_PROVIDER.CODEX_API, {
uuid: this.uuid
});
error.credentialMarkedUnhealthy = true;
}
// 触发后台异步刷新
this.triggerBackgroundRefresh();
error.credentialMarkedUnhealthy = true;
// Mark error for credential switch without recording error count
error.shouldSwitchCredential = true;