@jsonstudio/rcc 0.89.164 → 0.89.168
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.js +3 -3
- package/dist/build-info.js.map +1 -1
- package/dist/providers/core/config/service-profiles.js +15 -1
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/runtime/base-provider.d.ts +6 -1
- package/dist/providers/core/runtime/base-provider.js +51 -103
- package/dist/providers/core/runtime/base-provider.js.map +1 -1
- package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +1 -2
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +9 -5
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/gemini-http-provider.d.ts +1 -2
- package/dist/providers/core/runtime/gemini-http-provider.js +0 -12
- package/dist/providers/core/runtime/gemini-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/http-request-executor.d.ts +42 -0
- package/dist/providers/core/runtime/http-request-executor.js +133 -0
- package/dist/providers/core/runtime/http-request-executor.js.map +1 -0
- package/dist/providers/core/runtime/http-transport-provider.d.ts +7 -12
- package/dist/providers/core/runtime/http-transport-provider.js +168 -368
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/runtime/provider-error-classifier.d.ts +25 -0
- package/dist/providers/core/runtime/provider-error-classifier.js +139 -0
- package/dist/providers/core/runtime/provider-error-classifier.js.map +1 -0
- package/dist/providers/core/runtime/provider-error-types.d.ts +23 -0
- package/dist/providers/core/runtime/provider-error-types.js +2 -0
- package/dist/providers/core/runtime/provider-error-types.js.map +1 -0
- package/dist/providers/core/runtime/provider-factory.js +6 -0
- package/dist/providers/core/runtime/provider-factory.js.map +1 -1
- package/package.json +3 -2
- package/scripts/pack-mode.mjs +30 -1
- package/scripts/publish-rcc.mjs +31 -0
|
@@ -16,83 +16,34 @@ import { OAuthAuthProvider } from '../../auth/oauth-auth.js';
|
|
|
16
16
|
import { logOAuthDebug } from '../../auth/oauth-logger.js';
|
|
17
17
|
import { TokenFileAuthProvider } from '../../auth/tokenfile-auth.js';
|
|
18
18
|
import { ensureValidOAuthToken, handleUpstreamInvalidOAuthToken } from '../../auth/oauth-lifecycle.js';
|
|
19
|
-
import {
|
|
20
|
-
import { attachProviderSseSnapshotStream, shouldCaptureProviderStreamSnapshots, writeProviderSnapshot } from '../utils/snapshot-writer.js';
|
|
19
|
+
import { attachProviderSseSnapshotStream, writeProviderSnapshot } from '../utils/snapshot-writer.js';
|
|
21
20
|
import { attachProviderRuntimeMetadata } from './provider-runtime-metadata.js';
|
|
22
21
|
import { OpenAIChatProtocolClient } from '../../../client/openai/chat-protocol-client.js';
|
|
23
|
-
|
|
22
|
+
import { HttpRequestExecutor } from './http-request-executor.js';
|
|
23
|
+
import { extractStatusCodeFromError } from './provider-error-classifier.js';
|
|
24
24
|
const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
25
|
-
function createNoopHookSystemIntegration() {
|
|
26
|
-
const passthrough = async (_stage, _target, data) => ({
|
|
27
|
-
data,
|
|
28
|
-
metrics: {
|
|
29
|
-
executionTime: 0,
|
|
30
|
-
hookCount: 0,
|
|
31
|
-
successCount: 0,
|
|
32
|
-
readCount: 0,
|
|
33
|
-
writeCount: 0,
|
|
34
|
-
transformCount: 0
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
return {
|
|
38
|
-
getBidirectionalHookManager: () => ({
|
|
39
|
-
registerHook: () => { },
|
|
40
|
-
unregisterHook: () => { },
|
|
41
|
-
executeHookChain: passthrough,
|
|
42
|
-
setDebugConfig: () => { }
|
|
43
|
-
}),
|
|
44
|
-
setDebugConfig: () => { },
|
|
45
|
-
initialize: async () => { },
|
|
46
|
-
getStats: () => ({ enabled: false }),
|
|
47
|
-
healthCheck: async () => ({ healthy: true }),
|
|
48
|
-
start: async () => { },
|
|
49
|
-
stop: async () => { },
|
|
50
|
-
shutdown: async () => { }
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
const readStatusCodeFromResponse = (error) => {
|
|
54
|
-
const response = error?.response;
|
|
55
|
-
const directStatus = typeof response?.status === 'number' ? response.status : undefined;
|
|
56
|
-
const directStatusCode = typeof response?.statusCode === 'number'
|
|
57
|
-
? response.statusCode
|
|
58
|
-
: undefined;
|
|
59
|
-
const nestedStatus = response &&
|
|
60
|
-
typeof response === 'object' &&
|
|
61
|
-
typeof response?.data?.status === 'number'
|
|
62
|
-
? response.data.status
|
|
63
|
-
: undefined;
|
|
64
|
-
const nestedErrorStatus = response &&
|
|
65
|
-
typeof response === 'object' &&
|
|
66
|
-
typeof response?.data?.error?.status === 'number'
|
|
67
|
-
? response.data.error.status
|
|
68
|
-
: undefined;
|
|
69
|
-
return [directStatus, directStatusCode, nestedStatus, nestedErrorStatus].find((candidate) => typeof candidate === 'number' && Number.isFinite(candidate));
|
|
70
|
-
};
|
|
71
25
|
const DEFAULT_USER_AGENT = 'RouteCodex/2.0';
|
|
72
26
|
export class HttpTransportProvider extends BaseProvider {
|
|
73
27
|
type;
|
|
74
28
|
authProvider = null;
|
|
75
29
|
httpClient;
|
|
76
30
|
serviceProfile;
|
|
77
|
-
hookSystemIntegration;
|
|
78
31
|
protocolClient;
|
|
32
|
+
requestExecutor;
|
|
79
33
|
injectedConfig = null;
|
|
80
|
-
hooksEnabled;
|
|
81
34
|
constructor(config, dependencies, moduleType, protocolClient) {
|
|
82
35
|
super(config, dependencies);
|
|
83
36
|
this.type = moduleType;
|
|
84
37
|
this.protocolClient = protocolClient ?? new OpenAIChatProtocolClient();
|
|
85
|
-
this.hooksEnabled = ENABLE_PROVIDER_HOOKS;
|
|
86
38
|
// 获取服务配置档案
|
|
87
39
|
this.serviceProfile = this.getServiceProfile();
|
|
88
40
|
// 验证配置
|
|
89
41
|
this.validateConfig();
|
|
90
42
|
// 创建HTTP客户端
|
|
91
43
|
this.createHttpClient();
|
|
44
|
+
this.requestExecutor = new HttpRequestExecutor(this.httpClient, this.createRequestExecutorDeps());
|
|
92
45
|
// 创建认证提供者
|
|
93
46
|
this.authProvider = this.createAuthProvider();
|
|
94
|
-
// 初始化Hook系统集成
|
|
95
|
-
this.hookSystemIntegration = this.initializeHookSystem();
|
|
96
47
|
}
|
|
97
48
|
/**
|
|
98
49
|
* 确保认证提供者完成初始化(避免 ApiKeyAuthProvider 未初始化导致的报错)
|
|
@@ -104,7 +55,8 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
104
55
|
const providerConfig = this.config.config;
|
|
105
56
|
const extensions = this.getConfigExtensions();
|
|
106
57
|
const auth = providerConfig.auth;
|
|
107
|
-
|
|
58
|
+
const usesTokenFile = this.authProvider instanceof TokenFileAuthProvider;
|
|
59
|
+
if (this.normalizeAuthMode(auth.type) === 'oauth' && !usesTokenFile) {
|
|
108
60
|
const oauthAuth = auth;
|
|
109
61
|
const oauthProviderId = this.ensureOAuthProviderId(oauthAuth, extensions);
|
|
110
62
|
const forceReauthorize = false;
|
|
@@ -163,14 +115,6 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
163
115
|
}
|
|
164
116
|
}
|
|
165
117
|
}
|
|
166
|
-
if (this.hooksEnabled) {
|
|
167
|
-
await this.hookSystemIntegration.initialize();
|
|
168
|
-
this.configureHookDebugging();
|
|
169
|
-
this.dependencies.logger?.logModule(this.id, 'provider-hook-system-initialized', {
|
|
170
|
-
providerType: this.providerType,
|
|
171
|
-
integrationEnabled: true
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
118
|
}
|
|
175
119
|
catch (error) {
|
|
176
120
|
// 暴露问题,快速失败,便于定位凭证问题
|
|
@@ -200,62 +144,6 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
200
144
|
getConfig() {
|
|
201
145
|
return this.injectedConfig ?? this.config.config ?? null;
|
|
202
146
|
}
|
|
203
|
-
/**
|
|
204
|
-
* 初始化Hook系统集成
|
|
205
|
-
*/
|
|
206
|
-
initializeHookSystem() {
|
|
207
|
-
if (!this.hooksEnabled) {
|
|
208
|
-
return createNoopHookSystemIntegration();
|
|
209
|
-
}
|
|
210
|
-
return createHookSystemIntegration(this.dependencies, this.id, {
|
|
211
|
-
enabled: true,
|
|
212
|
-
debugMode: true,
|
|
213
|
-
snapshotEnabled: true,
|
|
214
|
-
migrationMode: true
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* 配置Hook调试(保持向后兼容)
|
|
219
|
-
*/
|
|
220
|
-
configureHookDebugging() {
|
|
221
|
-
if (!this.hooksEnabled) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
try {
|
|
225
|
-
// 设置调试配置(使用统一Hook系统的阶段字符串)
|
|
226
|
-
const debugConfig = {
|
|
227
|
-
enabled: true,
|
|
228
|
-
level: 'verbose',
|
|
229
|
-
maxDataSize: 1024 * 64, // 64KB 单次输出上限,避免过大控制台噪声
|
|
230
|
-
stages: [
|
|
231
|
-
'request_preprocessing',
|
|
232
|
-
'request_validation',
|
|
233
|
-
'authentication',
|
|
234
|
-
'http_request',
|
|
235
|
-
'http_response',
|
|
236
|
-
'response_validation',
|
|
237
|
-
'response_postprocessing',
|
|
238
|
-
'error_handling'
|
|
239
|
-
],
|
|
240
|
-
outputFormat: 'structured',
|
|
241
|
-
outputTargets: ['console'],
|
|
242
|
-
performanceThresholds: {
|
|
243
|
-
maxHookExecutionTime: 500, // 单个Hook 500ms告警
|
|
244
|
-
maxTotalExecutionTime: 5000, // 阶段总时长 5s 告警
|
|
245
|
-
maxDataSize: 1024 * 256 // 256KB 数据告警
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
|
-
this.hookSystemIntegration.setDebugConfig(debugConfig);
|
|
249
|
-
this.dependencies.logger?.logModule(this.id, 'provider-debug-hooks-configured', {
|
|
250
|
-
providerType: this.providerType
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
catch (error) {
|
|
254
|
-
this.dependencies.logger?.logModule(this.id, 'provider-debug-hooks-error', {
|
|
255
|
-
error: error instanceof Error ? error.message : String(error)
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
147
|
getServiceProfile() {
|
|
260
148
|
const cfg = this.config.config;
|
|
261
149
|
const profileKey = this.resolveProfileKey(cfg);
|
|
@@ -349,13 +237,17 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
349
237
|
const auth = this.config.config.auth;
|
|
350
238
|
const extensions = this.getConfigExtensions();
|
|
351
239
|
const authMode = this.normalizeAuthMode(auth.type);
|
|
352
|
-
|
|
240
|
+
this.authMode = authMode;
|
|
241
|
+
let providerIdForAuth = authMode === 'oauth'
|
|
353
242
|
? this.ensureOAuthProviderId(auth, extensions)
|
|
354
243
|
: this.providerType;
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
244
|
+
if (this.type === 'gemini-cli-http-provider') {
|
|
245
|
+
providerIdForAuth = 'gemini-cli';
|
|
246
|
+
}
|
|
247
|
+
if (authMode === 'oauth') {
|
|
248
|
+
this.oauthProviderId = providerIdForAuth;
|
|
249
|
+
}
|
|
250
|
+
const validation = ServiceProfileValidator.validateServiceProfile(providerIdForAuth, authMode);
|
|
359
251
|
if (!validation.isValid) {
|
|
360
252
|
throw new Error(`Invalid auth configuration for ${providerIdForAuth}: ${validation.errors.join(', ')}`);
|
|
361
253
|
}
|
|
@@ -405,6 +297,27 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
405
297
|
}
|
|
406
298
|
});
|
|
407
299
|
}
|
|
300
|
+
createRequestExecutorDeps() {
|
|
301
|
+
return {
|
|
302
|
+
wantsUpstreamSse: this.wantsUpstreamSse.bind(this),
|
|
303
|
+
getEffectiveEndpoint: () => this.getEffectiveEndpoint(),
|
|
304
|
+
resolveRequestEndpoint: this.resolveRequestEndpoint.bind(this),
|
|
305
|
+
buildRequestHeaders: this.buildRequestHeaders.bind(this),
|
|
306
|
+
finalizeRequestHeaders: this.finalizeRequestHeaders.bind(this),
|
|
307
|
+
applyStreamModeHeaders: this.applyStreamModeHeaders.bind(this),
|
|
308
|
+
getEffectiveBaseUrl: () => this.getEffectiveBaseUrl(),
|
|
309
|
+
buildHttpRequestBody: this.buildHttpRequestBody.bind(this),
|
|
310
|
+
prepareSseRequestBody: this.prepareSseRequestBody.bind(this),
|
|
311
|
+
getEntryEndpointFromPayload: this.getEntryEndpointFromPayload.bind(this),
|
|
312
|
+
getClientRequestIdFromContext: this.getClientRequestIdFromContext.bind(this),
|
|
313
|
+
wrapUpstreamSseResponse: this.wrapUpstreamSseResponse.bind(this),
|
|
314
|
+
getHttpRetryLimit: () => this.getHttpRetryLimit(),
|
|
315
|
+
shouldRetryHttpError: this.shouldRetryHttpError.bind(this),
|
|
316
|
+
delayBeforeHttpRetry: this.delayBeforeHttpRetry.bind(this),
|
|
317
|
+
tryRecoverOAuthAndReplay: this.tryRecoverOAuthAndReplay.bind(this),
|
|
318
|
+
normalizeHttpError: this.normalizeHttpError.bind(this)
|
|
319
|
+
};
|
|
320
|
+
}
|
|
408
321
|
async preprocessRequest(request) {
|
|
409
322
|
const context = this.createProviderContext();
|
|
410
323
|
const runtimeMetadata = context.runtimeMetadata;
|
|
@@ -447,36 +360,12 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
447
360
|
}
|
|
448
361
|
}
|
|
449
362
|
catch { /* ignore */ }
|
|
450
|
-
// 获取Hook管理器(新的统一系统)
|
|
451
|
-
const hookManager = this.getHookManager();
|
|
452
|
-
// 🔍 Hook 1: 请求预处理阶段
|
|
453
|
-
const preprocessResult = await hookManager.executeHookChain('request_preprocessing', 'request', processedRequest, context);
|
|
454
|
-
processedRequest = preprocessResult.data;
|
|
455
|
-
ensureRuntimeMetadata(processedRequest);
|
|
456
|
-
// 🔍 Hook 2: 请求验证阶段
|
|
457
|
-
const validationResult = await hookManager.executeHookChain('request_validation', 'request', processedRequest, context);
|
|
458
|
-
processedRequest = validationResult.data;
|
|
459
|
-
ensureRuntimeMetadata(processedRequest);
|
|
460
|
-
// Provider 层不再修改工具 schema;统一入口在 llmswitch-core/兼容层
|
|
461
|
-
// Provider 层不做协议兼容改写:compatibility 由 llmswitch-core Hub Pipeline 统一处理。
|
|
462
363
|
return processedRequest;
|
|
463
364
|
}
|
|
464
365
|
async postprocessResponse(response, context) {
|
|
465
366
|
const runtime = this.getRuntimeProfile();
|
|
466
367
|
const processingTime = Date.now() - context.startTime;
|
|
467
368
|
let processedResponse = response;
|
|
468
|
-
// 获取Hook管理器(新的统一系统)
|
|
469
|
-
const hookManager = this.getHookManager();
|
|
470
|
-
// 🔍 Hook 3: HTTP响应阶段
|
|
471
|
-
const httpResponseResult = await hookManager.executeHookChain('http_response', 'response', processedResponse, context);
|
|
472
|
-
processedResponse = httpResponseResult.data;
|
|
473
|
-
// 🔍 Hook 4: 响应验证阶段
|
|
474
|
-
const validationResult = await hookManager.executeHookChain('response_validation', 'response', processedResponse, context);
|
|
475
|
-
processedResponse = validationResult.data;
|
|
476
|
-
// 🔍 Hook 5: 响应后处理阶段
|
|
477
|
-
const postprocessResult = await hookManager.executeHookChain('response_postprocessing', 'response', processedResponse, context);
|
|
478
|
-
processedResponse = postprocessResult.data;
|
|
479
|
-
// Provider 层不做协议兼容改写:compatibility 由 llmswitch-core Hub Pipeline 统一处理。
|
|
480
369
|
const originalRecord = this.asResponseRecord(response);
|
|
481
370
|
const processedRecord = this.asResponseRecord(processedResponse);
|
|
482
371
|
const sseStream = processedRecord.__sse_responses ||
|
|
@@ -494,221 +383,13 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
494
383
|
providerType: this.providerType,
|
|
495
384
|
// 对外暴露的 model 统一为入站模型
|
|
496
385
|
model: context.model ?? this.extractModel(processedRecord) ?? this.extractModel(originalRecord),
|
|
497
|
-
usage: this.extractUsage(processedRecord) ?? this.extractUsage(originalRecord)
|
|
498
|
-
hookMetrics: {
|
|
499
|
-
httpResponse: httpResponseResult.metrics,
|
|
500
|
-
validation: validationResult.metrics,
|
|
501
|
-
postprocess: postprocessResult.metrics
|
|
502
|
-
}
|
|
386
|
+
usage: this.extractUsage(processedRecord) ?? this.extractUsage(originalRecord)
|
|
503
387
|
}
|
|
504
388
|
};
|
|
505
389
|
}
|
|
506
390
|
async sendRequestInternal(request) {
|
|
507
391
|
const context = this.createProviderContext();
|
|
508
|
-
|
|
509
|
-
const hookManager = this.getHookManager();
|
|
510
|
-
// 🔍 Hook 8: HTTP请求阶段
|
|
511
|
-
const httpRequestResult = await hookManager.executeHookChain('http_request', 'request', request, context);
|
|
512
|
-
const processedRequest = httpRequestResult.data;
|
|
513
|
-
const wantsSse = this.wantsUpstreamSse(processedRequest, context);
|
|
514
|
-
// 仅传入 endpoint,让 HttpClient 按 baseUrl 进行拼接;避免 full URL 再次拼接导致 /https:/ 重复
|
|
515
|
-
const defaultEndpoint = this.getEffectiveEndpoint();
|
|
516
|
-
const endpoint = this.resolveRequestEndpoint(processedRequest, defaultEndpoint);
|
|
517
|
-
const headers = await this.buildRequestHeaders();
|
|
518
|
-
let finalHeaders = await this.finalizeRequestHeaders(headers, processedRequest);
|
|
519
|
-
finalHeaders = this.applyStreamModeHeaders(finalHeaders, wantsSse);
|
|
520
|
-
const targetUrl = `${this.getEffectiveBaseUrl().replace(/\/$/, '')}/${endpoint.startsWith('/') ? endpoint.slice(1) : endpoint}`;
|
|
521
|
-
// Flatten request body to standard OpenAI Chat JSON
|
|
522
|
-
const finalBody = this.buildHttpRequestBody(processedRequest);
|
|
523
|
-
if (wantsSse) {
|
|
524
|
-
this.prepareSseRequestBody(finalBody, context);
|
|
525
|
-
}
|
|
526
|
-
const entryEndpoint = this.getEntryEndpointFromPayload(processedRequest);
|
|
527
|
-
const clientRequestId = this.getClientRequestIdFromContext(context);
|
|
528
|
-
// 快照:provider-request(默认开启,脱敏headers)
|
|
529
|
-
try {
|
|
530
|
-
await writeProviderSnapshot({
|
|
531
|
-
phase: 'provider-request',
|
|
532
|
-
requestId: context.requestId,
|
|
533
|
-
data: finalBody,
|
|
534
|
-
headers: finalHeaders,
|
|
535
|
-
url: targetUrl,
|
|
536
|
-
entryEndpoint,
|
|
537
|
-
clientRequestId
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
catch { /* non-blocking */ }
|
|
541
|
-
// 发送HTTP请求(根据是否需要 SSE 决定传输模式)
|
|
542
|
-
let response;
|
|
543
|
-
const captureSse = shouldCaptureProviderStreamSnapshots();
|
|
544
|
-
try {
|
|
545
|
-
if (wantsSse) {
|
|
546
|
-
const upstreamStream = await this.httpClient.postStream(endpoint, finalBody, finalHeaders);
|
|
547
|
-
const streamForHost = captureSse
|
|
548
|
-
? attachProviderSseSnapshotStream(upstreamStream, {
|
|
549
|
-
requestId: context.requestId,
|
|
550
|
-
headers: finalHeaders,
|
|
551
|
-
url: targetUrl,
|
|
552
|
-
entryEndpoint,
|
|
553
|
-
clientRequestId
|
|
554
|
-
})
|
|
555
|
-
: upstreamStream;
|
|
556
|
-
response = await this.wrapUpstreamSseResponse(streamForHost, context);
|
|
557
|
-
if (!captureSse) {
|
|
558
|
-
try {
|
|
559
|
-
await writeProviderSnapshot({
|
|
560
|
-
phase: 'provider-response',
|
|
561
|
-
requestId: context.requestId,
|
|
562
|
-
data: { mode: 'sse' },
|
|
563
|
-
headers: finalHeaders,
|
|
564
|
-
url: targetUrl,
|
|
565
|
-
entryEndpoint,
|
|
566
|
-
clientRequestId
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
catch { /* non-blocking */ }
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
else {
|
|
573
|
-
response = await this.httpClient.post(endpoint, finalBody, finalHeaders);
|
|
574
|
-
try {
|
|
575
|
-
await writeProviderSnapshot({
|
|
576
|
-
phase: 'provider-response',
|
|
577
|
-
requestId: context.requestId,
|
|
578
|
-
data: response,
|
|
579
|
-
headers: finalHeaders,
|
|
580
|
-
url: targetUrl,
|
|
581
|
-
entryEndpoint,
|
|
582
|
-
clientRequestId
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
catch { /* non-blocking */ }
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
catch (error) {
|
|
589
|
-
// OAuth token 失效:尝试刷新/重获并重试一次
|
|
590
|
-
try {
|
|
591
|
-
const providerAuth = this.config.config.auth;
|
|
592
|
-
if (this.normalizeAuthMode(providerAuth.type) === 'oauth') {
|
|
593
|
-
const shouldRetry = await handleUpstreamInvalidOAuthToken(this.providerType, providerAuth, error);
|
|
594
|
-
if (shouldRetry) {
|
|
595
|
-
const retryHeaders = await this.buildRequestHeaders();
|
|
596
|
-
let finalRetryHeaders = await this.finalizeRequestHeaders(retryHeaders, processedRequest);
|
|
597
|
-
finalRetryHeaders = this.applyStreamModeHeaders(finalRetryHeaders, wantsSse);
|
|
598
|
-
if (wantsSse) {
|
|
599
|
-
const upstreamStream = await this.httpClient.postStream(endpoint, finalBody, finalRetryHeaders);
|
|
600
|
-
const streamForHost = captureSse
|
|
601
|
-
? attachProviderSseSnapshotStream(upstreamStream, {
|
|
602
|
-
requestId: context.requestId,
|
|
603
|
-
headers: finalRetryHeaders,
|
|
604
|
-
url: targetUrl,
|
|
605
|
-
entryEndpoint,
|
|
606
|
-
clientRequestId,
|
|
607
|
-
extra: { retry: true }
|
|
608
|
-
})
|
|
609
|
-
: upstreamStream;
|
|
610
|
-
const wrapped = await this.wrapUpstreamSseResponse(streamForHost, context);
|
|
611
|
-
if (!captureSse) {
|
|
612
|
-
try {
|
|
613
|
-
await writeProviderSnapshot({
|
|
614
|
-
phase: 'provider-response',
|
|
615
|
-
requestId: context.requestId,
|
|
616
|
-
data: { mode: 'sse', retry: true },
|
|
617
|
-
headers: finalRetryHeaders,
|
|
618
|
-
url: targetUrl,
|
|
619
|
-
entryEndpoint,
|
|
620
|
-
clientRequestId
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
catch { /* non-blocking */ }
|
|
624
|
-
}
|
|
625
|
-
return wrapped;
|
|
626
|
-
}
|
|
627
|
-
response = await this.httpClient.post(endpoint, finalBody, finalRetryHeaders);
|
|
628
|
-
try {
|
|
629
|
-
await writeProviderSnapshot({
|
|
630
|
-
phase: 'provider-response',
|
|
631
|
-
requestId: context.requestId,
|
|
632
|
-
data: response,
|
|
633
|
-
headers: finalRetryHeaders,
|
|
634
|
-
url: targetUrl,
|
|
635
|
-
entryEndpoint,
|
|
636
|
-
clientRequestId
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
|
-
catch { /* non-blocking */ }
|
|
640
|
-
return response;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
catch { /* ignore and fallthrough */ }
|
|
645
|
-
// 🔍 Hook 9: 错误处理阶段
|
|
646
|
-
const errorResult = await hookManager.executeHookChain('error_handling', 'error', { error, request: processedRequest, url: targetUrl, headers: finalHeaders }, context);
|
|
647
|
-
// 如果Hook处理了错误,使用Hook的返回结果
|
|
648
|
-
const hookErrorData = errorResult.data;
|
|
649
|
-
if (hookErrorData && hookErrorData.error === false) {
|
|
650
|
-
return hookErrorData;
|
|
651
|
-
}
|
|
652
|
-
// 规范化错误:补充结构化字段,移除仅文本填充的旧做法
|
|
653
|
-
const normalized = error;
|
|
654
|
-
try {
|
|
655
|
-
const msg = typeof normalized.message === 'string' ? normalized.message : String(normalized || '');
|
|
656
|
-
const m = msg.match(/HTTP\s+(\d{3})/i);
|
|
657
|
-
const parsedStatus = m ? parseInt(m[1], 10) : undefined;
|
|
658
|
-
const responseStatus = readStatusCodeFromResponse(normalized);
|
|
659
|
-
const statusCode = Number.isFinite(normalized.statusCode)
|
|
660
|
-
? Number(normalized.statusCode)
|
|
661
|
-
: Number.isFinite(normalized.status)
|
|
662
|
-
? Number(normalized.status)
|
|
663
|
-
: responseStatus ?? parsedStatus ?? undefined;
|
|
664
|
-
if (statusCode && !Number.isNaN(statusCode)) {
|
|
665
|
-
normalized.statusCode = statusCode;
|
|
666
|
-
if (!normalized.status) {
|
|
667
|
-
normalized.status = statusCode;
|
|
668
|
-
}
|
|
669
|
-
if (!normalized.code) {
|
|
670
|
-
normalized.code = `HTTP_${statusCode}`;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
// 兼容 Manager 的 code 路径(response.data.error.code)
|
|
674
|
-
if (!normalized.response) {
|
|
675
|
-
normalized.response = {};
|
|
676
|
-
}
|
|
677
|
-
if (!normalized.response.data) {
|
|
678
|
-
normalized.response.data = {};
|
|
679
|
-
}
|
|
680
|
-
if (!normalized.response.data.error) {
|
|
681
|
-
normalized.response.data.error = {};
|
|
682
|
-
}
|
|
683
|
-
if (normalized.code && !normalized.response.data.error.code) {
|
|
684
|
-
normalized.response.data.error.code = normalized.code;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
catch { /* keep original */ }
|
|
688
|
-
// 快照:provider-error(结构化写入)
|
|
689
|
-
try {
|
|
690
|
-
await writeProviderSnapshot({
|
|
691
|
-
phase: 'provider-error',
|
|
692
|
-
requestId: context.requestId,
|
|
693
|
-
data: {
|
|
694
|
-
status: normalized?.statusCode ?? normalized?.status ?? null,
|
|
695
|
-
code: normalized?.code ?? null,
|
|
696
|
-
error: typeof normalized?.message === 'string' ? normalized.message : String(normalized || '')
|
|
697
|
-
},
|
|
698
|
-
headers: finalHeaders,
|
|
699
|
-
url: targetUrl,
|
|
700
|
-
entryEndpoint,
|
|
701
|
-
clientRequestId
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
catch { /* non-blocking */ }
|
|
705
|
-
throw normalized;
|
|
706
|
-
}
|
|
707
|
-
// Provider 不处理工具修复/注入逻辑:统一收敛到 llmswitch-core 与兼容层
|
|
708
|
-
// 此处不做任何自动修复/重试,保持单次请求的幂等与可观测性
|
|
709
|
-
try { /* no-op */ }
|
|
710
|
-
catch { /* ignore */ }
|
|
711
|
-
return response;
|
|
392
|
+
return this.requestExecutor.execute(request, context);
|
|
712
393
|
}
|
|
713
394
|
wantsUpstreamSse(_request, _context) {
|
|
714
395
|
return false;
|
|
@@ -744,6 +425,135 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
744
425
|
return false;
|
|
745
426
|
}
|
|
746
427
|
}
|
|
428
|
+
getHttpRetryLimit() {
|
|
429
|
+
return 3;
|
|
430
|
+
}
|
|
431
|
+
async delayBeforeHttpRetry(attempt) {
|
|
432
|
+
const delay = Math.min(500 * attempt, 2000);
|
|
433
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
434
|
+
}
|
|
435
|
+
shouldRetryHttpError(error, attempt, maxAttempts) {
|
|
436
|
+
if (attempt >= maxAttempts) {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
const normalized = error;
|
|
440
|
+
const statusCode = extractStatusCodeFromError(normalized);
|
|
441
|
+
if (statusCode && statusCode >= 500) {
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
async tryRecoverOAuthAndReplay(error, requestInfo, processedRequest, captureSse, context) {
|
|
447
|
+
try {
|
|
448
|
+
const providerAuth = this.config.config.auth;
|
|
449
|
+
if (this.normalizeAuthMode(providerAuth.type) !== 'oauth') {
|
|
450
|
+
return undefined;
|
|
451
|
+
}
|
|
452
|
+
const shouldRetry = await handleUpstreamInvalidOAuthToken(this.oauthProviderId || this.providerType, providerAuth, error);
|
|
453
|
+
if (!shouldRetry) {
|
|
454
|
+
return undefined;
|
|
455
|
+
}
|
|
456
|
+
const retryHeaders = await this.buildRequestHeaders();
|
|
457
|
+
let finalRetryHeaders = await this.finalizeRequestHeaders(retryHeaders, processedRequest);
|
|
458
|
+
finalRetryHeaders = this.applyStreamModeHeaders(finalRetryHeaders, requestInfo.wantsSse);
|
|
459
|
+
if (requestInfo.wantsSse) {
|
|
460
|
+
const upstreamStream = await this.httpClient.postStream(requestInfo.endpoint, requestInfo.body, finalRetryHeaders);
|
|
461
|
+
const streamForHost = captureSse
|
|
462
|
+
? attachProviderSseSnapshotStream(upstreamStream, {
|
|
463
|
+
requestId: context.requestId,
|
|
464
|
+
headers: finalRetryHeaders,
|
|
465
|
+
url: requestInfo.targetUrl,
|
|
466
|
+
entryEndpoint: requestInfo.entryEndpoint,
|
|
467
|
+
clientRequestId: requestInfo.clientRequestId,
|
|
468
|
+
extra: { retry: true }
|
|
469
|
+
})
|
|
470
|
+
: upstreamStream;
|
|
471
|
+
const wrapped = await this.wrapUpstreamSseResponse(streamForHost, context);
|
|
472
|
+
if (!captureSse) {
|
|
473
|
+
try {
|
|
474
|
+
await writeProviderSnapshot({
|
|
475
|
+
phase: 'provider-response',
|
|
476
|
+
requestId: context.requestId,
|
|
477
|
+
data: { mode: 'sse', retry: true },
|
|
478
|
+
headers: finalRetryHeaders,
|
|
479
|
+
url: requestInfo.targetUrl,
|
|
480
|
+
entryEndpoint: requestInfo.entryEndpoint,
|
|
481
|
+
clientRequestId: requestInfo.clientRequestId
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
catch { /* non-blocking */ }
|
|
485
|
+
}
|
|
486
|
+
return wrapped;
|
|
487
|
+
}
|
|
488
|
+
const response = await this.httpClient.post(requestInfo.endpoint, requestInfo.body, finalRetryHeaders);
|
|
489
|
+
try {
|
|
490
|
+
await writeProviderSnapshot({
|
|
491
|
+
phase: 'provider-response',
|
|
492
|
+
requestId: context.requestId,
|
|
493
|
+
data: response,
|
|
494
|
+
headers: finalRetryHeaders,
|
|
495
|
+
url: requestInfo.targetUrl,
|
|
496
|
+
entryEndpoint: requestInfo.entryEndpoint,
|
|
497
|
+
clientRequestId: requestInfo.clientRequestId
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
catch { /* non-blocking */ }
|
|
501
|
+
return response;
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
async normalizeHttpError(error, processedRequest, requestInfo, context) {
|
|
508
|
+
const normalized = error;
|
|
509
|
+
try {
|
|
510
|
+
const statusCode = extractStatusCodeFromError(normalized);
|
|
511
|
+
if (statusCode && !Number.isNaN(statusCode)) {
|
|
512
|
+
normalized.statusCode = statusCode;
|
|
513
|
+
if (!normalized.status) {
|
|
514
|
+
normalized.status = statusCode;
|
|
515
|
+
}
|
|
516
|
+
if (!normalized.code) {
|
|
517
|
+
normalized.code = `HTTP_${statusCode}`;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (!normalized.response) {
|
|
521
|
+
normalized.response = {};
|
|
522
|
+
}
|
|
523
|
+
if (!normalized.response.data) {
|
|
524
|
+
normalized.response.data = {};
|
|
525
|
+
}
|
|
526
|
+
if (!normalized.response.data.error) {
|
|
527
|
+
normalized.response.data.error = {};
|
|
528
|
+
}
|
|
529
|
+
if (normalized.code && !normalized.response.data.error.code) {
|
|
530
|
+
normalized.response.data.error.code = normalized.code;
|
|
531
|
+
}
|
|
532
|
+
if (normalized.message && !normalized.response.data.error.message) {
|
|
533
|
+
normalized.response.data.error.message = normalized.message;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
/* ignore */
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
await writeProviderSnapshot({
|
|
541
|
+
phase: 'provider-error',
|
|
542
|
+
requestId: context.requestId,
|
|
543
|
+
data: {
|
|
544
|
+
status: normalized?.statusCode ?? normalized?.status ?? null,
|
|
545
|
+
code: normalized?.code ?? null,
|
|
546
|
+
error: typeof normalized?.message === 'string' ? normalized.message : String(error || '')
|
|
547
|
+
},
|
|
548
|
+
headers: requestInfo.headers,
|
|
549
|
+
url: requestInfo.targetUrl,
|
|
550
|
+
entryEndpoint: requestInfo.entryEndpoint ?? this.getEntryEndpointFromPayload(processedRequest),
|
|
551
|
+
clientRequestId: requestInfo.clientRequestId ?? this.getClientRequestIdFromContext(context)
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
catch { /* non-blocking */ }
|
|
555
|
+
return normalized;
|
|
556
|
+
}
|
|
747
557
|
/**
|
|
748
558
|
* 为特定请求确定最终 endpoint(默认使用配置值,可由子类覆写)
|
|
749
559
|
*/
|
|
@@ -888,13 +698,6 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
888
698
|
if (resolvedOriginator) {
|
|
889
699
|
setHeader(finalHeaders, 'originator', resolvedOriginator);
|
|
890
700
|
}
|
|
891
|
-
// 获取Hook管理器(新的统一系统)
|
|
892
|
-
const hookManager = this.getHookManager();
|
|
893
|
-
// 🔍 Hook 6: 认证阶段
|
|
894
|
-
await hookManager.executeHookChain('authentication', 'auth', authHeaders, this.createProviderContext());
|
|
895
|
-
// 🔍 Hook 7: Headers处理阶段
|
|
896
|
-
const headersResult = await hookManager.executeHookChain('request_preprocessing', 'headers', finalHeaders, this.createProviderContext());
|
|
897
|
-
finalHeaders = headersResult.data;
|
|
898
701
|
return finalHeaders;
|
|
899
702
|
}
|
|
900
703
|
getEffectiveBaseUrl() {
|
|
@@ -934,9 +737,6 @@ export class HttpTransportProvider extends BaseProvider {
|
|
|
934
737
|
const trimmed = value.trim();
|
|
935
738
|
return /^https?:\/\//i.test(trimmed) || trimmed.startsWith('//');
|
|
936
739
|
}
|
|
937
|
-
getHookManager() {
|
|
938
|
-
return this.hookSystemIntegration.getBidirectionalHookManager();
|
|
939
|
-
}
|
|
940
740
|
// (工具自动修复辅助函数已删除)
|
|
941
741
|
getConfigExtensions() {
|
|
942
742
|
const extensions = this.config.config.extensions;
|