coding-tool-x 3.8.1 → 3.8.2

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 (31) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/web/assets/{Analytics-8dRv1aYW.js → Analytics-COEUXmd4.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-BQRXSICO.js → ConfigTemplates-DLLkS53H.js} +1 -1
  4. package/dist/web/assets/{Home-BtzWhKIa.js → Home-DCEyLzwX.js} +1 -1
  5. package/dist/web/assets/{PluginManager-BTa-Ygf6.js → PluginManager-Oe1ZHoMO.js} +1 -1
  6. package/dist/web/assets/{ProjectList-F_cB8ldA.js → ProjectList-CWjuAFof.js} +1 -1
  7. package/dist/web/assets/{SessionList-LOrbPQvu.js → SessionList-v3ehGg9U.js} +1 -1
  8. package/dist/web/assets/{SkillManager-BzI_t-yV.js → SkillManager-DUF_qP5S.js} +1 -1
  9. package/dist/web/assets/{WorkspaceManager-Db9RGiz7.js → WorkspaceManager-CjAWBosO.js} +1 -1
  10. package/dist/web/assets/index-D5hyHPj7.js +2 -0
  11. package/dist/web/index.html +1 -1
  12. package/docs/superpowers/plans/2026-08-10-codex-conversion-compatibility.md +251 -0
  13. package/docs/superpowers/plans/2026-08-11-windows-command-window-hiding.md +90 -0
  14. package/docs/superpowers/specs/2026-08-10-codex-conversion-compatibility-design.md +61 -0
  15. package/docs/superpowers/specs/2026-08-11-windows-command-window-hiding-design.md +44 -0
  16. package/package.json +1 -1
  17. package/src/commands/update.js +1 -1
  18. package/src/server/opencode-proxy-server.js +55 -1037
  19. package/src/server/services/claude-wire.js +694 -0
  20. package/src/server/services/codex-wire.js +219 -0
  21. package/src/server/services/gemini-wire.js +759 -0
  22. package/src/server/services/model-detector.js +49 -131
  23. package/src/server/services/omp-channels.js +23 -4
  24. package/src/server/services/omp-gateway.js +29 -1
  25. package/src/server/services/omp-settings-manager.js +27 -3
  26. package/src/server/services/opencode-gateway-adapters.js +22 -1147
  27. package/src/server/services/opencode-normalization.js +546 -0
  28. package/src/server/services/session-history-index.js +1 -1
  29. package/src/server/services/session-history-worker.js +2 -1
  30. package/src/server/services/speed-test.js +85 -303
  31. package/dist/web/assets/index-DIWJmyL6.js +0 -2
@@ -6,7 +6,6 @@
6
6
 
7
7
  const https = require('https');
8
8
  const http = require('http');
9
- const crypto = require('crypto');
10
9
  const { URL } = require('url');
11
10
  const { probeModelAvailability } = require('./model-detector');
12
11
  const { getEffectiveApiKey: getClaudeEffectiveApiKey } = require('./channels');
