coding-tool-x 3.6.7 → 3.6.9
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/web/assets/{Analytics-CKycBI0G.js → Analytics-BuBlsbcI.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-CACLQb8e.js → ConfigTemplates-DQczxuOX.js} +1 -1
- package/dist/web/assets/Home-BvAzzWnB.js +1 -0
- package/dist/web/assets/Home-CIua-b-o.css +1 -0
- package/dist/web/assets/{PluginManager-CQJl_uso.js → PluginManager-C8gAlaps.js} +1 -1
- package/dist/web/assets/{ProjectList-DSkvQFEP.js → ProjectList-nqgi6_cR.js} +1 -1
- package/dist/web/assets/{SessionList-Czjs1GN1.js → SessionList-D8AxMYy6.js} +1 -1
- package/dist/web/assets/{SkillManager-Cg1xZ5Cv.js → SkillManager-BDbNbNkK.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-COjYeKvN.js → WorkspaceManager-DGGaGW2c.js} +1 -1
- package/dist/web/assets/index-Dsh4Q5Cg.css +1 -0
- package/dist/web/assets/index-y-eEayxR.js +2 -0
- package/dist/web/assets/{naive-ui-DM897mIP.js → naive-ui-kcWDeJwl.js} +1 -1
- package/dist/web/index.html +3 -3
- package/package.json +1 -1
- package/src/commands/stats.js +1 -1
- package/src/config/channel-preset-websites.js +2 -1
- package/src/config/paths.js +8 -0
- package/src/server/api/agents.js +16 -54
- package/src/server/api/claude-hooks.js +1 -1
- package/src/server/api/claude-statistics.js +57 -0
- package/src/server/api/commands.js +16 -54
- package/src/server/api/dashboard.js +2 -2
- package/src/server/api/gemini-channels.js +2 -0
- package/src/server/api/hooks.js +4 -1
- package/src/server/api/plugins.js +46 -69
- package/src/server/api/skills.js +21 -77
- package/src/server/api/validation-errors.js +34 -0
- package/src/server/gemini-proxy-server.js +205 -21
- package/src/server/index.js +1 -0
- package/src/server/services/agents-service.js +166 -75
- package/src/server/services/base/base-channel-service.js +15 -0
- package/src/server/services/base/response-usage-parser.js +44 -0
- package/src/server/services/channel-balance.js +86 -1
- package/src/server/services/claude-statistics-service.js +139 -0
- package/src/server/services/commands-service.js +203 -121
- package/src/server/services/config-artifact-paths.js +116 -0
- package/src/server/services/config-export-service.js +7 -6
- package/src/server/services/config-registry-service.js +2 -2
- package/src/server/services/config-sync-manager.js +45 -11
- package/src/server/services/config-templates-service.js +21 -19
- package/src/server/services/format-converter.js +42 -4
- package/src/server/services/gemini-channels.js +30 -1
- package/src/server/services/gemini-sessions.js +347 -61
- package/src/server/services/mcp-client.js +236 -49
- package/src/server/services/mcp-service.js +51 -64
- package/src/server/services/notification-hooks.js +659 -88
- package/src/server/services/opencode-channels.js +15 -0
- package/src/server/services/plugins-service.js +1137 -86
- package/src/server/services/repo-scanner-base.js +6 -29
- package/src/server/services/skill-service.js +198 -65
- package/dist/web/assets/Home-BeGkC4bb.css +0 -1
- package/dist/web/assets/Home-Bk6sP5Ed.js +0 -1
- package/dist/web/assets/index-0isNzXNY.css +0 -1
- package/dist/web/assets/index-fWgvlZQx.js +0 -2
|
@@ -15,7 +15,14 @@ const { getEffectiveApiKey } = require('./services/gemini-channels');
|
|
|
15
15
|
const { persistProxyRequestSnapshot } = require('./services/request-logger');
|
|
16
16
|
const { publishUsageLog, publishFailureLog } = require('./services/proxy-log-helper');
|
|
17
17
|
const { redirectModel: redirectModelBase, resolveTargetUrl } = require('./services/base/proxy-utils');
|
|
18
|
-
const {
|
|
18
|
+
const {
|
|
19
|
+
parseSSEUsage,
|
|
20
|
+
parseNonStreamingUsage,
|
|
21
|
+
splitSSEEvents,
|
|
22
|
+
parseSSEEventText,
|
|
23
|
+
mergeUsageIntoTokenData,
|
|
24
|
+
createTokenData
|
|
25
|
+
} = require('./services/base/response-usage-parser');
|
|
19
26
|
const { attachServerShutdownHandling, expediteServerShutdown } = require('./services/server-shutdown');
|
|
20
27
|
|
|
21
28
|
let proxyServer = null;
|
|
@@ -47,6 +54,12 @@ const PRICING = {
|
|
|
47
54
|
};
|
|
48
55
|
|
|
49
56
|
const GEMINI_BASE_PRICING = DEFAULT_CONFIG.pricing.gemini;
|
|
57
|
+
const jsonBodyParser = express.json({
|
|
58
|
+
limit: '100mb',
|
|
59
|
+
verify: (req, res, buf) => {
|
|
60
|
+
req.rawBody = Buffer.from(buf);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
50
63
|
|
|
51
64
|
// resolveGeminiTarget replaced by resolveTargetUrl from proxy-utils
|
|
52
65
|
const resolveGeminiTarget = resolveTargetUrl;
|
|
@@ -56,6 +69,129 @@ function redirectModel(originalModel, channel) {
|
|
|
56
69
|
return redirectModelBase(originalModel, channel, { useTierFallback: false });
|
|
57
70
|
}
|
|
58
71
|
|
|
72
|
+
function shouldParseJson(req) {
|
|
73
|
+
const contentType = req.headers['content-type'] || '';
|
|
74
|
+
return req.method === 'POST' && contentType.includes('application/json');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isVertexAiV1Channel(channel) {
|
|
78
|
+
return String(channel?.apiFormat || '').trim().toLowerCase() === 'vertex_ai_v1';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function stripVertexFunctionResponseIdsFromParts(parts) {
|
|
82
|
+
if (!Array.isArray(parts)) return false;
|
|
83
|
+
let changed = false;
|
|
84
|
+
|
|
85
|
+
for (const part of parts) {
|
|
86
|
+
if (!part || typeof part !== 'object' || Array.isArray(part)) continue;
|
|
87
|
+
const functionResponse = part.functionResponse || part.function_response;
|
|
88
|
+
if (!functionResponse || typeof functionResponse !== 'object' || Array.isArray(functionResponse)) continue;
|
|
89
|
+
if (Object.prototype.hasOwnProperty.call(functionResponse, 'id')) {
|
|
90
|
+
delete functionResponse.id;
|
|
91
|
+
changed = true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return changed;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function stripVertexFunctionResponseIds(body) {
|
|
99
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) return false;
|
|
100
|
+
let changed = false;
|
|
101
|
+
|
|
102
|
+
if (Array.isArray(body.contents)) {
|
|
103
|
+
for (const content of body.contents) {
|
|
104
|
+
changed = stripVertexFunctionResponseIdsFromParts(content?.parts) || changed;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (Array.isArray(body.cachedContent?.contents)) {
|
|
109
|
+
for (const content of body.cachedContent.contents) {
|
|
110
|
+
changed = stripVertexFunctionResponseIdsFromParts(content?.parts) || changed;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return changed;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function buildVertexAiV1Path(baseUrl, requestPath) {
|
|
118
|
+
let basePath = '';
|
|
119
|
+
try {
|
|
120
|
+
basePath = new URL(baseUrl).pathname.replace(/\/+$/, '');
|
|
121
|
+
} catch (_) {
|
|
122
|
+
basePath = String(baseUrl || '').replace(/\/+$/, '');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!basePath) {
|
|
126
|
+
return requestPath;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const actionMatch = String(requestPath || '').match(/\/models\/([^/:?]+)(:[^?]*)?(\?.*)?$/);
|
|
130
|
+
if (!actionMatch) {
|
|
131
|
+
return requestPath;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const encodedModel = actionMatch[1];
|
|
135
|
+
const suffix = actionMatch[2] || '';
|
|
136
|
+
const query = actionMatch[3] || '';
|
|
137
|
+
if (basePath.includes('/publishers/google')) {
|
|
138
|
+
return `/models/${encodedModel}${suffix}${query}`;
|
|
139
|
+
}
|
|
140
|
+
return `${basePath}/models/${encodedModel}${suffix}${query}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function parseJsonSafely(value) {
|
|
144
|
+
try {
|
|
145
|
+
return JSON.parse(value);
|
|
146
|
+
} catch (_) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function extractGeminiUpstreamErrorMessage(rawBody, statusCode) {
|
|
152
|
+
const body = String(rawBody || '').trim();
|
|
153
|
+
const fallback = `Gemini upstream error: HTTP ${statusCode || 500}`;
|
|
154
|
+
|
|
155
|
+
if (!body) {
|
|
156
|
+
return fallback;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const parsed = parseJsonSafely(body);
|
|
160
|
+
const message = parsed?.error?.message
|
|
161
|
+
|| parsed?.message
|
|
162
|
+
|| (typeof parsed?.error === 'string' ? parsed.error : '')
|
|
163
|
+
|| '';
|
|
164
|
+
|
|
165
|
+
if (message) {
|
|
166
|
+
return `Gemini upstream error (${statusCode || parsed?.error?.code || 500}): ${message}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const sseDataLine = body
|
|
170
|
+
.split(/\r?\n/)
|
|
171
|
+
.map(line => line.trim())
|
|
172
|
+
.find(line => line.startsWith('data:'));
|
|
173
|
+
if (sseDataLine) {
|
|
174
|
+
const eventPayload = sseDataLine.slice(5).trim();
|
|
175
|
+
if (eventPayload && eventPayload !== '[DONE]') {
|
|
176
|
+
const eventJson = parseJsonSafely(eventPayload);
|
|
177
|
+
const eventMessage = eventJson?.error?.message
|
|
178
|
+
|| eventJson?.message
|
|
179
|
+
|| (typeof eventJson?.error === 'string' ? eventJson.error : '')
|
|
180
|
+
|| '';
|
|
181
|
+
if (eventMessage) {
|
|
182
|
+
return `Gemini upstream error (${statusCode || eventJson?.error?.code || 500}): ${eventMessage}`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return `Gemini upstream error (${statusCode || 500}): ${body.slice(0, 500)}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function isHttpErrorStatus(statusCode) {
|
|
191
|
+
const code = Number(statusCode);
|
|
192
|
+
return Number.isFinite(code) && (code < 200 || code >= 300);
|
|
193
|
+
}
|
|
194
|
+
|
|
59
195
|
/**
|
|
60
196
|
* 计算请求成本
|
|
61
197
|
*/
|
|
@@ -109,6 +245,14 @@ async function startGeminiProxyServer(options = {}) {
|
|
|
109
245
|
currentPort = port;
|
|
110
246
|
|
|
111
247
|
proxyApp = express();
|
|
248
|
+
|
|
249
|
+
proxyApp.use((req, res, next) => {
|
|
250
|
+
if (shouldParseJson(req)) {
|
|
251
|
+
return jsonBodyParser(req, res, next);
|
|
252
|
+
}
|
|
253
|
+
return next();
|
|
254
|
+
});
|
|
255
|
+
|
|
112
256
|
const proxy = httpProxy.createProxyServer({});
|
|
113
257
|
|
|
114
258
|
proxy.on('proxyReq', (proxyReq, req) => {
|
|
@@ -138,6 +282,15 @@ async function startGeminiProxyServer(options = {}) {
|
|
|
138
282
|
if (!proxyReq.getHeader('content-type')) {
|
|
139
283
|
proxyReq.setHeader('content-type', 'application/json');
|
|
140
284
|
}
|
|
285
|
+
|
|
286
|
+
if (shouldParseJson(req) && (req.rawBody || req.body)) {
|
|
287
|
+
const bodyBuffer = req.rawBody
|
|
288
|
+
? Buffer.isBuffer(req.rawBody) ? req.rawBody : Buffer.from(req.rawBody)
|
|
289
|
+
: Buffer.from(JSON.stringify(req.body));
|
|
290
|
+
proxyReq.setHeader('Content-Length', bodyBuffer.length);
|
|
291
|
+
proxyReq.write(bodyBuffer);
|
|
292
|
+
proxyReq.end();
|
|
293
|
+
}
|
|
141
294
|
});
|
|
142
295
|
|
|
143
296
|
proxyApp.use(async (req, res) => {
|
|
@@ -224,6 +377,20 @@ async function startGeminiProxyServer(options = {}) {
|
|
|
224
377
|
}
|
|
225
378
|
}
|
|
226
379
|
|
|
380
|
+
let bodyMutated = false;
|
|
381
|
+
|
|
382
|
+
if (isVertexAiV1Channel(channel) && stripVertexFunctionResponseIds(req.body)) {
|
|
383
|
+
bodyMutated = true;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (isVertexAiV1Channel(channel)) {
|
|
387
|
+
req.url = buildVertexAiV1Path(channel.baseUrl, req.url);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (bodyMutated) {
|
|
391
|
+
req.rawBody = Buffer.from(JSON.stringify(req.body));
|
|
392
|
+
}
|
|
393
|
+
|
|
227
394
|
const target = resolveGeminiTarget(channel.baseUrl, req.url);
|
|
228
395
|
|
|
229
396
|
proxy.web(req, res, {
|
|
@@ -315,6 +482,8 @@ async function startGeminiProxyServer(options = {}) {
|
|
|
315
482
|
let buffer = '';
|
|
316
483
|
let tokenData = createTokenData();
|
|
317
484
|
let usageRecorded = false;
|
|
485
|
+
const upstreamStatusCode = Number(proxyRes.statusCode) || 200;
|
|
486
|
+
const isUpstreamError = isHttpErrorStatus(upstreamStatusCode);
|
|
318
487
|
const parsedStream = createDecodedStream(proxyRes);
|
|
319
488
|
|
|
320
489
|
function recordUsageIfReady() {
|
|
@@ -362,30 +531,19 @@ async function startGeminiProxyServer(options = {}) {
|
|
|
362
531
|
buffer += chunk.toString('utf8');
|
|
363
532
|
|
|
364
533
|
// 检查是否是 SSE 流
|
|
365
|
-
if (proxyRes.headers['content-type']?.includes('text/event-stream')) {
|
|
366
|
-
const
|
|
367
|
-
buffer =
|
|
534
|
+
if (!isUpstreamError && proxyRes.headers['content-type']?.includes('text/event-stream')) {
|
|
535
|
+
const parsedEvents = splitSSEEvents(buffer);
|
|
536
|
+
buffer = parsedEvents.remainder;
|
|
368
537
|
|
|
369
|
-
events.forEach((eventText) => {
|
|
538
|
+
parsedEvents.events.forEach((eventText) => {
|
|
370
539
|
if (!eventText.trim()) return;
|
|
371
540
|
|
|
372
541
|
try {
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
let data = '';
|
|
376
|
-
|
|
377
|
-
lines.forEach(line => {
|
|
378
|
-
if (line.startsWith('event:')) {
|
|
379
|
-
eventType = line.substring(6).trim();
|
|
380
|
-
} else if (line.startsWith('data:')) {
|
|
381
|
-
data = line.substring(5).trim();
|
|
382
|
-
}
|
|
383
|
-
});
|
|
542
|
+
const event = parseSSEEventText(eventText);
|
|
543
|
+
if (!event) return;
|
|
384
544
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const parsed = JSON.parse(data);
|
|
388
|
-
const usage = parseSSEUsage(parsed, eventType);
|
|
545
|
+
const parsed = JSON.parse(event.data);
|
|
546
|
+
const usage = parseSSEUsage(parsed, event.eventType);
|
|
389
547
|
mergeUsageIntoTokenData(tokenData, usage);
|
|
390
548
|
|
|
391
549
|
if (usage.isDone) {
|
|
@@ -399,6 +557,26 @@ async function startGeminiProxyServer(options = {}) {
|
|
|
399
557
|
});
|
|
400
558
|
|
|
401
559
|
parsedStream.on('end', () => {
|
|
560
|
+
if (isUpstreamError) {
|
|
561
|
+
const message = extractGeminiUpstreamErrorMessage(buffer, upstreamStatusCode);
|
|
562
|
+
const error = new Error(buffer.trim() || message);
|
|
563
|
+
recordFailure(metadata.channelId, 'gemini', error);
|
|
564
|
+
publishFailureLog({
|
|
565
|
+
source: 'gemini',
|
|
566
|
+
metadata,
|
|
567
|
+
message,
|
|
568
|
+
error,
|
|
569
|
+
statusCode: upstreamStatusCode,
|
|
570
|
+
stage: 'upstream_response',
|
|
571
|
+
broadcastLog
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
if (!isResponseClosed) {
|
|
575
|
+
requestMetadata.delete(req);
|
|
576
|
+
}
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
402
580
|
// 如果不是流式响应,尝试从完整响应中解析
|
|
403
581
|
if (!proxyRes.headers['content-type']?.includes('text/event-stream')) {
|
|
404
582
|
try {
|
|
@@ -568,5 +746,11 @@ module.exports = {
|
|
|
568
746
|
stopGeminiProxyServer,
|
|
569
747
|
getGeminiProxyStatus,
|
|
570
748
|
clearGeminiRedirectCache,
|
|
571
|
-
calculateCost
|
|
749
|
+
calculateCost,
|
|
750
|
+
_test: {
|
|
751
|
+
buildVertexAiV1Path,
|
|
752
|
+
extractGeminiUpstreamErrorMessage,
|
|
753
|
+
isHttpErrorStatus,
|
|
754
|
+
stripVertexFunctionResponseIds
|
|
755
|
+
}
|
|
572
756
|
};
|
package/src/server/index.js
CHANGED
|
@@ -213,6 +213,7 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
|
|
|
213
213
|
app.use('/api/config', require('./api/config'));
|
|
214
214
|
app.use('/api/convert', require('./api/convert'));
|
|
215
215
|
app.use('/api/statistics', require('./api/statistics'));
|
|
216
|
+
app.use('/api/claude/statistics', require('./api/claude-statistics'));
|
|
216
217
|
app.use('/api/codex/statistics', require('./api/codex-statistics'));
|
|
217
218
|
app.use('/api/gemini/statistics', require('./api/gemini-statistics'));
|
|
218
219
|
app.use('/api/pm2-autostart', require('./api/pm2-autostart')());
|