coding-tool-x 3.8.1 → 3.8.3

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 (36) 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/plans/2026-08-13-omp-restart-recovery.md +48 -0
  15. package/docs/superpowers/specs/2026-08-10-codex-conversion-compatibility-design.md +61 -0
  16. package/docs/superpowers/specs/2026-08-11-windows-command-window-hiding-design.md +44 -0
  17. package/docs/superpowers/specs/2026-08-13-omp-restart-recovery-design.md +22 -0
  18. package/package.json +1 -1
  19. package/src/commands/daemon.js +3 -12
  20. package/src/commands/update.js +1 -1
  21. package/src/index.js +8 -1
  22. package/src/server/omp-proxy-server.js +9 -6
  23. package/src/server/opencode-proxy-server.js +55 -1037
  24. package/src/server/services/claude-wire.js +694 -0
  25. package/src/server/services/codex-wire.js +219 -0
  26. package/src/server/services/gemini-wire.js +759 -0
  27. package/src/server/services/model-detector.js +49 -131
  28. package/src/server/services/omp-channels.js +23 -4
  29. package/src/server/services/omp-gateway.js +29 -1
  30. package/src/server/services/omp-settings-manager.js +27 -3
  31. package/src/server/services/opencode-gateway-adapters.js +22 -1147
  32. package/src/server/services/opencode-normalization.js +546 -0
  33. package/src/server/services/session-history-index.js +1 -1
  34. package/src/server/services/session-history-worker.js +2 -1
  35. package/src/server/services/speed-test.js +85 -303
  36. package/dist/web/assets/index-DIWJmyL6.js +0 -2
@@ -11,6 +11,13 @@ const { URL } = require('url');
11
11
  const crypto = require('crypto');
12
12
  const zlib = require('zlib');
13
13
  const { loadConfig } = require('../../config/loader');
14
+ const { createCodexRequest, buildCodexTargetUrl } = require('./codex-wire');
15
+ const { createClaudeRequest, buildClaudeTargetUrl } = require('./claude-wire');
16
+ const {
17
+ createGeminiRequest,
18
+ buildGeminiTargetUrl,
19
+ shouldUseGeminiCliFormat
20
+ } = require('./gemini-wire');
14
21
  const { PATHS } = require('../../config/paths');
15
22
 
16
23
  // 内置模型优先级(当配置缺失时兜底)
@@ -236,7 +243,6 @@ const TEST_TIMEOUT_MS = 10000; // 10 seconds per model test
236
243
  const MODEL_LIST_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
237
244
  const MODEL_LIST_FAILURE_TTL_MS = 5 * 60 * 1000;
238
245
  const MODEL_LIST_AUTH_FAILURE_TTL_MS = 60 * 60 * 1000;
239
- const CLAUDE_CODE_BETA_HEADER = 'claude-code-20250219,interleaved-thinking-2025-05-14';
240
246
 