@@ -14,7 +13,13 @@ const { getEffectiveApiKey: getCodexEffectiveApiKey } = require('./codex-channel
14
13
  const { getEffectiveApiKey: getGeminiEffectiveApiKey } = require('./gemini-channels');
15
14
  const { getEffectiveApiKey: getOpenCodeEffectiveApiKey } = require('./opencode-channels');
16
15
  const { getEffectiveApiKey: getOmpEffectiveApiKey } = require('./omp-channels');
17
- const { loadClaudeRequestTemplate } = require('./request-logger');
16
+ const { createCodexRequest } = require('./codex-wire');
17
+ const { createClaudeRequest, buildClaudeTargetUrl } = require('./claude-wire');
18
+ const {
19
+ createGeminiRequest,
20
+ buildGeminiTargetUrl,
21
+ shouldUseGeminiCliFormat
22
+ } = require('./gemini-wire');
18
23
 
19
24
  // 测试结果缓存
20
25
  const testResultsCache = new Map();
@@ -23,19 +28,7 @@ const testResultsCache = new Map();
23
28
  const DEFAULT_TIMEOUT = 15000;
24
29
  const MIN_TIMEOUT = 5000;
25
30
  const MAX_TIMEOUT = 60000;
26
- const CLAUDE_CODE_USER_AGENT = 'claude-cli/2.1.59 (external, cli)';
27
- const CLAUDE_MESSAGES_BETA_FLAGS = Object.freeze([
28
- 'claude-code-20250219',
29
- 'interleaved-thinking-2025-05-14',
30
- 'prompt-caching-scope-2026-01-05',
31
- 'effort-2025-11-24'
32
- ]);
33
- const CLAUDE_ADVANCED_TOOL_USE_BETA = 'advanced-tool-use-2025-11-20';
34
31
  const ROUTE_OR_METHOD_MISMATCH_STATUS = new Set([404, 405, 501]);
35
- const CLAUDE_USER_ID_ACCOUNT_RE = /^user_([0-9a-f]{64})_account__session_[a-z0-9._-]+$/i;
36
- const CLAUDE_USER_ID_FULL_RE = /^user_[0-9a-f]{64}_account__session_[a-z0-9._-]+$/i;
37
- let cachedClaudeAccountId = '';
38
- let cachedClaudeUserId = '';
39
32
 
40
33
  /**
41
34
  * 规范化超时时间
@@ -117,200 +110,23 @@ function resolveEffectiveApiKey(channel, channelType) {
117
110
  }
118
111
  }
119
112
 
120
- function mapStainlessOs() {
121
- switch (process.platform) {
122
- case 'darwin':
123
- return 'MacOS';
124
- case 'win32':
125
- return 'Windows';
126
- case 'linux':
127
- return 'Linux';
128
- default:
129
- return `other::${process.platform}`;
130
- }
131
- }
132
-
133
- function mapStainlessArch() {
134
- switch (process.arch) {
135
- case 'x64':
136
- return 'x64';
137
- case 'arm64':
138
- return 'arm64';
139
- case 'ia32':
140
- return 'x86';
141
- default:
142
- return `other::${process.arch}`;
143
- }
144
- }
145
-
146
- function buildClaudeBetaHeader(options = {}) {
147
- const hasTools = !!options.hasTools;
148
- const betaFlags = [...CLAUDE_MESSAGES_BETA_FLAGS];
149
- if (hasTools) {
150
- betaFlags.push(CLAUDE_ADVANCED_TOOL_USE_BETA);
151
- }
152
- return betaFlags.join(',');
153
- }
154
-
155
- function buildClaudeRequestHeaders(apiKey, options = {}) {
156
- return {
157
- 'x-api-key': apiKey || '',
158
- authorization: `Bearer ${apiKey || ''}`,
159
- 'anthropic-version': '2023-06-01',
160
- 'anthropic-beta': buildClaudeBetaHeader(options),
161
- 'anthropic-dangerous-direct-browser-access': 'true',
162
- 'x-app': 'cli',
163
- 'x-stainless-retry-count': '0',
164
- 'x-stainless-timeout': '600',
165
- 'x-stainless-runtime-version': process.version,
166
- 'x-stainless-package-version': '0.74.0',
167
- 'x-stainless-lang': 'js',
168
- 'x-stainless-runtime': 'node',
169
- 'x-stainless-arch': mapStainlessArch(),
170
- 'x-stainless-os': mapStainlessOs(),
171
- accept: 'application/json',
172
- 'accept-encoding': 'gzip, deflate',
173
- 'accept-language': '*',
174
- 'sec-fetch-mode': 'cors',
175
- connection: 'keep-alive',
176
- 'content-type': 'application/json',
177
- 'user-agent': CLAUDE_CODE_USER_AGENT
178
- };
179
- }
180
-
181
- function resolveClaudeAccountIdFromUserId(userId = '') {
182
- const value = normalizeNonEmptyString(userId);
183
- if (!value) return '';
184
- const matched = value.match(CLAUDE_USER_ID_ACCOUNT_RE);
185
- return matched ? matched[1].toLowerCase() : '';
186
- }
187
-
188
- function resolveClaudeAccountIdFromLogs() {
189
- try {
190
- const template = loadClaudeRequestTemplate();
191
- const userId = template?.userId || '';
192
- const accountId = resolveClaudeAccountIdFromUserId(userId);
193
- return (accountId && accountId !== '0'.repeat(64)) ? accountId : '';
194
- } catch {
195
- return '';
196
- }
197
- }
198
-
199
- function resolveClaudeUserIdFromLogs() {
113
+ function buildRequestPathFromTargetUrl(targetUrl) {
114
+ if (!targetUrl) return '';
200
115
  try {
201
- const template = loadClaudeRequestTemplate();
202
- const userId = normalizeNonEmptyString(template?.userId || '');
203
- if (!userId || !CLAUDE_USER_ID_FULL_RE.test(userId)) return '';
204
- const accountId = resolveClaudeAccountIdFromUserId(userId);
205
- if (!accountId || accountId === '0'.repeat(64)) return '';
206
- return userId;
116
+ const parsed = new URL(targetUrl);
117
+ return parsed.pathname + parsed.search;
207
118
  } catch {
208
119
  return '';
209
120
  }
210
121
  }
211
122
 
212
- function resolveClaudeRequestTemplate() {
213
- return loadClaudeRequestTemplate();
214
- }
215
-
216
- function resolveClaudePreferredUserId() {
217
- if (cachedClaudeUserId) {
218
- return cachedClaudeUserId;
219
- }
220
-
221
- const envUserId = normalizeNonEmptyString(
222
- process.env.OPENCODE_CLAUDE_USER_ID || process.env.CLAUDE_CODE_USER_ID
223
- );
224
-
225
- if (envUserId && CLAUDE_USER_ID_FULL_RE.test(envUserId)) {
226
- cachedClaudeUserId = envUserId;
227
- return cachedClaudeUserId;
228
- }
229
-
230
- const fromLogs = resolveClaudeUserIdFromLogs();
231
- if (fromLogs) {
232
- cachedClaudeUserId = fromLogs;
233
- return cachedClaudeUserId;
234
- }
235
-
236
- return '';
237
- }
238
-
239
- function resolveClaudeAccountId() {
240
- if (cachedClaudeAccountId) {
241
- return cachedClaudeAccountId;
242
- }
243
-
244
- const envAccountId = String(
245
- process.env.OPENCODE_CLAUDE_ACCOUNT_ID || process.env.CLAUDE_CODE_ACCOUNT_ID || ''
246
- ).trim().toLowerCase();
247
-
248
- if (/^[0-9a-f]{64}$/.test(envAccountId)) {
249
- cachedClaudeAccountId = envAccountId;
250
- return cachedClaudeAccountId;
251
- }
252
-
253
- const fromLogs = resolveClaudeAccountIdFromLogs();
254
- if (fromLogs) {
255
- cachedClaudeAccountId = fromLogs;
256
- return cachedClaudeAccountId;
257
- }
258
-
259
- cachedClaudeAccountId = '0'.repeat(64);
260
- return cachedClaudeAccountId;
261
- }
262
-
263
- function buildClaudeCodeUserId() {
264
- const preferredUserId = resolveClaudePreferredUserId();
265
- if (preferredUserId) {
266
- return preferredUserId;
267
- }
268
-
269
- const accountId = resolveClaudeAccountId();
270
- const sessionId = typeof crypto.randomUUID === 'function'
271
- ? crypto.randomUUID()
272
- : `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
273
- return `user_${accountId}_account__session_${sessionId}`;
274
- }
275
-
276
-
277
- function buildGeminiNativeGeneratePath(parsedUrl, model) {
278
- let pathname = parsedUrl.pathname.replace(/\/+$/, '');
279
- const modelsIndex = pathname.indexOf('/models');
280
- if (modelsIndex >= 0) {
281
- pathname = pathname.slice(0, modelsIndex);
282
- }
283
-
284
- let apiBasePath;
285
- if (!pathname || pathname === '/') {
286
- apiBasePath = '/v1beta';
287
- } else if (pathname.endsWith('/v1beta') || pathname.endsWith('/v1')) {
288
- apiBasePath = pathname;
289
- } else {
290
- apiBasePath = `${pathname}/v1beta`;
291
- }
292
-
293
- return `${apiBasePath}/models/${encodeURIComponent(model)}:generateContent`;
294
- }
295
-
296
- function buildGeminiCliGeneratePath(parsedUrl) {
297
- let pathname = parsedUrl.pathname.replace(/\/+$/, '');
298
- if (!pathname || pathname === '/') {
299
- return '/v1internal:generateContent';
123
+ function resolveGeminiWireMode(channel, baseUrl) {
124
+ const providerApi = String(channel?.providerApi || channel?.wireApi || '').trim().toLowerCase();
125
+ if (providerApi === 'google-gemini-cli') return { useCli: true, allowFallback: false };
126
+ if (providerApi === 'google-generative-ai' || providerApi === 'google-vertex') {
127
+ return { useCli: false, allowFallback: false };
300
128
  }
301
- if (pathname.endsWith(':streamGenerateContent')) {
302
- return pathname.replace(/:streamGenerateContent$/, ':generateContent');
303
- }
304
- if (pathname.endsWith(':generateContent')) {
305
- return pathname;
306
- }
307
- if (pathname.endsWith('/v1internal')) {
308
- return `${pathname}:generateContent`;
309
- }
310
- if (pathname.endsWith('/v1')) {
311
- return '/v1internal:generateContent';
312
- }
313
- return `${pathname}/v1internal:generateContent`;
129
+ return { useCli: shouldUseGeminiCliFormat(baseUrl), allowFallback: true };
314
130
  }
315
131
 
316
132
  function buildCodexResponsesPath(parsedUrl) {
@@ -327,24 +143,6 @@ function buildCodexResponsesPath(parsedUrl) {
327
143
  return `${pathname}/responses`;
328
144
  }
329
145
 
330
- function shouldUseGeminiCliFormat(parsedUrl) {
331
- const host = String(parsedUrl.hostname || '').toLowerCase();
332
- const pathname = parsedUrl.pathname.replace(/\/+$/, '');
333
-
334
- if (pathname.includes('/v1internal') || pathname.endsWith(':generateContent') || pathname.endsWith(':streamGenerateContent')) {
335
- return true;
336
- }
337
- if (pathname.includes('/v1beta') || pathname.includes('/models/')) {
338
- return false;
339
- }
340
- if (host.includes('cloudcode-pa.googleapis.com')) {
341
- return true;
342
- }
343
- if (!pathname || pathname === '/') {
344
- return !host.includes('generativelanguage.googleapis.com') && !host.includes('aiplatform.googleapis.com');
345
- }
346
- return false;
347
- }
348
146
 
349
147
  function extractJsonPayloads(responseData) {
350
148
  const payloads = [];
@@ -660,127 +458,111 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
660
458
  }
661
459
 
662
460
  if (channelType === 'claude') {
663
- // Anthropic Messages API - 模拟 Claude Code 请求格式
664
- let aompPath = parsedUrl.pathname.replace(/\/$/, '');
665
- if (!aompPath.endsWith('/messages')) {
666
- aompPath = aompPath + (aompPath.endsWith('/v1') ? '/messages' : '/v1/messages');
667
- }
668
- aompPath += '?beta=true';
669
-
670
461
  testModel = modelProbe?.preferredTestModel || normalizeNonEmptyString(model) || 'claude-sonnet-4-20250514';
671
- const sessionId = typeof crypto.randomUUID === 'function'
672
- ? crypto.randomUUID()
673
- : `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
674
- const userId = buildClaudeCodeUserId() || `user_${'0'.repeat(64)}_account__session_${sessionId}`;
675
- const template = resolveClaudeRequestTemplate();
676
- const systemBlocks = template.system;
677
- const toolsToUse = template.tools;
678
462
  const requestPayload = {
679
463
  model: testModel,
680
464
  max_tokens: 10,
681
465
  temperature: 1,
682
- stream: true,
683
- messages: [{ role: 'user', content: [{ type: 'text', text: 'Hi' }] }],
684
- system: systemBlocks,
685
- metadata: { user_id: userId },
686
- tools: toolsToUse
466
+ messages: [{ role: 'user', content: [{ type: 'text', text: 'Hi' }] }]
687
467
  };
468
+ const primaryConverted = createClaudeRequest('/v1/messages', requestPayload, {
469
+ apiKey,
470
+ baseUrl,
471
+ fallbackModel: testModel,
472
+ stream: true
473
+ });
474
+ const fallbackConverted = createClaudeRequest('/v1/messages', requestPayload, {
475
+ apiKey,
476
+ baseUrl,
477
+ fallbackModel: testModel,
478
+ stream: false
479
+ });
480
+ const aompPath = buildRequestPathFromTargetUrl(buildClaudeTargetUrl(baseUrl));
688
481
  primaryRequestConfig = {
689
482
  aompPath,
690
- requestBody: JSON.stringify(requestPayload),
691
- headers: buildClaudeRequestHeaders(apiKey, { hasTools: true }),
483
+ requestBody: JSON.stringify(primaryConverted.body),
484
+ headers: primaryConverted.headers,
692
485
  isStreamingResponse: true
693
486
  };
694
- // Fallback: non-streaming for gateways that don't support SSE
695
487
  fallbackRequestConfig = {
696
488
  aompPath,
697
- requestBody: JSON.stringify({ ...requestPayload, stream: false }),
698
- headers: buildClaudeRequestHeaders(apiKey, { hasTools: true }),
489
+ requestBody: JSON.stringify(fallbackConverted.body),
490
+ headers: fallbackConverted.headers,
699
491
  isStreamingResponse: false
700
492
  };
701
493
  } else if (channelType === 'codex') {
702
494
  const aompPath = buildCodexResponsesPath(parsedUrl);
703
- testModel = modelProbe?.preferredTestModel || normalizeNonEmptyString(model) || 'gpt-5-codex';
495
+ testModel = modelProbe?.preferredTestModel || normalizeNonEmptyString(model) || 'gpt-5.5';
704
496
  const codexSessionId = `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
705
-
706
- const baseBody = {
497
+ const converted = createCodexRequest({
707
498
  model: testModel,
708
499
  instructions: 'You are Codex.',
709
500
  input: [{ type: 'message', role: 'user', content: [{ type: 'input_text', text: 'ping' }] }],
710
- store: false,
711
- prompt_cache_key: codexSessionId
712
- };
501
+ prompt_cache_key: codexSessionId,
502
+ stream: false,
503
+ store: false
504
+ }, {
505
+ apiKey,
506
+ sessionId: codexSessionId
507
+ });
713
508
 
714
509
  primaryRequestConfig = {
715
510
  aompPath,
716
- requestBody: JSON.stringify({ ...baseBody, stream: false }),
717
- headers: {
718
- 'Authorization': `Bearer ${apiKey || ''}`,
719
- 'Accept': 'application/json',
720
- 'Connection': 'Keep-Alive',
721
- 'Version': '0.101.0',
722
- 'Session_id': codexSessionId,
723
- 'Originator': 'codex_cli_rs',
724
- 'Content-Type': 'application/json',
725
- 'User-Agent': 'codex_cli_rs/0.101.0 (Mac OS 26.0.1; arm64) Apple_Terminal/464',
726
- 'openai-beta': 'responses=experimental'
727
- },
728
- isStreamingResponse: false
729
- };
730
-
731
- fallbackRequestConfig = {
732
- aompPath,
733
- requestBody: JSON.stringify({ ...baseBody, stream: true }),
734
- headers: {
735
- ...primaryRequestConfig.headers,
736
- 'Accept': 'text/event-stream'
737
- },
511
+ requestBody: JSON.stringify(converted.body),
512
+ headers: converted.headers,
738
513
  isStreamingResponse: true
739
514
  };
515
+ fallbackRequestConfig = primaryRequestConfig;
740
516
  } else if (channelType === 'gemini') {
741
517
  testModel = modelProbe?.preferredTestModel || normalizeNonEmptyString(model) || 'gemini-2.5-pro';
742
- const useCliFormat = shouldUseGeminiCliFormat(parsedUrl);
743
-
744
- const cliRequestConfig = {
745
- aompPath: buildGeminiCliGeneratePath(parsedUrl),
746
- requestBody: JSON.stringify({
747
- project: '',
748
- model: testModel,
749
- request: {
750
- contents: [{ role: 'user', parts: [{ text: 'ping' }] }],
751
- generationConfig: { maxOutputTokens: 1, temperature: 0 }
752
- }
753
- }),
754
- headers: {
755
- 'Authorization': `Bearer ${apiKey || ''}`,
756
- 'x-goog-api-key': apiKey || '',
757
- 'Accept': 'application/json',
758
- 'Content-Type': 'application/json',
759
- 'User-Agent': 'google-api-nodejs-client/9.15.1',
760
- 'X-Goog-Api-Client': 'gl-node/22.17.0',
761
- 'Client-Metadata': 'ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI'
762
- },
763
- isStreamingResponse: false
518
+ const minimalPayload = {
519
+ model: testModel,
520
+ max_output_tokens: 1,
521
+ temperature: 0,
522
+ input: [{ type: 'message', role: 'user', content: [{ type: 'input_text', text: 'ping' }] }]
523
+ };
524
+ const wireMode = resolveGeminiWireMode(channel, baseUrl);
525
+ const createGeminiRequestConfig = (useCli) => {
526
+ const converted = createGeminiRequest('/v1/responses', minimalPayload, {
527
+ apiKey,
528
+ fallbackModel: testModel,
529
+ stream: false,
530
+ useCli
531
+ });
532
+ return {
533
+ aompPath: buildRequestPathFromTargetUrl(
534
+ buildGeminiTargetUrl(baseUrl, converted.model, apiKey, { stream: false, useCli })
535
+ ),
536
+ requestBody: JSON.stringify(converted.body),
537
+ headers: converted.headers,
538
+ isStreamingResponse: false
539
+ };
764
540
  };
765
541
 
766
- const nativeRequestConfig = {
767
- aompPath: buildGeminiNativeGeneratePath(parsedUrl, testModel),
542
+ primaryRequestConfig = createGeminiRequestConfig(wireMode.useCli);
543
+ fallbackRequestConfig = wireMode.allowFallback
544
+ ? createGeminiRequestConfig(!wireMode.useCli)
545
+ : null;
546
+ } else if (channelType === 'openai_compatible' || channelType === 'opencode') {
547
+ testModel = modelProbe?.preferredTestModel || normalizeNonEmptyString(model) || 'gpt-4o-mini';
548
+ let aompPath = parsedUrl.pathname.replace(/\/$/, '');
549
+ if (!aompPath.endsWith('/chat/completions')) {
550
+ aompPath = aompPath + (aompPath.endsWith('/v1') ? '/chat/completions' : '/v1/chat/completions');
551
+ }
552
+ primaryRequestConfig = {
553
+ aompPath,
768
554
  requestBody: JSON.stringify({
769
- contents: [{ role: 'user', parts: [{ text: 'ping' }] }],
770
- generationConfig: { maxOutputTokens: 1, temperature: 0 }
555
+ model: testModel,
556
+ max_tokens: 1,
557
+ messages: [{ role: 'user', content: 'Hi' }]
771
558
  }),
772
559
  headers: {
773
560
  'Authorization': `Bearer ${apiKey || ''}`,
774
- 'x-goog-api-key': apiKey || '',
775
- 'Accept': 'application/json',
776
561
  'Content-Type': 'application/json',
777
- 'User-Agent': 'google-genai-sdk/0.8.0'
562
+ 'User-Agent': 'Coding-Tool-SpeedTest/1.0'
778
563
  },
779
564
  isStreamingResponse: false
780
565
  };
781
-
782
- primaryRequestConfig = useCliFormat ? cliRequestConfig : nativeRequestConfig;
783
- fallbackRequestConfig = useCliFormat ? nativeRequestConfig : cliRequestConfig;
784
566
  } else {
785
567
  testModel = modelProbe?.preferredTestModel || normalizeNonEmptyString(model) || 'gpt-4o-mini';
786
568
  let aompPath = parsedUrl.pathname.replace(/\/$/, '');