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.
- package/CHANGELOG.md +2 -0
- package/dist/web/assets/{Analytics-8dRv1aYW.js → Analytics-COEUXmd4.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-BQRXSICO.js → ConfigTemplates-DLLkS53H.js} +1 -1
- package/dist/web/assets/{Home-BtzWhKIa.js → Home-DCEyLzwX.js} +1 -1
- package/dist/web/assets/{PluginManager-BTa-Ygf6.js → PluginManager-Oe1ZHoMO.js} +1 -1
- package/dist/web/assets/{ProjectList-F_cB8ldA.js → ProjectList-CWjuAFof.js} +1 -1
- package/dist/web/assets/{SessionList-LOrbPQvu.js → SessionList-v3ehGg9U.js} +1 -1
- package/dist/web/assets/{SkillManager-BzI_t-yV.js → SkillManager-DUF_qP5S.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-Db9RGiz7.js → WorkspaceManager-CjAWBosO.js} +1 -1
- package/dist/web/assets/index-D5hyHPj7.js +2 -0
- package/dist/web/index.html +1 -1
- package/docs/superpowers/plans/2026-08-10-codex-conversion-compatibility.md +251 -0
- package/docs/superpowers/plans/2026-08-11-windows-command-window-hiding.md +90 -0
- package/docs/superpowers/plans/2026-08-13-omp-restart-recovery.md +48 -0
- package/docs/superpowers/specs/2026-08-10-codex-conversion-compatibility-design.md +61 -0
- package/docs/superpowers/specs/2026-08-11-windows-command-window-hiding-design.md +44 -0
- package/docs/superpowers/specs/2026-08-13-omp-restart-recovery-design.md +22 -0
- package/package.json +1 -1
- package/src/commands/daemon.js +3 -12
- package/src/commands/update.js +1 -1
- package/src/index.js +8 -1
- package/src/server/omp-proxy-server.js +9 -6
- package/src/server/opencode-proxy-server.js +55 -1037
- package/src/server/services/claude-wire.js +694 -0
- package/src/server/services/codex-wire.js +219 -0
- package/src/server/services/gemini-wire.js +759 -0
- package/src/server/services/model-detector.js +49 -131
- package/src/server/services/omp-channels.js +23 -4
- package/src/server/services/omp-gateway.js +29 -1
- package/src/server/services/omp-settings-manager.js +27 -3
- package/src/server/services/opencode-gateway-adapters.js +22 -1147
- package/src/server/services/opencode-normalization.js +546 -0
- package/src/server/services/session-history-index.js +1 -1
- package/src/server/services/session-history-worker.js +2 -1
- package/src/server/services/speed-test.js +85 -303
- 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
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
return
|
|
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
|
|
402
|
-
const
|
|
403
|
-
const
|
|
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
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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-
|
|
452
|
-
url:
|
|
453
|
-
body:
|
|
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
|
|
468
|
-
|
|
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
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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:
|
|
487
|
-
body:
|
|
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
|
|
499
|
-
|
|
500
|
-
|
|
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:
|
|
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 =
|
|
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
|
|
614
|
-
wireApi:
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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,
|