241
247
  const MODEL_UNAVAILABLE_HINTS = [
242
248
  'not found',
@@ -347,170 +353,82 @@ function buildOpenAiCompatibleUrl(baseUrl, endpoint) {
347
353
  return `${trimmed}${normalizedEndpoint}`;
348
354
  }
349
355
 
350
- function buildClaudeMessagesUrl(baseUrl, options = {}) {
351
- const withBeta = options.withBeta !== false;
352
- const parsed = new URL(String(baseUrl || '').trim());
353
- let pathname = parsed.pathname.replace(/\/+$/, '');
354
-
355
- if (!pathname || pathname === '/') {
356
- pathname = '/v1/messages';
357
- } else if (pathname.endsWith('/messages')) {
358
- // noop
359
- } else if (pathname.endsWith('/v1')) {
360
- pathname = `${pathname}/messages`;
361
- } else {
362
- pathname = `${pathname}/v1/messages`;
363
- }
364
-
365
- parsed.pathname = pathname;
366
- if (withBeta) {
367
- parsed.searchParams.set('beta', 'true');
368
- }
369
- return parsed.toString();
370
- }
371
-
372
- function buildGeminiGenerateContentUrl(baseUrl, model, apiKey = '') {
373
- const modelName = String(model || '').trim();
374
- if (!modelName) {
375
- throw new Error('Model is required for Gemini probe');
376
- }
377
-
378
- const parsed = new URL(String(baseUrl || '').trim());
379
- let pathname = parsed.pathname.replace(/\/+$/, '');
380
- const modelsIndex = pathname.indexOf('/models');
381
- if (modelsIndex >= 0) {
382
- pathname = pathname.slice(0, modelsIndex);
383
- }
384
-
385
- let apiBasePath;
386
- if (!pathname || pathname === '/') {
387
- apiBasePath = '/v1beta';
388
- } else if (pathname.endsWith('/v1beta') || pathname.endsWith('/v1')) {
389
- apiBasePath = pathname;
390
- } else {
391
- apiBasePath = `${pathname}/v1beta`;
392
- }
393
356
 
394
- parsed.pathname = `${apiBasePath}/models/${encodeURIComponent(modelName)}:generateContent`;
395
- if (apiKey) {
396
- parsed.searchParams.set('key', apiKey);
397
- }
398
- return parsed.toString();
357
+ function resolveExplicitGeminiUseCli(channel) {
358
+ const providerApi = String(channel?.providerApi || channel?.wireApi || '').trim().toLowerCase();
359
+ if (providerApi === 'google-gemini-cli') return true;
360
+ if (providerApi === 'google-generative-ai' || providerApi === 'google-vertex') return false;
361
+ return shouldUseGeminiCliFormat(channel?.baseUrl || '');
399
362
  }
400
363
 
401
- function buildClaudeProbePayload(model, options = {}) {
402
- const includeSystem = options.includeSystem === true;
403
- const systemAsArray = options.systemAsArray === true;
404
- const includeMetadata = options.includeMetadata === true;
405
- const sessionId = Math.random().toString(36).substring(2, 15);
406
-
407
- const payload = {
364
+ function createClaudeProbeAttempts(channel, model) {
365
+ const apiKey = channel.apiKey || '';
366
+ const minimalPayload = {
408
367
  model,
409
368
  max_tokens: 1,
410
369
  stream: false,
411
370
  messages: [{ role: 'user', content: [{ type: 'text', text: 'ping' }] }]
412
371
  };
413
-
414
- if (includeSystem) {
415
- const systemPrompt = "You are Claude Code, Anthropic's official CLI for Claude.";
416
- payload.system = systemAsArray
417
- ? [{ type: 'text', text: systemPrompt }]
418
- : systemPrompt;
419
- }
420
-
421
- if (includeMetadata) {
422
- payload.metadata = {
423
- user_id: `user_0000000000000000000000000000000000000000000000000000000000000000_account__session_${sessionId}`
424
- };
425
- }
426
-
427
- return JSON.stringify(payload);
428
- }
429
-
430
- function createClaudeProbeAttempts(channel, model) {
431
- const apiKey = channel.apiKey || '';
432
- const commonHeaders = {
433
- ...buildRequestHeaders('claude', channel),
434
- 'Content-Type': 'application/json',
435
- 'x-api-key': apiKey,
436
- 'Authorization': `Bearer ${apiKey}`,
437
- 'anthropic-version': '2023-06-01',
438
- 'anthropic-beta': CLAUDE_CODE_BETA_HEADER,
439
- 'Accept-Encoding': 'gzip, deflate, br',
440
- 'User-Agent': 'claude-cli/2.0.53 (external, cli)'
441
- };
442
-
443
- const legacyBody = buildClaudeProbePayload(model, {
444
- includeSystem: true,
445
- systemAsArray: true,
446
- includeMetadata: true
372
+ const converted = createClaudeRequest('/v1/messages', minimalPayload, {
373
+ apiKey,
374
+ baseUrl: channel.baseUrl,
375
+ fallbackModel: model,
376
+ stream: false
447
377
  });
448
378
 
449
379
  return [
450
380
  {
451
- label: 'claude-code-legacy-beta',
452
- url: buildClaudeMessagesUrl(channel.baseUrl, { withBeta: true }),
453
- body: legacyBody,
454
- headers: {
455
- ...commonHeaders,
456
- 'anthropic-dangerous-direct-browser-access': 'true',
457
- 'x-app': 'cli',
458
- 'x-stainless-lang': 'js',
459
- 'x-stainless-runtime': 'node'
460
- }
381
+ label: 'claude-messages',
382
+ url: buildClaudeTargetUrl(channel.baseUrl),
383
+ body: JSON.stringify(converted.body),
384
+ headers: converted.headers
461
385
  }
462
386
  ];
463
387
  }
464
-
465
388
  function createCodexProbeAttempts(channel, model) {
466
389
  const apiKey = channel.apiKey || '';
467
- const commonHeaders = {
468
- ...buildRequestHeaders('codex', channel),
469
- 'Content-Type': 'application/json',
470
- 'Authorization': `Bearer ${apiKey}`,
471
- 'User-Agent': 'codex_cli_rs/0.65.0'
472
- };
473
-
474
- const responsesBody = JSON.stringify({
390
+ const sessionId = crypto.randomUUID();
391
+ const converted = createCodexRequest({
475
392
  model,
476
393
  instructions: 'You are Codex.',
477
394
  input: [{ type: 'message', role: 'user', content: [{ type: 'input_text', text: 'ping' }] }],
478
- max_output_tokens: 1,
479
- stream: false,
480
- store: false
395
+ prompt_cache_key: sessionId
396
+ }, {
397
+ apiKey,
398
+ sessionId
481
399
  });
482
400
 
483
401
  return [
484
402
  {
485
403
  label: 'codex-responses',
486
- url: buildOpenAiCompatibleUrl(channel.baseUrl, '/v1/responses'),
487
- body: responsesBody,
488
- headers: {
489
- ...commonHeaders,
490
- 'openai-beta': 'responses=experimental'
491
- }
404
+ url: buildCodexTargetUrl(channel.baseUrl),
405
+ body: JSON.stringify(converted.body),
406
+ headers: converted.headers
492
407
  }
493
408
  ];
494
409
  }
495
410
 
496
411
  function createGeminiProbeAttempt(channel, model) {
497
412
  const apiKey = channel.apiKey || '';
498
- const body = JSON.stringify({
499
- contents: [{ role: 'user', parts: [{ text: 'test' }] }],
500
- generationConfig: { maxOutputTokens: 1, temperature: 0 }
413
+ const useCli = resolveExplicitGeminiUseCli(channel);
414
+ const minimalPayload = {
415
+ model,
416
+ max_output_tokens: 1,
417
+ temperature: 0,
418
+ input: [{ type: 'message', role: 'user', content: [{ type: 'input_text', text: 'ping' }] }]
419
+ };
420
+ const converted = createGeminiRequest('/v1/responses', minimalPayload, {
421
+ apiKey,
422
+ fallbackModel: model,
423
+ stream: false,
424
+ useCli
501
425
  });
502
426
 
503
427
  return {
504
- label: 'gemini-generate-content',
505
- url: buildGeminiGenerateContentUrl(channel.baseUrl, model, apiKey),
506
- body,
507
- headers: {
508
- ...buildRequestHeaders('gemini', channel),
509
- 'Content-Type': 'application/json',
510
- 'Authorization': `Bearer ${apiKey}`,
511
- 'x-goog-api-key': apiKey,
512
- 'User-Agent': 'google-genai-sdk/0.8.0'
513
- }
428
+ label: useCli ? 'gemini-cli-generate-content' : 'gemini-generate-content',
429
+ url: buildGeminiTargetUrl(channel.baseUrl, converted.model, apiKey, { stream: false, useCli }),
430
+ body: JSON.stringify(converted.body),
431
+ headers: converted.headers
514
432
  };
515
433
  }
516
434
 
@@ -172,7 +172,10 @@ class OmpChannelService extends BaseChannelService {
172
172
  _applyDefaults(channel) {
173
173
  const normalized = super._applyDefaults(channel);
174
174
  normalized.providerKey = normalized.providerKey || normalized.provider || normalized.name || normalized.id;
175
- normalized.providerApi = normalized.providerApi || normalized.api || normalized.wireApi || 'openai-completions';
175
+ normalized.providerApi = normalizeProviderApi(
176
+ normalized.providerApi || normalized.api || normalized.wireApi || 'openai-completions',
177
+ { gatewaySourceType: normalized.gatewaySourceType }
178
+ );
176
179
  normalized.authMode = ['api_key', 'oauth', 'none'].includes(normalized.authMode)
177
180
  ? normalized.authMode
178
181
  : 'api_key';
@@ -605,19 +608,35 @@ function buildOmpSyncCandidate(modelsConfig, selection, channels) {
605
608
  };
606
609
  }
607
610
 
611
+ const rawProviderApi = sourceProvider.api || provider.api || 'openai-completions';
612
+ const apiVal = String(rawProviderApi).trim();
613
+ let gatewaySourceType;
614
+ if (existing?.gatewaySourceType) {
615
+ gatewaySourceType = existing.gatewaySourceType;
616
+ } else if (apiVal === 'anthropic-messages') {
617
+ gatewaySourceType = 'claude';
618
+ } else if (apiVal === 'google-generative-ai' || apiVal === 'google-gemini-cli' || apiVal === 'google-vertex') {
619
+ gatewaySourceType = 'gemini';
620
+ } else if (apiVal === 'openai-codex-responses') {
621
+ gatewaySourceType = 'codex';
622
+ } else {
623
+ gatewaySourceType = 'openai_compatible';
624
+ }
625
+ const providerApi = normalizeProviderApi(rawProviderApi, { gatewaySourceType });
626
+
608
627
  return {
609
628
  name: existing?.name || originalProviderId || providerId,
610
629
  providerKey: originalProviderId || providerId,
611
630
  baseUrl,
612
631
  apiKey: authMode === 'none' ? '' : apiKey,
613
- providerApi: normalizeProviderApi(sourceProvider.api || provider.api),
614
- wireApi: sourceProvider.api || provider.api || 'openai-completions',
632
+ providerApi,
633
+ wireApi: providerApi,
615
634
  authMode,
616
635
  oauthProviderId: authMode === 'oauth' ? (existing?.oauthProviderId || originalProviderId || providerId) : '',
617
636
  model: preferredModel,
618
637
  allowedModels,
619
638
  models: filterOmpProviderModels(provider.models, allowedModels),
620
- gatewaySourceType: existing?.gatewaySourceType || 'openai_compatible',
639
+ gatewaySourceType,
621
640
  credentialSource: credential.value ? credential.source : 'existing-channel'
622
641
  };
623
642
  }
@@ -58,6 +58,34 @@ function buildUpstreamRequestPath(baseUrl, requestUrl) {
58
58
  return `${pathname.startsWith('/') ? pathname : `/${pathname}`}${query ? `?${query}` : ''}`;
59
59
  }
60
60
 
61
+ function rewriteCodexResponsesRequestPath(baseUrl, requestUrl, providerApi = '') {
62
+ if (providerApi !== 'openai-codex-responses') return buildUpstreamRequestPath(baseUrl, requestUrl);
63
+
64
+ const base = new URL(baseUrl);
65
+ const request = new URL(requestUrl, 'http://127.0.0.1');
66
+ const requestedPath = request.pathname.replace(/\/+$/, '') || '/';
67
+ if (!requestedPath.endsWith('/codex/responses')) {
68
+ return buildUpstreamRequestPath(baseUrl, requestUrl);
69
+ }
70
+
71
+ const basePath = base.pathname === '/' ? '' : base.pathname.replace(/\/+$/, '');
72
+ let pathname;
73
+ if (!basePath) {
74
+ pathname = '/codex/responses';
75
+ } else if (basePath.endsWith('/responses')) {
76
+ pathname = basePath;
77
+ } else if (basePath.endsWith('/v1') || basePath.endsWith('/codex')) {
78
+ pathname = `${basePath}/responses`;
79
+ } else {
80
+ pathname = `${basePath}/codex/responses`;
81
+ }
82
+
83
+ const search = new URLSearchParams(base.searchParams);
84
+ request.searchParams.forEach((value, name) => search.set(name, value));
85
+ const query = search.toString();
86
+ return `${pathname.startsWith('/') ? pathname : `/${pathname}`}${query ? `?${query}` : ''}`;
87
+ }
88
+
61
89
  function rewriteCredential(headers, requestUrl, route, channel, options = {}) {
62
90
  const next = { ...headers };
63
91
  delete next.host;
@@ -428,7 +456,7 @@ function createOmpGateway(options = {}) {
428
456
  return;
429
457
  }
430
458
 
431
- const upstreamPath = buildUpstreamRequestPath(channel.baseUrl, route.upstreamPath);
459
+ const upstreamPath = rewriteCodexResponsesRequestPath(channel.baseUrl, route.upstreamPath, route.providerApi);
432
460
  const rewritten = rewriteCredential(req.headers, upstreamPath, route, channel);
433
461
  const transport = upstream.protocol === 'https:' ? https : http;
434
462
  let upstreamResponse = null;
@@ -60,9 +60,30 @@ function isManagedProviderId(providerId = '') {
60
60
  return String(providerId || '').startsWith(MANAGED_PROVIDER_PREFIX);
61
61
  }
62
62
 
63
- function normalizeProviderApi(value = '') {
63
+ function normalizeProviderApi(value = '', options = {}) {
64
64
  const normalized = String(value || '').trim();
65
- if (!normalized || normalized === 'openai' || normalized === 'chat' || normalized === 'chat.completions') {
65
+ const gatewaySourceType = String(options.gatewaySourceType || '').trim().toLowerCase();
66
+
67
+ const isGeneric = !normalized
68
+ || normalized === 'openai'
69
+ || normalized === 'chat'
70
+ || normalized === 'chat.completions'
71
+ || normalized === 'openai-completions';
72
+
73
+ if (gatewaySourceType === 'codex' && (
74
+ isGeneric
75
+ || normalized === 'responses'
76
+ || normalized === 'openai-responses'
77
+ )) {
78
+ return 'openai-codex-responses';
79
+ }
80
+ if (gatewaySourceType === 'claude' && isGeneric) {
81
+ return 'anthropic-messages';
82
+ }
83
+ if (gatewaySourceType === 'gemini' && isGeneric) {
84
+ return 'google-generative-ai';
85
+ }
86
+ if (isGeneric) {
66
87
  return 'openai-completions';
67
88
  }
68
89
  if (normalized === 'responses') {
@@ -341,7 +362,9 @@ function buildProviderEntry(channel = {}, options = {}) {
341
362
  const entry = compactObject({
342
363
  ...normalizedProvider.config,
343
364
  baseUrl: String(channel.baseUrl || providerConfig.baseUrl || '').trim(),
344
- api: normalizeProviderApi(channel.providerApi || channel.api || channel.wireApi || providerConfig.api),
365
+ api: normalizeProviderApi(channel.providerApi || channel.api || channel.wireApi || providerConfig.api, {
366
+ gatewaySourceType: channel.gatewaySourceType
367
+ }),
345
368
  models
346
369
  });
347
370
 
@@ -1220,6 +1243,7 @@ module.exports = {
1220
1243
  getLastManagedOmpSyncResult,
1221
1244
  isManagedOmpProvidersActive,
1222
1245
  isManagedProviderExtensionActive: isManagedOmpProvidersActive,
1246
+ isManagedProviderId,
1223
1247
  normalizeProviderId,
1224
1248
  normalizeProviderApi,
1225
1249
  normalizeModels,