coding-tool-x 3.6.7 → 3.6.8

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.
Files changed (54) hide show
  1. package/dist/web/assets/{Analytics-CKycBI0G.js → Analytics-C57ZRDq5.js} +1 -1
  2. package/dist/web/assets/{ConfigTemplates-CACLQb8e.js → ConfigTemplates-B3qqqL05.js} +1 -1
  3. package/dist/web/assets/Home-CG5XrTNc.js +1 -0
  4. package/dist/web/assets/Home-CIua-b-o.css +1 -0
  5. package/dist/web/assets/{PluginManager-CQJl_uso.js → PluginManager-BhX0LUzr.js} +1 -1
  6. package/dist/web/assets/{ProjectList-DSkvQFEP.js → ProjectList-BQ01OZn9.js} +1 -1
  7. package/dist/web/assets/{SessionList-Czjs1GN1.js → SessionList-DPv2IOXG.js} +1 -1
  8. package/dist/web/assets/{SkillManager-Cg1xZ5Cv.js → SkillManager-D5CfQm2O.js} +1 -1
  9. package/dist/web/assets/{WorkspaceManager-COjYeKvN.js → WorkspaceManager-Ce8ggteK.js} +1 -1
  10. package/dist/web/assets/index-CpnFNWqC.js +2 -0
  11. package/dist/web/assets/index-DJ9VFidd.css +1 -0
  12. package/dist/web/assets/{naive-ui-DM897mIP.js → naive-ui-kcWDeJwl.js} +1 -1
  13. package/dist/web/index.html +3 -3
  14. package/package.json +1 -1
  15. package/src/commands/stats.js +1 -1
  16. package/src/config/channel-preset-websites.js +2 -1
  17. package/src/config/paths.js +8 -0
  18. package/src/server/api/agents.js +16 -54
  19. package/src/server/api/claude-hooks.js +1 -1
  20. package/src/server/api/claude-statistics.js +57 -0
  21. package/src/server/api/commands.js +16 -54
  22. package/src/server/api/dashboard.js +2 -2
  23. package/src/server/api/gemini-channels.js +2 -0
  24. package/src/server/api/hooks.js +4 -1
  25. package/src/server/api/plugins.js +40 -65
  26. package/src/server/api/skills.js +21 -77
  27. package/src/server/api/validation-errors.js +34 -0
  28. package/src/server/gemini-proxy-server.js +205 -21
  29. package/src/server/index.js +1 -0
  30. package/src/server/services/agents-service.js +166 -75
  31. package/src/server/services/base/base-channel-service.js +15 -0
  32. package/src/server/services/base/response-usage-parser.js +44 -0
  33. package/src/server/services/channel-balance.js +86 -1
  34. package/src/server/services/claude-statistics-service.js +139 -0
  35. package/src/server/services/commands-service.js +203 -121
  36. package/src/server/services/config-artifact-paths.js +116 -0
  37. package/src/server/services/config-export-service.js +7 -6
  38. package/src/server/services/config-registry-service.js +2 -2
  39. package/src/server/services/config-sync-manager.js +45 -11
  40. package/src/server/services/config-templates-service.js +21 -19
  41. package/src/server/services/format-converter.js +42 -4
  42. package/src/server/services/gemini-channels.js +30 -1
  43. package/src/server/services/gemini-sessions.js +347 -61
  44. package/src/server/services/mcp-client.js +236 -49
  45. package/src/server/services/mcp-service.js +51 -64
  46. package/src/server/services/notification-hooks.js +659 -88
  47. package/src/server/services/opencode-channels.js +15 -0
  48. package/src/server/services/plugins-service.js +771 -63
  49. package/src/server/services/repo-scanner-base.js +6 -29
  50. package/src/server/services/skill-service.js +187 -63
  51. package/dist/web/assets/Home-BeGkC4bb.css +0 -1
  52. package/dist/web/assets/Home-Bk6sP5Ed.js +0 -1
  53. package/dist/web/assets/index-0isNzXNY.css +0 -1
  54. 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 { parseSSEUsage, parseNonStreamingUsage, mergeUsageIntoTokenData, createTokenData } = require('./services/base/response-usage-parser');
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 events = buffer.split('\n\n');
367
- buffer = events.pop() || '';
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 lines = eventText.split('\n');
374
- let eventType = '';
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
- if (!data || data === '[DONE]') return;
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
  };
@@ -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')());