coding-tool-x 3.3.8 → 3.4.0
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/CHANGELOG.md +17 -2
- package/README.md +253 -326
- package/dist/web/assets/{Analytics-DLpoDZ2M.js → Analytics-DEjfL5Jx.js} +4 -4
- package/dist/web/assets/Analytics-RNn1BUbG.css +1 -0
- package/dist/web/assets/{ConfigTemplates-D_hRb55W.js → ConfigTemplates-DkRL_-tf.js} +1 -1
- package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
- package/dist/web/assets/Home-CF-L640I.js +1 -0
- package/dist/web/assets/{PluginManager-JXsyym1s.js → PluginManager-BzNYTdNB.js} +1 -1
- package/dist/web/assets/{ProjectList-DZWSeb-q.js → ProjectList-C0-JgHMM.js} +1 -1
- package/dist/web/assets/{SessionList-Cs624DR3.js → SessionList-CkZUdX5N.js} +1 -1
- package/dist/web/assets/{SkillManager-bEliz7qz.js → SkillManager-Cak0-4d4.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-J3RecFGn.js → WorkspaceManager-CGDJzwEr.js} +1 -1
- package/dist/web/assets/{icons-Cuc23WS7.js → icons-B5Pl4lrD.js} +1 -1
- package/dist/web/assets/index-D_WItvHE.js +2 -0
- package/dist/web/assets/index-Dz7v9OM0.css +1 -0
- package/dist/web/assets/{markdown-C9MYpaSi.js → markdown-DyTJGI4N.js} +1 -1
- package/dist/web/assets/{naive-ui-CxpuzdjU.js → naive-ui-Bdxp09n2.js} +1 -1
- package/dist/web/assets/{vendors-DMjSfzlv.js → vendors-CKPV1OAU.js} +2 -2
- package/dist/web/assets/{vue-vendor-DET08QYg.js → vue-vendor-3bf-fPGP.js} +1 -1
- package/dist/web/index.html +7 -7
- package/docs/home.png +0 -0
- package/package.json +13 -5
- package/src/commands/daemon.js +3 -2
- package/src/commands/security.js +1 -2
- package/src/config/paths.js +638 -93
- package/src/server/api/agents.js +1 -1
- package/src/server/api/claude-hooks.js +13 -8
- package/src/server/api/codex-proxy.js +5 -4
- package/src/server/api/hooks.js +45 -0
- package/src/server/api/plugins.js +0 -1
- package/src/server/api/statistics.js +4 -4
- package/src/server/api/ui-config.js +5 -0
- package/src/server/api/workspaces.js +1 -3
- package/src/server/codex-proxy-server.js +89 -59
- package/src/server/gemini-proxy-server.js +107 -88
- package/src/server/index.js +1 -0
- package/src/server/opencode-proxy-server.js +381 -225
- package/src/server/proxy-server.js +86 -60
- package/src/server/services/alias.js +3 -3
- package/src/server/services/channels.js +3 -2
- package/src/server/services/codex-channels.js +38 -87
- package/src/server/services/codex-env-manager.js +426 -0
- package/src/server/services/codex-settings-manager.js +15 -15
- package/src/server/services/codex-statistics-service.js +3 -27
- package/src/server/services/config-export-service.js +20 -7
- package/src/server/services/config-registry-service.js +3 -2
- package/src/server/services/config-sync-manager.js +1 -1
- package/src/server/services/favorites.js +4 -3
- package/src/server/services/gemini-channels.js +3 -3
- package/src/server/services/gemini-statistics-service.js +3 -25
- package/src/server/services/mcp-service.js +2 -3
- package/src/server/services/model-detector.js +4 -3
- package/src/server/services/native-oauth-adapters.js +2 -1
- package/src/server/services/network-access.js +39 -1
- package/src/server/services/notification-hooks.js +951 -0
- package/src/server/services/opencode-channels.js +6 -6
- package/src/server/services/opencode-sessions.js +2 -2
- package/src/server/services/opencode-statistics-service.js +3 -27
- package/src/server/services/plugins-service.js +110 -31
- package/src/server/services/prompts-service.js +2 -3
- package/src/server/services/proxy-log-helper.js +242 -0
- package/src/server/services/proxy-runtime.js +6 -4
- package/src/server/services/repo-scanner-base.js +12 -4
- package/src/server/services/request-logger.js +7 -7
- package/src/server/services/security-config.js +4 -4
- package/src/server/services/session-cache.js +2 -2
- package/src/server/services/sessions.js +2 -2
- package/src/server/services/skill-service.js +174 -55
- package/src/server/services/statistics-service.js +10 -6
- package/src/server/services/ui-config.js +4 -3
- package/src/server/services/workspace-service.js +101 -156
- package/src/server/websocket-server.js +5 -4
- package/dist/web/assets/Analytics-DuYvId7u.css +0 -1
- package/dist/web/assets/Home-BMoFdAwy.css +0 -1
- package/dist/web/assets/Home-DNwp-0J-.js +0 -1
- package/dist/web/assets/index-BXeSvAwU.js +0 -2
- package/dist/web/assets/index-DWAC3Tdv.css +0 -1
- package/docs/bannel.png +0 -0
- package/docs/model-redirection.md +0 -251
|
@@ -14,13 +14,13 @@ const { recordSuccess, recordFailure } = require('./services/channel-health');
|
|
|
14
14
|
const { loadConfig } = require('../config/loader');
|
|
15
15
|
const DEFAULT_CONFIG = require('../config/default');
|
|
16
16
|
const { PATHS, ensureStorageDirMigrated } = require('../config/paths');
|
|
17
|
-
const {
|
|
17
|
+
const { resolveModelPricing } = require('./utils/pricing');
|
|
18
18
|
const { recordRequest: recordOpenCodeRequest } = require('./services/opencode-statistics-service');
|
|
19
19
|
const { saveProxyStartTime, clearProxyStartTime, getProxyStartTime, getProxyRuntime } = require('./services/proxy-runtime');
|
|
20
20
|
const { getEnabledChannels, getEffectiveApiKey } = require('./services/opencode-channels');
|
|
21
21
|
const { persistProxyRequestSnapshot, loadClaudeRequestTemplate } = require('./services/request-logger');
|
|
22
22
|
const { probeModelAvailability, fetchModelsFromProvider } = require('./services/model-detector');
|
|
23
|
-
const {
|
|
23
|
+
const { publishUsageLog, publishFailureLog } = require('./services/proxy-log-helper');
|
|
24
24
|
|
|
25
25
|
let proxyServer = null;
|
|
26
26
|
let proxyApp = null;
|
|
@@ -190,61 +190,33 @@ function resolveOpenCodeTarget(baseUrl = '', requestPath = '') {
|
|
|
190
190
|
* 计算请求成本
|
|
191
191
|
*/
|
|
192
192
|
function calculateCost(model, tokens) {
|
|
193
|
-
let
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// 非 Claude 模型,使用 PRICING 对象(OpenAI 等)
|
|
217
|
-
pricing = PRICING[model];
|
|
218
|
-
|
|
219
|
-
// 如果没有精确匹配,尝试模糊匹配
|
|
220
|
-
if (!pricing) {
|
|
221
|
-
const modelLower = model.toLowerCase();
|
|
222
|
-
if (modelLower.includes('gpt-4o-mini')) {
|
|
223
|
-
pricing = PRICING['gpt-4o-mini'];
|
|
224
|
-
} else if (modelLower.includes('gpt-4o')) {
|
|
225
|
-
pricing = PRICING['gpt-4o'];
|
|
226
|
-
} else if (modelLower.includes('gpt-4')) {
|
|
227
|
-
pricing = PRICING['gpt-4'];
|
|
228
|
-
} else if (modelLower.includes('gpt-3.5')) {
|
|
229
|
-
pricing = PRICING['gpt-3.5-turbo'];
|
|
230
|
-
} else if (modelLower.includes('o1-mini')) {
|
|
231
|
-
pricing = PRICING['o1-mini'];
|
|
232
|
-
} else if (modelLower.includes('o1-pro')) {
|
|
233
|
-
pricing = PRICING['o1-pro'];
|
|
234
|
-
} else if (modelLower.includes('o1')) {
|
|
235
|
-
pricing = PRICING['o1'];
|
|
236
|
-
} else if (modelLower.includes('o3-mini')) {
|
|
237
|
-
pricing = PRICING['o3-mini'];
|
|
238
|
-
} else if (modelLower.includes('o3')) {
|
|
239
|
-
pricing = PRICING['o3'];
|
|
240
|
-
} else if (modelLower.includes('o4-mini')) {
|
|
241
|
-
pricing = PRICING['o4-mini'];
|
|
242
|
-
}
|
|
193
|
+
let fallbackPricing = PRICING[model];
|
|
194
|
+
if (!fallbackPricing) {
|
|
195
|
+
const modelLower = String(model || '').toLowerCase();
|
|
196
|
+
if (modelLower.includes('gpt-4o-mini')) {
|
|
197
|
+
fallbackPricing = PRICING['gpt-4o-mini'];
|
|
198
|
+
} else if (modelLower.includes('gpt-4o')) {
|
|
199
|
+
fallbackPricing = PRICING['gpt-4o'];
|
|
200
|
+
} else if (modelLower.includes('gpt-4')) {
|
|
201
|
+
fallbackPricing = PRICING['gpt-4'];
|
|
202
|
+
} else if (modelLower.includes('gpt-3.5')) {
|
|
203
|
+
fallbackPricing = PRICING['gpt-3.5-turbo'];
|
|
204
|
+
} else if (modelLower.includes('o1-mini')) {
|
|
205
|
+
fallbackPricing = PRICING['o1-mini'];
|
|
206
|
+
} else if (modelLower.includes('o1-pro')) {
|
|
207
|
+
fallbackPricing = PRICING['o1-pro'];
|
|
208
|
+
} else if (modelLower.includes('o1')) {
|
|
209
|
+
fallbackPricing = PRICING['o1'];
|
|
210
|
+
} else if (modelLower.includes('o3-mini')) {
|
|
211
|
+
fallbackPricing = PRICING['o3-mini'];
|
|
212
|
+
} else if (modelLower.includes('o3')) {
|
|
213
|
+
fallbackPricing = PRICING['o3'];
|
|
214
|
+
} else if (modelLower.includes('o4-mini')) {
|
|
215
|
+
fallbackPricing = PRICING['o4-mini'];
|
|
243
216
|
}
|
|
244
217
|
}
|
|
245
218
|
|
|
246
|
-
|
|
247
|
-
pricing = resolvePricing('opencode', pricing, OPENCODE_BASE_PRICING);
|
|
219
|
+
const pricing = resolveModelPricing('opencode', model, fallbackPricing, OPENCODE_BASE_PRICING);
|
|
248
220
|
const inputRate = typeof pricing.input === 'number' ? pricing.input : OPENCODE_BASE_PRICING.input;
|
|
249
221
|
const outputRate = typeof pricing.output === 'number' ? pricing.output : OPENCODE_BASE_PRICING.output;
|
|
250
222
|
|
|
@@ -1998,59 +1970,63 @@ function sendOpenAiStyleError(res, statusCode, message, type = 'invalid_request_
|
|
|
1998
1970
|
});
|
|
1999
1971
|
}
|
|
2000
1972
|
|
|
2001
|
-
function
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
const tokens = {
|
|
2016
|
-
input: inputTokens,
|
|
2017
|
-
output: outputTokens,
|
|
2018
|
-
total: totalTokens
|
|
2019
|
-
};
|
|
2020
|
-
const cost = calculateCost(model || '', tokens);
|
|
1973
|
+
function reportOpenCodeGatewayFailure({
|
|
1974
|
+
req,
|
|
1975
|
+
res,
|
|
1976
|
+
channel,
|
|
1977
|
+
statusCode,
|
|
1978
|
+
message,
|
|
1979
|
+
type = 'invalid_request_error',
|
|
1980
|
+
error = null,
|
|
1981
|
+
stage = 'gateway',
|
|
1982
|
+
model = ''
|
|
1983
|
+
}) {
|
|
1984
|
+
if (channel?.id && error) {
|
|
1985
|
+
recordFailure(channel.id, 'opencode', error);
|
|
1986
|
+
}
|
|
2021
1987
|
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
1988
|
+
publishFailureLog({
|
|
1989
|
+
source: 'opencode',
|
|
1990
|
+
metadata: (req && requestMetadata.get(req)) || {
|
|
1991
|
+
channel: channel?.name,
|
|
1992
|
+
channelId: channel?.id,
|
|
1993
|
+
model: model || req?.body?.model
|
|
1994
|
+
},
|
|
1995
|
+
channel: channel?.name,
|
|
1996
|
+
model: model || req?.body?.model || '',
|
|
1997
|
+
message,
|
|
1998
|
+
error,
|
|
1999
|
+
statusCode,
|
|
2000
|
+
stage,
|
|
2001
|
+
broadcastLog
|
|
2035
2002
|
});
|
|
2036
2003
|
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2004
|
+
if (!res.headersSent) {
|
|
2005
|
+
sendOpenAiStyleError(res, statusCode, message, type);
|
|
2006
|
+
}
|
|
2007
|
+
return true;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
function publishOpenCodeUsageLog({ requestId, channel, model, usage, startTime }) {
|
|
2011
|
+
return publishUsageLog({
|
|
2012
|
+
source: 'opencode',
|
|
2013
|
+
metadata: {
|
|
2014
|
+
id: requestId,
|
|
2015
|
+
channel: channel?.name,
|
|
2016
|
+
channelId: channel?.id,
|
|
2017
|
+
startTime
|
|
2018
|
+
},
|
|
2043
2019
|
model: model || '',
|
|
2044
2020
|
tokens: {
|
|
2045
|
-
input:
|
|
2046
|
-
output:
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
total:
|
|
2021
|
+
input: Number(usage?.input_tokens || usage?.prompt_tokens || 0),
|
|
2022
|
+
output: Number(usage?.output_tokens || usage?.completion_tokens || 0),
|
|
2023
|
+
cached: Number(usage?.input_tokens_details?.cached_tokens || 0),
|
|
2024
|
+
reasoning: Number(usage?.output_tokens_details?.reasoning_tokens || 0),
|
|
2025
|
+
total: Number(usage?.total_tokens || 0)
|
|
2050
2026
|
},
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2027
|
+
calculateCost,
|
|
2028
|
+
broadcastLog,
|
|
2029
|
+
recordRequest: recordOpenCodeRequest
|
|
2054
2030
|
});
|
|
2055
2031
|
}
|
|
2056
2032
|
|
|
@@ -3001,8 +2977,14 @@ async function handleClaudeGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3001
2977
|
}
|
|
3002
2978
|
|
|
3003
2979
|
if (!shouldParseJson(req)) {
|
|
3004
|
-
|
|
3005
|
-
|
|
2980
|
+
return reportOpenCodeGatewayFailure({
|
|
2981
|
+
req,
|
|
2982
|
+
res,
|
|
2983
|
+
channel,
|
|
2984
|
+
statusCode: 400,
|
|
2985
|
+
message: 'Claude gateway only supports JSON POST payload',
|
|
2986
|
+
stage: 'validate_request'
|
|
2987
|
+
});
|
|
3006
2988
|
}
|
|
3007
2989
|
|
|
3008
2990
|
const requestId = `opencode-${Date.now()}-${Math.random()}`;
|
|
@@ -3035,9 +3017,16 @@ async function handleClaudeGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3035
3017
|
try {
|
|
3036
3018
|
streamUpstream = await postJsonStream(buildClaudeTargetUrl(channel.baseUrl), headers, claudePayload, 120000);
|
|
3037
3019
|
} catch (error) {
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3020
|
+
return reportOpenCodeGatewayFailure({
|
|
3021
|
+
req,
|
|
3022
|
+
res,
|
|
3023
|
+
channel,
|
|
3024
|
+
statusCode: 502,
|
|
3025
|
+
message: `Claude gateway network error: ${error.message}`,
|
|
3026
|
+
type: 'proxy_error',
|
|
3027
|
+
error,
|
|
3028
|
+
stage: 'claude_gateway_network'
|
|
3029
|
+
});
|
|
3041
3030
|
}
|
|
3042
3031
|
|
|
3043
3032
|
const statusCode = Number(streamUpstream.statusCode) || 500;
|
|
@@ -3056,9 +3045,16 @@ async function handleClaudeGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3056
3045
|
parsedError = null;
|
|
3057
3046
|
}
|
|
3058
3047
|
const upstreamMessage = parsedError?.error?.message || parsedError?.message || rawBody || `HTTP ${statusCode}`;
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3048
|
+
return reportOpenCodeGatewayFailure({
|
|
3049
|
+
req,
|
|
3050
|
+
res,
|
|
3051
|
+
channel,
|
|
3052
|
+
statusCode,
|
|
3053
|
+
message: String(upstreamMessage).slice(0, 1000),
|
|
3054
|
+
type: 'upstream_error',
|
|
3055
|
+
error: new Error(String(upstreamMessage).slice(0, 200)),
|
|
3056
|
+
stage: 'claude_gateway_upstream'
|
|
3057
|
+
});
|
|
3062
3058
|
}
|
|
3063
3059
|
|
|
3064
3060
|
try {
|
|
@@ -3072,10 +3068,16 @@ async function handleClaudeGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3072
3068
|
});
|
|
3073
3069
|
recordSuccess(channel.id, 'opencode');
|
|
3074
3070
|
} catch (error) {
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3071
|
+
reportOpenCodeGatewayFailure({
|
|
3072
|
+
req,
|
|
3073
|
+
res,
|
|
3074
|
+
channel,
|
|
3075
|
+
statusCode: 502,
|
|
3076
|
+
message: `Claude stream relay error: ${error.message}`,
|
|
3077
|
+
type: 'proxy_error',
|
|
3078
|
+
error,
|
|
3079
|
+
stage: 'claude_stream_relay'
|
|
3080
|
+
});
|
|
3079
3081
|
}
|
|
3080
3082
|
return true;
|
|
3081
3083
|
}
|
|
@@ -3084,9 +3086,16 @@ async function handleClaudeGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3084
3086
|
try {
|
|
3085
3087
|
upstream = await postJson(buildClaudeTargetUrl(channel.baseUrl), headers, claudePayload, 120000);
|
|
3086
3088
|
} catch (error) {
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3089
|
+
return reportOpenCodeGatewayFailure({
|
|
3090
|
+
req,
|
|
3091
|
+
res,
|
|
3092
|
+
channel,
|
|
3093
|
+
statusCode: 502,
|
|
3094
|
+
message: `Claude gateway network error: ${error.message}`,
|
|
3095
|
+
type: 'proxy_error',
|
|
3096
|
+
error,
|
|
3097
|
+
stage: 'claude_gateway_network'
|
|
3098
|
+
});
|
|
3090
3099
|
}
|
|
3091
3100
|
|
|
3092
3101
|
const statusCode = Number(upstream.statusCode) || 500;
|
|
@@ -3099,15 +3108,29 @@ async function handleClaudeGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3099
3108
|
|
|
3100
3109
|
if (statusCode < 200 || statusCode >= 300) {
|
|
3101
3110
|
const upstreamMessage = parsedBody?.error?.message || parsedBody?.message || upstream.rawBody || `HTTP ${statusCode}`;
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3111
|
+
return reportOpenCodeGatewayFailure({
|
|
3112
|
+
req,
|
|
3113
|
+
res,
|
|
3114
|
+
channel,
|
|
3115
|
+
statusCode,
|
|
3116
|
+
message: String(upstreamMessage).slice(0, 1000),
|
|
3117
|
+
type: 'upstream_error',
|
|
3118
|
+
error: new Error(String(upstreamMessage).slice(0, 200)),
|
|
3119
|
+
stage: 'claude_gateway_upstream'
|
|
3120
|
+
});
|
|
3105
3121
|
}
|
|
3106
3122
|
|
|
3107
3123
|
if (!parsedBody || typeof parsedBody !== 'object') {
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3124
|
+
return reportOpenCodeGatewayFailure({
|
|
3125
|
+
req,
|
|
3126
|
+
res,
|
|
3127
|
+
channel,
|
|
3128
|
+
statusCode: 502,
|
|
3129
|
+
message: 'Invalid Claude gateway response',
|
|
3130
|
+
type: 'proxy_error',
|
|
3131
|
+
error: new Error('Invalid Claude gateway response'),
|
|
3132
|
+
stage: 'claude_gateway_parse'
|
|
3133
|
+
});
|
|
3111
3134
|
}
|
|
3112
3135
|
|
|
3113
3136
|
if (isResponsesPath(pathname)) {
|
|
@@ -3152,8 +3175,14 @@ async function handleCodexGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3152
3175
|
}
|
|
3153
3176
|
|
|
3154
3177
|
if (!shouldParseJson(req)) {
|
|
3155
|
-
|
|
3156
|
-
|
|
3178
|
+
return reportOpenCodeGatewayFailure({
|
|
3179
|
+
req,
|
|
3180
|
+
res,
|
|
3181
|
+
channel,
|
|
3182
|
+
statusCode: 400,
|
|
3183
|
+
message: 'Codex gateway only supports JSON POST payload',
|
|
3184
|
+
stage: 'validate_request'
|
|
3185
|
+
});
|
|
3157
3186
|
}
|
|
3158
3187
|
|
|
3159
3188
|
const requestId = `opencode-${Date.now()}-${Math.random()}`;
|
|
@@ -3164,14 +3193,26 @@ async function handleCodexGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3164
3193
|
const targetModel = converted.model;
|
|
3165
3194
|
|
|
3166
3195
|
if (!targetModel) {
|
|
3167
|
-
|
|
3168
|
-
|
|
3196
|
+
return reportOpenCodeGatewayFailure({
|
|
3197
|
+
req,
|
|
3198
|
+
res,
|
|
3199
|
+
channel,
|
|
3200
|
+
statusCode: 400,
|
|
3201
|
+
message: 'Missing model in request and channel configuration',
|
|
3202
|
+
stage: 'resolve_model'
|
|
3203
|
+
});
|
|
3169
3204
|
}
|
|
3170
3205
|
|
|
3171
3206
|
const targetUrl = buildCodexTargetUrl(channel.baseUrl);
|
|
3172
3207
|
if (!targetUrl) {
|
|
3173
|
-
|
|
3174
|
-
|
|
3208
|
+
return reportOpenCodeGatewayFailure({
|
|
3209
|
+
req,
|
|
3210
|
+
res,
|
|
3211
|
+
channel,
|
|
3212
|
+
statusCode: 400,
|
|
3213
|
+
message: 'Failed to build Codex target URL',
|
|
3214
|
+
stage: 'build_target_url'
|
|
3215
|
+
});
|
|
3175
3216
|
}
|
|
3176
3217
|
|
|
3177
3218
|
const codexSessionId = `${Date.now()}-${Math.random().toString(36).slice(2, 15)}`;
|
|
@@ -3198,9 +3239,16 @@ async function handleCodexGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3198
3239
|
try {
|
|
3199
3240
|
streamUpstream = await postJsonStream(targetUrl, headers, converted.requestBody, 120000);
|
|
3200
3241
|
} catch (error) {
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3242
|
+
return reportOpenCodeGatewayFailure({
|
|
3243
|
+
req,
|
|
3244
|
+
res,
|
|
3245
|
+
channel,
|
|
3246
|
+
statusCode: 502,
|
|
3247
|
+
message: `Codex gateway network error: ${error.message}`,
|
|
3248
|
+
type: 'proxy_error',
|
|
3249
|
+
error,
|
|
3250
|
+
stage: 'codex_gateway_network'
|
|
3251
|
+
});
|
|
3204
3252
|
}
|
|
3205
3253
|
|
|
3206
3254
|
const statusCode = Number(streamUpstream.statusCode) || 500;
|
|
@@ -3220,9 +3268,16 @@ async function handleCodexGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3220
3268
|
}
|
|
3221
3269
|
|
|
3222
3270
|
const upstreamMessage = parsedError?.error?.message || parsedError?.message || rawBody || `HTTP ${statusCode}`;
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3271
|
+
return reportOpenCodeGatewayFailure({
|
|
3272
|
+
req,
|
|
3273
|
+
res,
|
|
3274
|
+
channel,
|
|
3275
|
+
statusCode,
|
|
3276
|
+
message: String(upstreamMessage).slice(0, 1000),
|
|
3277
|
+
type: 'upstream_error',
|
|
3278
|
+
error: new Error(String(upstreamMessage).slice(0, 200)),
|
|
3279
|
+
stage: 'codex_gateway_upstream'
|
|
3280
|
+
});
|
|
3226
3281
|
}
|
|
3227
3282
|
|
|
3228
3283
|
try {
|
|
@@ -3241,9 +3296,16 @@ async function handleCodexGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3241
3296
|
|
|
3242
3297
|
const responseObject = await collectCodexResponsesNonStream(streamUpstream.response, originalPayload);
|
|
3243
3298
|
if (!responseObject || typeof responseObject !== 'object') {
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3299
|
+
return reportOpenCodeGatewayFailure({
|
|
3300
|
+
req,
|
|
3301
|
+
res,
|
|
3302
|
+
channel,
|
|
3303
|
+
statusCode: 502,
|
|
3304
|
+
message: 'Invalid Codex gateway response',
|
|
3305
|
+
type: 'proxy_error',
|
|
3306
|
+
error: new Error('Invalid Codex gateway response'),
|
|
3307
|
+
stage: 'codex_gateway_parse'
|
|
3308
|
+
});
|
|
3247
3309
|
}
|
|
3248
3310
|
res.json(responseObject);
|
|
3249
3311
|
publishOpenCodeUsageLog({
|
|
@@ -3256,10 +3318,16 @@ async function handleCodexGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3256
3318
|
recordSuccess(channel.id, 'opencode');
|
|
3257
3319
|
return true;
|
|
3258
3320
|
} catch (error) {
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3321
|
+
reportOpenCodeGatewayFailure({
|
|
3322
|
+
req,
|
|
3323
|
+
res,
|
|
3324
|
+
channel,
|
|
3325
|
+
statusCode: 502,
|
|
3326
|
+
message: `Codex stream relay error: ${error.message}`,
|
|
3327
|
+
type: 'proxy_error',
|
|
3328
|
+
error,
|
|
3329
|
+
stage: 'codex_stream_relay'
|
|
3330
|
+
});
|
|
3263
3331
|
return true;
|
|
3264
3332
|
}
|
|
3265
3333
|
}
|
|
@@ -3925,8 +3993,14 @@ async function handleGeminiGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3925
3993
|
}
|
|
3926
3994
|
|
|
3927
3995
|
if (!shouldParseJson(req)) {
|
|
3928
|
-
|
|
3929
|
-
|
|
3996
|
+
return reportOpenCodeGatewayFailure({
|
|
3997
|
+
req,
|
|
3998
|
+
res,
|
|
3999
|
+
channel,
|
|
4000
|
+
statusCode: 400,
|
|
4001
|
+
message: 'Gemini gateway only supports JSON POST payload',
|
|
4002
|
+
stage: 'validate_request'
|
|
4003
|
+
});
|
|
3930
4004
|
}
|
|
3931
4005
|
|
|
3932
4006
|
const requestId = `opencode-${Date.now()}-${Math.random()}`;
|
|
@@ -3939,8 +4013,14 @@ async function handleGeminiGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3939
4013
|
const useGeminiCli = shouldUseGeminiCliFormat(channel.baseUrl);
|
|
3940
4014
|
|
|
3941
4015
|
if (!targetModel) {
|
|
3942
|
-
|
|
3943
|
-
|
|
4016
|
+
return reportOpenCodeGatewayFailure({
|
|
4017
|
+
req,
|
|
4018
|
+
res,
|
|
4019
|
+
channel,
|
|
4020
|
+
statusCode: 400,
|
|
4021
|
+
message: 'Missing model in request and channel configuration',
|
|
4022
|
+
stage: 'resolve_model'
|
|
4023
|
+
});
|
|
3944
4024
|
}
|
|
3945
4025
|
|
|
3946
4026
|
const targetUrl = buildGeminiTargetUrl(channel.baseUrl, targetModel, effectiveKey, {
|
|
@@ -3948,8 +4028,14 @@ async function handleGeminiGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3948
4028
|
useCli: useGeminiCli
|
|
3949
4029
|
});
|
|
3950
4030
|
if (!targetUrl) {
|
|
3951
|
-
|
|
3952
|
-
|
|
4031
|
+
return reportOpenCodeGatewayFailure({
|
|
4032
|
+
req,
|
|
4033
|
+
res,
|
|
4034
|
+
channel,
|
|
4035
|
+
statusCode: 400,
|
|
4036
|
+
message: 'Failed to build Gemini target URL',
|
|
4037
|
+
stage: 'build_target_url'
|
|
4038
|
+
});
|
|
3953
4039
|
}
|
|
3954
4040
|
|
|
3955
4041
|
const geminiPayload = useGeminiCli
|
|
@@ -3985,9 +4071,16 @@ async function handleGeminiGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
3985
4071
|
try {
|
|
3986
4072
|
streamUpstream = await postJsonStream(targetUrl, headers, geminiPayload, 120000);
|
|
3987
4073
|
} catch (error) {
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
4074
|
+
return reportOpenCodeGatewayFailure({
|
|
4075
|
+
req,
|
|
4076
|
+
res,
|
|
4077
|
+
channel,
|
|
4078
|
+
statusCode: 502,
|
|
4079
|
+
message: `Gemini gateway network error: ${error.message}`,
|
|
4080
|
+
type: 'proxy_error',
|
|
4081
|
+
error,
|
|
4082
|
+
stage: 'gemini_gateway_network'
|
|
4083
|
+
});
|
|
3991
4084
|
}
|
|
3992
4085
|
|
|
3993
4086
|
const statusCode = Number(streamUpstream.statusCode) || 500;
|
|
@@ -4006,9 +4099,16 @@ async function handleGeminiGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
4006
4099
|
parsedError = null;
|
|
4007
4100
|
}
|
|
4008
4101
|
const upstreamMessage = parsedError?.error?.message || parsedError?.message || rawBody || `HTTP ${statusCode}`;
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4102
|
+
return reportOpenCodeGatewayFailure({
|
|
4103
|
+
req,
|
|
4104
|
+
res,
|
|
4105
|
+
channel,
|
|
4106
|
+
statusCode,
|
|
4107
|
+
message: String(upstreamMessage).slice(0, 1000),
|
|
4108
|
+
type: 'upstream_error',
|
|
4109
|
+
error: new Error(String(upstreamMessage).slice(0, 200)),
|
|
4110
|
+
stage: 'gemini_gateway_upstream'
|
|
4111
|
+
});
|
|
4012
4112
|
}
|
|
4013
4113
|
|
|
4014
4114
|
try {
|
|
@@ -4022,10 +4122,16 @@ async function handleGeminiGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
4022
4122
|
});
|
|
4023
4123
|
recordSuccess(channel.id, 'opencode');
|
|
4024
4124
|
} catch (error) {
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4125
|
+
reportOpenCodeGatewayFailure({
|
|
4126
|
+
req,
|
|
4127
|
+
res,
|
|
4128
|
+
channel,
|
|
4129
|
+
statusCode: 502,
|
|
4130
|
+
message: `Gemini stream relay error: ${error.message}`,
|
|
4131
|
+
type: 'proxy_error',
|
|
4132
|
+
error,
|
|
4133
|
+
stage: 'gemini_stream_relay'
|
|
4134
|
+
});
|
|
4029
4135
|
}
|
|
4030
4136
|
return true;
|
|
4031
4137
|
}
|
|
@@ -4034,9 +4140,16 @@ async function handleGeminiGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
4034
4140
|
try {
|
|
4035
4141
|
upstream = await postJson(targetUrl, headers, geminiPayload, 120000);
|
|
4036
4142
|
} catch (error) {
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4143
|
+
return reportOpenCodeGatewayFailure({
|
|
4144
|
+
req,
|
|
4145
|
+
res,
|
|
4146
|
+
channel,
|
|
4147
|
+
statusCode: 502,
|
|
4148
|
+
message: `Gemini gateway network error: ${error.message}`,
|
|
4149
|
+
type: 'proxy_error',
|
|
4150
|
+
error,
|
|
4151
|
+
stage: 'gemini_gateway_network'
|
|
4152
|
+
});
|
|
4040
4153
|
}
|
|
4041
4154
|
|
|
4042
4155
|
const statusCode = Number(upstream.statusCode) || 500;
|
|
@@ -4049,15 +4162,29 @@ async function handleGeminiGatewayRequest(req, res, channel, effectiveKey) {
|
|
|
4049
4162
|
|
|
4050
4163
|
if (statusCode < 200 || statusCode >= 300) {
|
|
4051
4164
|
const upstreamMessage = parsedBody?.error?.message || parsedBody?.message || upstream.rawBody || `HTTP ${statusCode}`;
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4165
|
+
return reportOpenCodeGatewayFailure({
|
|
4166
|
+
req,
|
|
4167
|
+
res,
|
|
4168
|
+
channel,
|
|
4169
|
+
statusCode,
|
|
4170
|
+
message: String(upstreamMessage).slice(0, 1000),
|
|
4171
|
+
type: 'upstream_error',
|
|
4172
|
+
error: new Error(String(upstreamMessage).slice(0, 200)),
|
|
4173
|
+
stage: 'gemini_gateway_upstream'
|
|
4174
|
+
});
|
|
4055
4175
|
}
|
|
4056
4176
|
|
|
4057
4177
|
if (!parsedBody || typeof parsedBody !== 'object') {
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4178
|
+
return reportOpenCodeGatewayFailure({
|
|
4179
|
+
req,
|
|
4180
|
+
res,
|
|
4181
|
+
channel,
|
|
4182
|
+
statusCode: 502,
|
|
4183
|
+
message: 'Invalid Gemini gateway response',
|
|
4184
|
+
type: 'proxy_error',
|
|
4185
|
+
error: new Error('Invalid Gemini gateway response'),
|
|
4186
|
+
stage: 'gemini_gateway_parse'
|
|
4187
|
+
});
|
|
4061
4188
|
}
|
|
4062
4189
|
|
|
4063
4190
|
if (isResponsesPath(pathname)) {
|
|
@@ -4241,6 +4368,14 @@ async function startOpenCodeProxyServer(options = {}) {
|
|
|
4241
4368
|
if (!effectiveKey) {
|
|
4242
4369
|
releaseChannel(channel.id, 'opencode');
|
|
4243
4370
|
broadcastSchedulerState('opencode', getSchedulerState('opencode'));
|
|
4371
|
+
publishFailureLog({
|
|
4372
|
+
source: 'opencode',
|
|
4373
|
+
channel: channel.name,
|
|
4374
|
+
message: 'API key not configured or expired. Please update your channel key.',
|
|
4375
|
+
statusCode: 401,
|
|
4376
|
+
stage: 'preflight',
|
|
4377
|
+
broadcastLog
|
|
4378
|
+
});
|
|
4244
4379
|
return res.status(401).json({
|
|
4245
4380
|
error: {
|
|
4246
4381
|
message: 'API key not configured or expired. Please update your channel key.',
|
|
@@ -4332,6 +4467,20 @@ async function startOpenCodeProxyServer(options = {}) {
|
|
|
4332
4467
|
release();
|
|
4333
4468
|
if (err) {
|
|
4334
4469
|
recordFailure(channel.id, 'opencode', err);
|
|
4470
|
+
const metadata = requestMetadata.get(req) || {
|
|
4471
|
+
channel: channel.name,
|
|
4472
|
+
channelId: channel.id,
|
|
4473
|
+
startTime: Date.now()
|
|
4474
|
+
};
|
|
4475
|
+
publishFailureLog({
|
|
4476
|
+
source: 'opencode',
|
|
4477
|
+
metadata,
|
|
4478
|
+
message: err.message,
|
|
4479
|
+
error: err,
|
|
4480
|
+
statusCode: 502,
|
|
4481
|
+
stage: 'proxy_web',
|
|
4482
|
+
broadcastLog
|
|
4483
|
+
});
|
|
4335
4484
|
console.error('OpenCode proxy error:', err);
|
|
4336
4485
|
if (res && !res.headersSent) {
|
|
4337
4486
|
res.status(502).json({
|
|
@@ -4345,6 +4494,13 @@ async function startOpenCodeProxyServer(options = {}) {
|
|
|
4345
4494
|
});
|
|
4346
4495
|
} catch (error) {
|
|
4347
4496
|
console.error('OpenCode channel allocation error:', error);
|
|
4497
|
+
publishFailureLog({
|
|
4498
|
+
source: 'opencode',
|
|
4499
|
+
message: error.message || 'No OpenCode channel available',
|
|
4500
|
+
statusCode: 503,
|
|
4501
|
+
stage: 'allocate_channel',
|
|
4502
|
+
broadcastLog
|
|
4503
|
+
});
|
|
4348
4504
|
if (!res.headersSent) {
|
|
4349
4505
|
res.status(503).json({
|
|
4350
4506
|
error: {
|
|
@@ -4397,6 +4553,38 @@ async function startOpenCodeProxyServer(options = {}) {
|
|
|
4397
4553
|
totalTokens: 0,
|
|
4398
4554
|
model: ''
|
|
4399
4555
|
};
|
|
4556
|
+
let usageRecorded = false;
|
|
4557
|
+
|
|
4558
|
+
function recordUsageIfReady() {
|
|
4559
|
+
if (usageRecorded) {
|
|
4560
|
+
return false;
|
|
4561
|
+
}
|
|
4562
|
+
|
|
4563
|
+
const result = publishUsageLog({
|
|
4564
|
+
source: 'opencode',
|
|
4565
|
+
metadata,
|
|
4566
|
+
model: tokenData.model,
|
|
4567
|
+
tokens: {
|
|
4568
|
+
input: tokenData.inputTokens,
|
|
4569
|
+
output: tokenData.outputTokens,
|
|
4570
|
+
cached: tokenData.cachedTokens,
|
|
4571
|
+
reasoning: tokenData.reasoningTokens,
|
|
4572
|
+
total: tokenData.totalTokens
|
|
4573
|
+
},
|
|
4574
|
+
calculateCost,
|
|
4575
|
+
broadcastLog,
|
|
4576
|
+
recordRequest: recordOpenCodeRequest,
|
|
4577
|
+
recordSuccess,
|
|
4578
|
+
allowBroadcast: !isResponseClosed
|
|
4579
|
+
});
|
|
4580
|
+
|
|
4581
|
+
if (!result) {
|
|
4582
|
+
return false;
|
|
4583
|
+
}
|
|
4584
|
+
|
|
4585
|
+
usageRecorded = true;
|
|
4586
|
+
return true;
|
|
4587
|
+
}
|
|
4400
4588
|
|
|
4401
4589
|
proxyRes.on('data', (chunk) => {
|
|
4402
4590
|
// 如果响应已关闭,停止处理
|
|
@@ -4462,7 +4650,10 @@ async function startOpenCodeProxyServer(options = {}) {
|
|
|
4462
4650
|
// 兼容 Responses API 和 Chat Completions API
|
|
4463
4651
|
tokenData.inputTokens = parsed.usage.input_tokens || parsed.usage.prompt_tokens || 0;
|
|
4464
4652
|
tokenData.outputTokens = parsed.usage.output_tokens || parsed.usage.completion_tokens || 0;
|
|
4653
|
+
tokenData.totalTokens = parsed.usage.total_tokens || (tokenData.inputTokens + tokenData.outputTokens);
|
|
4465
4654
|
}
|
|
4655
|
+
|
|
4656
|
+
recordUsageIfReady();
|
|
4466
4657
|
} catch (err) {
|
|
4467
4658
|
// 忽略解析错误
|
|
4468
4659
|
}
|
|
@@ -4482,71 +4673,14 @@ async function startOpenCodeProxyServer(options = {}) {
|
|
|
4482
4673
|
// 兼容两种格式
|
|
4483
4674
|
tokenData.inputTokens = parsed.usage.input_tokens || parsed.usage.prompt_tokens || 0;
|
|
4484
4675
|
tokenData.outputTokens = parsed.usage.output_tokens || parsed.usage.completion_tokens || 0;
|
|
4676
|
+
tokenData.totalTokens = parsed.usage.total_tokens || (tokenData.inputTokens + tokenData.outputTokens);
|
|
4485
4677
|
}
|
|
4486
4678
|
} catch (err) {
|
|
4487
4679
|
// 忽略解析错误
|
|
4488
4680
|
}
|
|
4489
4681
|
}
|
|
4490
4682
|
|
|
4491
|
-
|
|
4492
|
-
if (tokenData.inputTokens > 0 || tokenData.outputTokens > 0) {
|
|
4493
|
-
const now = new Date();
|
|
4494
|
-
const time = now.toLocaleTimeString('zh-CN', {
|
|
4495
|
-
hour12: false,
|
|
4496
|
-
hour: '2-digit',
|
|
4497
|
-
minute: '2-digit',
|
|
4498
|
-
second: '2-digit'
|
|
4499
|
-
});
|
|
4500
|
-
|
|
4501
|
-
// 记录统计数据(先计算)
|
|
4502
|
-
const tokens = {
|
|
4503
|
-
input: tokenData.inputTokens,
|
|
4504
|
-
output: tokenData.outputTokens,
|
|
4505
|
-
total: tokenData.inputTokens + tokenData.outputTokens
|
|
4506
|
-
};
|
|
4507
|
-
const cost = calculateCost(tokenData.model, tokens);
|
|
4508
|
-
|
|
4509
|
-
// 广播日志(仅当响应仍然开放时)
|
|
4510
|
-
if (!isResponseClosed) {
|
|
4511
|
-
broadcastLog({
|
|
4512
|
-
type: 'log',
|
|
4513
|
-
id: metadata.id,
|
|
4514
|
-
time: time,
|
|
4515
|
-
channel: metadata.channel,
|
|
4516
|
-
model: tokenData.model,
|
|
4517
|
-
inputTokens: tokenData.inputTokens,
|
|
4518
|
-
outputTokens: tokenData.outputTokens,
|
|
4519
|
-
cachedTokens: tokenData.cachedTokens,
|
|
4520
|
-
reasoningTokens: tokenData.reasoningTokens,
|
|
4521
|
-
totalTokens: tokenData.totalTokens,
|
|
4522
|
-
cost: cost,
|
|
4523
|
-
source: 'opencode'
|
|
4524
|
-
});
|
|
4525
|
-
}
|
|
4526
|
-
|
|
4527
|
-
const duration = Date.now() - metadata.startTime;
|
|
4528
|
-
|
|
4529
|
-
recordOpenCodeRequest({
|
|
4530
|
-
id: metadata.id,
|
|
4531
|
-
timestamp: new Date(metadata.startTime).toISOString(),
|
|
4532
|
-
toolType: 'opencode',
|
|
4533
|
-
channel: metadata.channel,
|
|
4534
|
-
channelId: metadata.channelId,
|
|
4535
|
-
model: tokenData.model,
|
|
4536
|
-
tokens: {
|
|
4537
|
-
input: tokenData.inputTokens,
|
|
4538
|
-
output: tokenData.outputTokens,
|
|
4539
|
-
reasoning: tokenData.reasoningTokens,
|
|
4540
|
-
cached: tokenData.cachedTokens,
|
|
4541
|
-
total: tokens.total
|
|
4542
|
-
},
|
|
4543
|
-
duration: duration,
|
|
4544
|
-
success: true,
|
|
4545
|
-
cost: cost
|
|
4546
|
-
});
|
|
4547
|
-
|
|
4548
|
-
recordSuccess(metadata.channelId, 'opencode');
|
|
4549
|
-
}
|
|
4683
|
+
recordUsageIfReady();
|
|
4550
4684
|
|
|
4551
4685
|
if (!isResponseClosed) {
|
|
4552
4686
|
requestMetadata.delete(req);
|
|
@@ -4560,6 +4694,15 @@ async function startOpenCodeProxyServer(options = {}) {
|
|
|
4560
4694
|
}
|
|
4561
4695
|
isResponseClosed = true;
|
|
4562
4696
|
recordFailure(metadata.channelId, 'opencode', err);
|
|
4697
|
+
publishFailureLog({
|
|
4698
|
+
source: 'opencode',
|
|
4699
|
+
metadata,
|
|
4700
|
+
message: err.message,
|
|
4701
|
+
error: err,
|
|
4702
|
+
statusCode: proxyRes.statusCode,
|
|
4703
|
+
stage: 'response_stream',
|
|
4704
|
+
broadcastLog
|
|
4705
|
+
});
|
|
4563
4706
|
requestMetadata.delete(req);
|
|
4564
4707
|
});
|
|
4565
4708
|
});
|
|
@@ -4572,6 +4715,19 @@ async function startOpenCodeProxyServer(options = {}) {
|
|
|
4572
4715
|
releaseChannel(req.selectedChannel.id, 'opencode');
|
|
4573
4716
|
broadcastSchedulerState('opencode', getSchedulerState('opencode'));
|
|
4574
4717
|
}
|
|
4718
|
+
publishFailureLog({
|
|
4719
|
+
source: 'opencode',
|
|
4720
|
+
metadata: (req && requestMetadata.get(req)) || {
|
|
4721
|
+
channel: req?.selectedChannel?.name,
|
|
4722
|
+
channelId: req?.selectedChannel?.id,
|
|
4723
|
+
model: req?.body?.model
|
|
4724
|
+
},
|
|
4725
|
+
message: err.message,
|
|
4726
|
+
error: err,
|
|
4727
|
+
statusCode: 502,
|
|
4728
|
+
stage: 'proxy',
|
|
4729
|
+
broadcastLog
|
|
4730
|
+
});
|
|
4575
4731
|
if (res && !res.headersSent) {
|
|
4576
4732
|
res.status(502).json({
|
|
4577
4733
|
error: {
|