coding-tool-x 3.8.0 → 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.
- package/CHANGELOG.md +2 -0
- package/README.md +4 -6
- 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/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/package.json +2 -2
- package/src/commands/update.js +1 -1
- package/src/config/paths.js +1 -0
- package/src/server/api/codex-sessions.js +14 -14
- package/src/server/api/gemini-sessions.js +52 -14
- package/src/server/api/omp-projects.js +2 -2
- package/src/server/api/omp-sessions.js +61 -23
- package/src/server/api/sessions.js +44 -2
- package/src/server/opencode-proxy-server.js +55 -1037
- package/src/server/services/claude-wire.js +694 -0
- package/src/server/services/codex-sessions.js +41 -233
- package/src/server/services/codex-wire.js +219 -0
- package/src/server/services/dashboard-snapshot-worker.js +5 -5
- package/src/server/services/gemini-sessions.js +67 -157
- 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-sessions.js +102 -105
- 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/opencode-sessions.js +339 -546
- package/src/server/services/session-converter.js +4 -4
- package/src/server/services/session-history-adapters/claude.js +235 -0
- package/src/server/services/session-history-adapters/codex.js +158 -0
- package/src/server/services/session-history-adapters/gemini.js +292 -0
- package/src/server/services/session-history-adapters/index.js +20 -0
- package/src/server/services/session-history-adapters/omp.js +256 -0
- package/src/server/services/session-history-index.js +930 -0
- package/src/server/services/session-history-worker.js +99 -0
- package/src/server/services/session-snapshot-worker.js +7 -7
- package/src/server/services/sessions.js +167 -775
- package/src/server/services/speed-test.js +85 -303
- package/src/server/services/sqlite-connection.js +78 -0
- package/src/server/services/workspace-service.js +3 -3
- 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;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { PATHS, HOME_DIR } = require('../../config/paths');
|
|
4
|
+
const {
|
|
5
|
+
listProjects: idxListProjects,
|
|
6
|
+
listSessions: idxListSessions,
|
|
7
|
+
getRecentSessions: idxGetRecent,
|
|
8
|
+
searchSessions: idxSearch,
|
|
9
|
+
getSessionStatus: idxGetSessionStatus,
|
|
10
|
+
} = require('./session-history-index');
|
|
4
11
|
const { getOmpCommand, getOmpPaths, resolveOmpRuntime } = require('./omp-config');
|
|
5
12
|
|
|
6
13
|
const PROJECT_ORDER_FILE = PATHS.ompProjectOrder;
|
|
@@ -286,18 +293,16 @@ function scanSessionFiles(rootDir = getOmpSessionPaths().sessions) {
|
|
|
286
293
|
return files;
|
|
287
294
|
}
|
|
288
295
|
|
|
289
|
-
function getAllSessions() {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
.filter(Boolean)
|
|
300
|
-
.sort((a, b) => (b.mtimeMs || 0) - (a.mtimeMs || 0));
|
|
296
|
+
async function getAllSessions(options = {}) {
|
|
297
|
+
const projects = await idxListProjects('omp', options);
|
|
298
|
+
const groups = await Promise.all(projects.map(project => idxListSessions('omp', project.name, options)));
|
|
299
|
+
return groups.flat().map(session => normalizeSession({
|
|
300
|
+
...session,
|
|
301
|
+
mtime: new Date(session.mtime).toISOString(),
|
|
302
|
+
mtimeMs: session.mtime,
|
|
303
|
+
directory: session.projectFullPath,
|
|
304
|
+
usage: session.tokens
|
|
305
|
+
}));
|
|
301
306
|
}
|
|
302
307
|
|
|
303
308
|
function getOmpUsageEvents(rootDir = getOmpSessionPaths().sessions) {
|
|
@@ -350,32 +355,20 @@ function loadSessionOrder() {
|
|
|
350
355
|
return safeReadJson(SESSION_ORDER_FILE, {});
|
|
351
356
|
}
|
|
352
357
|
|
|
353
|
-
function getProjects(_options = {}) {
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
mtimeMs: 0,
|
|
367
|
-
source: 'omp'
|
|
368
|
-
};
|
|
369
|
-
existing.sessionCount += 1;
|
|
370
|
-
if ((session.mtimeMs || 0) > (existing.mtimeMs || 0)) {
|
|
371
|
-
existing.latestSession = session.sessionId;
|
|
372
|
-
existing.mtime = session.mtime;
|
|
373
|
-
existing.mtimeMs = session.mtimeMs;
|
|
374
|
-
}
|
|
375
|
-
projectMap.set(key, existing);
|
|
376
|
-
});
|
|
358
|
+
async function getProjects(_options = {}) {
|
|
359
|
+
const idxProjects = await idxListProjects('omp');
|
|
360
|
+
const projects = idxProjects.map(p => ({
|
|
361
|
+
name: p.name,
|
|
362
|
+
path: p.fullPath || p.path || '',
|
|
363
|
+
fullPath: p.fullPath || '',
|
|
364
|
+
displayName: p.displayName || p.name,
|
|
365
|
+
sessionCount: p.sessionCount || 0,
|
|
366
|
+
latestSession: p.latestSession || null,
|
|
367
|
+
mtime: p.lastUsed || null,
|
|
368
|
+
mtimeMs: p.lastUsed ? new Date(p.lastUsed).getTime() : 0,
|
|
369
|
+
source: 'omp'
|
|
370
|
+
}));
|
|
377
371
|
|
|
378
|
-
const projects = Array.from(projectMap.values());
|
|
379
372
|
const order = loadProjectOrder();
|
|
380
373
|
projects.sort((a, b) => {
|
|
381
374
|
const ai = order.indexOf(a.name);
|
|
@@ -390,10 +383,26 @@ function getProjects(_options = {}) {
|
|
|
390
383
|
return projects;
|
|
391
384
|
}
|
|
392
385
|
|
|
393
|
-
function getSessionsByProject(projectName, _options = {}) {
|
|
386
|
+
async function getSessionsByProject(projectName, _options = {}) {
|
|
387
|
+
const idxSessions = await idxListSessions('omp', projectName);
|
|
388
|
+
const sessions = idxSessions.map(s => ({
|
|
389
|
+
sessionId: s.sessionId,
|
|
390
|
+
filePath: s.filePath,
|
|
391
|
+
firstMessage: s.firstMessage,
|
|
392
|
+
gitBranch: s.gitBranch,
|
|
393
|
+
provider: s.provider,
|
|
394
|
+
model: s.model,
|
|
395
|
+
size: s.size,
|
|
396
|
+
mtime: new Date(s.mtime).toISOString(),
|
|
397
|
+
mtimeMs: s.mtime,
|
|
398
|
+
directory: s.projectFullPath,
|
|
399
|
+
cwd: s.projectFullPath,
|
|
400
|
+
messageCount: s.messageCount,
|
|
401
|
+
usage: s.tokens
|
|
402
|
+
}));
|
|
403
|
+
|
|
394
404
|
const orderMap = loadSessionOrder();
|
|
395
405
|
const order = Array.isArray(orderMap[projectName]) ? orderMap[projectName] : [];
|
|
396
|
-
const sessions = getAllSessions().filter(session => session.projectName === projectName);
|
|
397
406
|
sessions.sort((a, b) => {
|
|
398
407
|
const ai = order.indexOf(a.sessionId);
|
|
399
408
|
const bi = order.indexOf(b.sessionId);
|
|
@@ -427,83 +436,71 @@ function normalizeSession(session) {
|
|
|
427
436
|
};
|
|
428
437
|
}
|
|
429
438
|
|
|
430
|
-
function getSessionById(sessionId) {
|
|
431
|
-
|
|
439
|
+
async function getSessionById(sessionId) {
|
|
440
|
+
const status = await idxGetSessionStatus('omp', sessionId);
|
|
441
|
+
if (!status) return null;
|
|
442
|
+
return {
|
|
443
|
+
sessionId: status.sessionId,
|
|
444
|
+
filePath: status.filePath,
|
|
445
|
+
size: status.size,
|
|
446
|
+
mtime: new Date(status.lastModified).toISOString(),
|
|
447
|
+
mtimeMs: status.lastModified,
|
|
448
|
+
source: 'omp',
|
|
449
|
+
firstMessage: null,
|
|
450
|
+
gitBranch: null,
|
|
451
|
+
forkedFrom: null,
|
|
452
|
+
provider: '',
|
|
453
|
+
model: '',
|
|
454
|
+
directory: null,
|
|
455
|
+
messageCount: 0,
|
|
456
|
+
tokens: { input: 0, output: 0, cached: 0, reasoning: 0, total: 0 }
|
|
457
|
+
};
|
|
432
458
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const session = getSessionById(sessionId);
|
|
459
|
+
async function getSessionMessages(sessionId) {
|
|
460
|
+
const session = await getSessionById(sessionId);
|
|
436
461
|
if (!session) {
|
|
437
462
|
throw new Error('Session not found');
|
|
438
463
|
}
|
|
439
464
|
return readJsonLines(session.filePath).map(convertOmpEntry).filter(Boolean);
|
|
440
465
|
}
|
|
441
466
|
|
|
442
|
-
function getRecentSessions(limit = 5) {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
const end = Math.min(text.length, index + needle.length + contextLength);
|
|
461
|
-
matches.push({
|
|
462
|
-
role: message.role || message.type,
|
|
463
|
-
context: text.slice(start, end),
|
|
464
|
-
timestamp: message.timestamp || null
|
|
465
|
-
});
|
|
466
|
-
});
|
|
467
|
-
if (matches.length === 0) return null;
|
|
468
|
-
return {
|
|
469
|
-
...normalizeSession(session),
|
|
470
|
-
projectName: session.projectName,
|
|
471
|
-
projectDisplayName: getDisplayName(session.cwd),
|
|
472
|
-
matchCount: matches.length,
|
|
473
|
-
matches
|
|
474
|
-
};
|
|
475
|
-
})
|
|
476
|
-
.filter(Boolean);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function deleteSession(sessionId) {
|
|
480
|
-
const session = getSessionById(sessionId);
|
|
481
|
-
if (!session) {
|
|
482
|
-
throw new Error('Session not found');
|
|
483
|
-
}
|
|
467
|
+
async function getRecentSessions(limit = 5) {
|
|
468
|
+
const indexed = await idxGetRecent('omp', limit);
|
|
469
|
+
return indexed.map(session => normalizeSession({
|
|
470
|
+
...session,
|
|
471
|
+
mtime: new Date(session.mtime).toISOString(),
|
|
472
|
+
mtimeMs: session.mtime,
|
|
473
|
+
directory: session.projectFullPath,
|
|
474
|
+
usage: session.tokens
|
|
475
|
+
}));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async function searchSessions(keyword, contextLength = 35, projectName = null) {
|
|
479
|
+
return idxSearch('omp', keyword, { contextLength, projectName });
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function deleteSession(sessionId) {
|
|
483
|
+
const session = await getSessionById(sessionId);
|
|
484
|
+
if (!session) throw new Error('Session not found');
|
|
484
485
|
fs.unlinkSync(session.filePath);
|
|
485
486
|
return { success: true };
|
|
486
487
|
}
|
|
487
488
|
|
|
488
|
-
function deleteProject(projectName) {
|
|
489
|
-
const sessions =
|
|
490
|
-
if (sessions.length === 0)
|
|
491
|
-
throw new Error('Project not found');
|
|
492
|
-
}
|
|
489
|
+
async function deleteProject(projectName) {
|
|
490
|
+
const sessions = await getSessionsByProject(projectName);
|
|
491
|
+
if (sessions.length === 0) throw new Error('Project not found');
|
|
493
492
|
sessions.forEach(session => fs.unlinkSync(session.filePath));
|
|
494
493
|
return { success: true, deletedSessions: sessions.length };
|
|
495
494
|
}
|
|
496
495
|
|
|
497
|
-
function forkSession(sessionId) {
|
|
498
|
-
const session = getSessionById(sessionId);
|
|
499
|
-
if (!session)
|
|
500
|
-
|
|
501
|
-
}
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
fs.copyFileSync(session.filePath, targetPath);
|
|
506
|
-
return { success: true, newSessionId: parseOmpSessionId(targetPath), filePath: targetPath };
|
|
496
|
+
async function forkSession(sessionId) {
|
|
497
|
+
const session = await getSessionById(sessionId);
|
|
498
|
+
if (!session) throw new Error('Session not found');
|
|
499
|
+
const newSessionId = crypto.randomUUID();
|
|
500
|
+
const newFile = path.join(path.dirname(session.filePath), `${newSessionId}.jsonl`);
|
|
501
|
+
const content = fs.readFileSync(session.filePath, 'utf8');
|
|
502
|
+
fs.writeFileSync(newFile, content, 'utf8');
|
|
503
|
+
return { success: true, sessionId: newSessionId, filePath: newFile, forkedFrom: sessionId };
|
|
507
504
|
}
|
|
508
505
|
|
|
509
506
|
function saveProjectOrder(order) {
|
|
@@ -516,11 +513,11 @@ function saveSessionOrder(projectName, order) {
|
|
|
516
513
|
safeWriteJson(SESSION_ORDER_FILE, data);
|
|
517
514
|
}
|
|
518
515
|
|
|
519
|
-
function getProjectAndSessionCounts(
|
|
520
|
-
const
|
|
516
|
+
async function getProjectAndSessionCounts(options = {}) {
|
|
517
|
+
const projects = await idxListProjects('omp', options);
|
|
521
518
|
return {
|
|
522
|
-
projectCount:
|
|
523
|
-
sessionCount:
|
|
519
|
+
projectCount: projects.length,
|
|
520
|
+
sessionCount: projects.reduce((sum, project) => sum + (project.sessionCount || 0), 0)
|
|
524
521
|
};
|
|
525
522
|
}
|
|
526
523
|
|
|
@@ -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,
|