codexmate 0.0.54 → 0.0.56
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/README.md +3 -0
- package/README.vi.md +172 -0
- package/README.zh.md +2 -0
- package/cli/local-bridge.js +221 -22
- package/cli.js +666 -6
- package/package.json +1 -1
- package/web-ui/app.js +40 -0
- package/web-ui/logic.claude.mjs +8 -0
- package/web-ui/modules/app.computed.session.mjs +210 -0
- package/web-ui/modules/app.methods.claude-config.mjs +196 -13
- package/web-ui/modules/app.methods.codex-config.mjs +294 -65
- package/web-ui/modules/app.methods.index.mjs +4 -0
- package/web-ui/modules/app.methods.navigation.mjs +7 -1
- package/web-ui/modules/app.methods.provider-cache.mjs +223 -0
- package/web-ui/modules/app.methods.providers.mjs +15 -1
- package/web-ui/modules/app.methods.runtime.mjs +7 -2
- package/web-ui/modules/app.methods.session-actions.mjs +36 -0
- package/web-ui/modules/app.methods.session-browser.mjs +3 -0
- package/web-ui/modules/app.methods.session-trash.mjs +3 -0
- package/web-ui/modules/app.methods.startup-claude.mjs +47 -4
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +161 -0
- package/web-ui/modules/i18n/locales/en.mjs +106 -3
- package/web-ui/modules/i18n/locales/ja.mjs +103 -3
- package/web-ui/modules/i18n/locales/vi.mjs +950 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +103 -3
- package/web-ui/modules/i18n/locales/zh.mjs +103 -3
- package/web-ui/modules/provider-default-names.mjs +25 -0
- package/web-ui/partials/index/layout-header.html +18 -5
- package/web-ui/partials/index/modal-health-check.html +69 -5
- package/web-ui/partials/index/modals-basic.html +182 -0
- package/web-ui/partials/index/panel-config-codex.html +2 -2
- package/web-ui/partials/index/panel-sessions.html +99 -19
- package/web-ui/partials/index/panel-settings.html +20 -0
- package/web-ui/res/web-ui-render.precompiled.js +742 -113
- package/web-ui/session-helpers.mjs +4 -1
- package/web-ui/styles/layout-shell.css +51 -1
- package/web-ui/styles/responsive.css +98 -0
- package/web-ui/styles/sessions-preview.css +212 -2
- package/web-ui/styles/sessions-toolbar-trash.css +61 -18
- package/web-ui/styles/settings-panel.css +398 -0
- package/web-ui/styles/skills-list.css +122 -0
- package/web-ui/styles/titles-cards.css +52 -0
package/cli.js
CHANGED
|
@@ -218,6 +218,29 @@ const CLAUDE_PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');
|
|
|
218
218
|
const CODEBUDDY_DIR = path.join(os.homedir(), '.codebuddy');
|
|
219
219
|
const CODEBUDDY_PROJECTS_DIR = path.join(CODEBUDDY_DIR, 'projects');
|
|
220
220
|
const CODEXMATE_DIR = path.join(os.homedir(), '.codexmate');
|
|
221
|
+
const PROVIDER_CACHE_FILE_GROUPS = Object.freeze({
|
|
222
|
+
claude: [
|
|
223
|
+
'claude-providers.json'
|
|
224
|
+
],
|
|
225
|
+
codex: [
|
|
226
|
+
'codex-providers.json',
|
|
227
|
+
'codex-provider-current-models.json'
|
|
228
|
+
],
|
|
229
|
+
opencode: [
|
|
230
|
+
'opencode-providers.json',
|
|
231
|
+
'opencode-provider-current-models.json'
|
|
232
|
+
]
|
|
233
|
+
});
|
|
234
|
+
const PROVIDER_CACHE_PROVIDER_FILES = Object.freeze([
|
|
235
|
+
'codex-providers.json',
|
|
236
|
+
'claude-providers.json',
|
|
237
|
+
'opencode-providers.json'
|
|
238
|
+
]);
|
|
239
|
+
const PROVIDER_CACHE_CURRENT_MODEL_FILES = Object.freeze([
|
|
240
|
+
'codex-provider-current-models.json',
|
|
241
|
+
'opencode-provider-current-models.json'
|
|
242
|
+
]);
|
|
243
|
+
const PROVIDER_CACHE_MAX_FILE_BYTES = 256 * 1024;
|
|
221
244
|
const CODEXMATE_PREFERENCES_FILE = path.join(CODEXMATE_DIR, 'preferences.json');
|
|
222
245
|
const CODEXMATE_OPENCODE_DIR = path.join(CODEXMATE_DIR, 'opencode');
|
|
223
246
|
const CODEXMATE_OPENCODE_PROVIDER_STORE_FILE = path.join(CODEXMATE_OPENCODE_DIR, 'providers.json');
|
|
@@ -915,6 +938,102 @@ function writeCodexmatePreferences(preferences) {
|
|
|
915
938
|
writeJsonAtomic(CODEXMATE_PREFERENCES_FILE, isPlainObject(preferences) ? preferences : {});
|
|
916
939
|
}
|
|
917
940
|
|
|
941
|
+
function normalizeShareCommandPrefixPreference(value) {
|
|
942
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
943
|
+
return normalized === 'codexmate' ? 'codexmate' : 'npm start';
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function normalizeBooleanPreference(value, defaultValue = true) {
|
|
947
|
+
if (value === true) return true;
|
|
948
|
+
if (value === false) return false;
|
|
949
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
950
|
+
if (normalized === '1' || normalized === 'true' || normalized === 'on' || normalized === 'yes') return true;
|
|
951
|
+
if (normalized === '0' || normalized === 'false' || normalized === 'off' || normalized === 'no') return false;
|
|
952
|
+
return defaultValue !== false;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function normalizeSessionTrashRetentionPreference(value) {
|
|
956
|
+
const numeric = Number(value);
|
|
957
|
+
if (!Number.isFinite(numeric) || numeric < 1) return 30;
|
|
958
|
+
return Math.min(365, Math.max(1, Math.floor(numeric)));
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function normalizeSessionTimelineStylePreference(value) {
|
|
962
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
963
|
+
return normalized === 'bar' ? 'bar' : 'dots';
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function normalizeSettingsTabPreference(value) {
|
|
967
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
968
|
+
return normalized === 'data' ? 'data' : 'general';
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
function normalizeMainTabPreference(value) {
|
|
972
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
973
|
+
const allowed = new Set(['dashboard', 'config', 'sessions', 'usage', 'orchestration', 'market', 'plugins', 'docs', 'settings', 'trash', 'prompts']);
|
|
974
|
+
return allowed.has(normalized) ? normalized : 'dashboard';
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function normalizeConfigModePreference(value) {
|
|
978
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
979
|
+
return ['codex', 'claude', 'openclaw', 'opencode'].includes(normalized) ? normalized : 'codex';
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function normalizeUsageTimeRangePreference(value) {
|
|
983
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
984
|
+
if (normalized === 'all' || normalized === '30d') return normalized;
|
|
985
|
+
return '7d';
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function normalizePromptsSubTabPreference(value) {
|
|
989
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
990
|
+
return normalized === 'claude-project' ? 'claude-project' : 'codex';
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
function normalizeWebUiPreferences(value = {}) {
|
|
994
|
+
const source = isPlainObject(value) ? value : {};
|
|
995
|
+
const navigation = isPlainObject(source.navigation) ? source.navigation : {};
|
|
996
|
+
return {
|
|
997
|
+
shareCommandPrefix: normalizeShareCommandPrefixPreference(source.shareCommandPrefix),
|
|
998
|
+
sessionTrashEnabled: normalizeBooleanPreference(source.sessionTrashEnabled, true),
|
|
999
|
+
sessionTrashRetentionDays: normalizeSessionTrashRetentionPreference(source.sessionTrashRetentionDays),
|
|
1000
|
+
sessionTimelineStyle: normalizeSessionTimelineStylePreference(source.sessionTimelineStyle),
|
|
1001
|
+
configTemplateDiffConfirmEnabled: normalizeBooleanPreference(source.configTemplateDiffConfirmEnabled, true),
|
|
1002
|
+
sessionsUsageTimeRange: normalizeUsageTimeRangePreference(source.sessionsUsageTimeRange),
|
|
1003
|
+
promptsSubTab: normalizePromptsSubTabPreference(source.promptsSubTab),
|
|
1004
|
+
projectClaudeMdPath: typeof source.projectClaudeMdPath === 'string' ? source.projectClaudeMdPath : '',
|
|
1005
|
+
navigation: {
|
|
1006
|
+
mainTab: normalizeMainTabPreference(navigation.mainTab),
|
|
1007
|
+
configMode: normalizeConfigModePreference(navigation.configMode),
|
|
1008
|
+
settingsTab: normalizeSettingsTabPreference(navigation.settingsTab),
|
|
1009
|
+
skillsTargetApp: navigation.skillsTargetApp === 'claude' ? 'claude' : 'codex',
|
|
1010
|
+
promptTemplatesMode: navigation.promptTemplatesMode === 'manage' ? 'manage' : 'compose'
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function readWebUiPreferences() {
|
|
1016
|
+
const preferences = readCodexmatePreferences();
|
|
1017
|
+
return normalizeWebUiPreferences(preferences.webUi || {});
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function setWebUiPreferences(params = {}) {
|
|
1021
|
+
const preferences = readCodexmatePreferences();
|
|
1022
|
+
const current = isPlainObject(preferences.webUi) ? preferences.webUi : {};
|
|
1023
|
+
const incoming = isPlainObject(params && params.preferences) ? params.preferences : {};
|
|
1024
|
+
const next = normalizeWebUiPreferences({
|
|
1025
|
+
...current,
|
|
1026
|
+
...incoming,
|
|
1027
|
+
navigation: {
|
|
1028
|
+
...(isPlainObject(current.navigation) ? current.navigation : {}),
|
|
1029
|
+
...(isPlainObject(incoming.navigation) ? incoming.navigation : {})
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
preferences.webUi = next;
|
|
1033
|
+
writeCodexmatePreferences(preferences);
|
|
1034
|
+
return { success: true, preferences: next };
|
|
1035
|
+
}
|
|
1036
|
+
|
|
918
1037
|
function readToolConfigPermissions() {
|
|
919
1038
|
const preferences = readCodexmatePreferences();
|
|
920
1039
|
return normalizeToolConfigPermissions(preferences.toolConfigPermissions || TOOL_CONFIG_PERMISSION_DEFAULTS);
|
|
@@ -973,7 +1092,8 @@ function getApiToolConfigWriteTarget(action) {
|
|
|
973
1092
|
'restore-claude-dir',
|
|
974
1093
|
'claude-local-bridge-toggle',
|
|
975
1094
|
'claude-local-bridge-set-excluded',
|
|
976
|
-
'claude-local-bridge-sync-providers'
|
|
1095
|
+
'claude-local-bridge-sync-providers',
|
|
1096
|
+
'delete-provider-cache-record'
|
|
977
1097
|
]);
|
|
978
1098
|
const opencodeWriteActions = new Set([
|
|
979
1099
|
'apply-opencode-config',
|
|
@@ -2506,6 +2626,513 @@ function updateProviderInConfig(params = {}) {
|
|
|
2506
2626
|
}
|
|
2507
2627
|
}
|
|
2508
2628
|
|
|
2629
|
+
|
|
2630
|
+
function redactProviderCacheValue(value) {
|
|
2631
|
+
const secretKeyPattern = /(?:^key$|api[_-]?key|auth[_-]?token|access[_-]?token|refresh[_-]?token|id[_-]?token|token|password|passwd|secret|credential|authorization|bearer|cookie|session|private[_-]?key|client[_-]?secret|x-api-key)/i;
|
|
2632
|
+
const secretQueryPattern = /(?:api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token|token|password|passwd|secret|credential|authorization|client[_-]?secret|key)/i;
|
|
2633
|
+
const secretValuePattern = /^(?:bearer\s+|sk-[A-Za-z0-9]|sk_[A-Za-z0-9]|gsk_|AIza|xox[baprs]-|gh[pousr]_|or-[A-Za-z0-9]|ds-[A-Za-z0-9])/i;
|
|
2634
|
+
const redactSecretString = (text) => String(text || '') ? '***' : '';
|
|
2635
|
+
const redactUrlString = (text) => {
|
|
2636
|
+
if (typeof text !== 'string' || !/^https?:\/\//i.test(text)) return text;
|
|
2637
|
+
try {
|
|
2638
|
+
const parsed = new URL(text);
|
|
2639
|
+
if (parsed.username) parsed.username = '***';
|
|
2640
|
+
if (parsed.password) parsed.password = '***';
|
|
2641
|
+
for (const key of Array.from(parsed.searchParams.keys())) {
|
|
2642
|
+
if (secretQueryPattern.test(key)) parsed.searchParams.set(key, '***');
|
|
2643
|
+
}
|
|
2644
|
+
return parsed.toString();
|
|
2645
|
+
} catch (_) {
|
|
2646
|
+
return text.replace(/([?&](?:api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token|token|password|passwd|secret|credential|authorization|client[_-]?secret|key)=)[^&#]*/gi, '$1***');
|
|
2647
|
+
}
|
|
2648
|
+
};
|
|
2649
|
+
const visit = (input, key = '') => {
|
|
2650
|
+
if (secretKeyPattern.test(key)) {
|
|
2651
|
+
if (typeof input === 'boolean' || typeof input === 'number') return input;
|
|
2652
|
+
if (input === null || input === undefined || input === '') return input === undefined ? null : input;
|
|
2653
|
+
return redactSecretString(input);
|
|
2654
|
+
}
|
|
2655
|
+
if (Array.isArray(input)) {
|
|
2656
|
+
return input.map((item) => visit(item, key));
|
|
2657
|
+
}
|
|
2658
|
+
if (isPlainObject(input)) {
|
|
2659
|
+
const output = {};
|
|
2660
|
+
for (const [childKey, childValue] of Object.entries(input)) {
|
|
2661
|
+
output[childKey] = visit(childValue, childKey);
|
|
2662
|
+
}
|
|
2663
|
+
return output;
|
|
2664
|
+
}
|
|
2665
|
+
if (typeof input === 'string') {
|
|
2666
|
+
const urlRedacted = redactUrlString(input);
|
|
2667
|
+
if (urlRedacted !== input) return urlRedacted;
|
|
2668
|
+
if (secretValuePattern.test(input.trim())) return redactSecretString(input.trim());
|
|
2669
|
+
}
|
|
2670
|
+
return input;
|
|
2671
|
+
};
|
|
2672
|
+
return visit(value);
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
function getProviderCacheDisplayPath(fileName) {
|
|
2676
|
+
return `~/.codexmate/${fileName}`;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
function sanitizeProviderCacheErrorMessage(message, fileName, fallback = '读取缓存文件失败') {
|
|
2680
|
+
const raw = typeof message === 'string' && message.trim() ? message : fallback;
|
|
2681
|
+
const displayPath = getProviderCacheDisplayPath(fileName);
|
|
2682
|
+
const absolutePath = path.join(CODEXMATE_DIR, fileName);
|
|
2683
|
+
return raw
|
|
2684
|
+
.split(absolutePath).join(displayPath)
|
|
2685
|
+
.split(CODEXMATE_DIR).join('~/.codexmate');
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
function pickProviderCacheString(source, keys) {
|
|
2689
|
+
if (!isPlainObject(source)) return '';
|
|
2690
|
+
for (const key of keys) {
|
|
2691
|
+
const value = source[key];
|
|
2692
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
2693
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
2694
|
+
}
|
|
2695
|
+
return '';
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
function summarizeProviderCacheEntry(name, entry = {}) {
|
|
2699
|
+
const provider = isPlainObject(entry) ? entry : {};
|
|
2700
|
+
const providerName = pickProviderCacheString(provider, ['name', 'id', 'provider', 'title']) || String(name || '').trim() || 'provider';
|
|
2701
|
+
const baseUrl = pickProviderCacheString(provider, ['base_url', 'baseUrl', 'url', 'endpoint']);
|
|
2702
|
+
const wireApi = pickProviderCacheString(provider, ['wire_api', 'wireApi', 'api', 'type']);
|
|
2703
|
+
const authMethod = pickProviderCacheString(provider, ['preferred_auth_method', 'authMethod', 'auth_method']);
|
|
2704
|
+
const model = pickProviderCacheString(provider, ['model', 'default_model', 'defaultModel']);
|
|
2705
|
+
return {
|
|
2706
|
+
name: providerName,
|
|
2707
|
+
baseUrl: baseUrl ? redactProviderCacheValue(baseUrl) : '',
|
|
2708
|
+
wireApi,
|
|
2709
|
+
authMethod: authMethod ? redactProviderCacheValue(authMethod) : '',
|
|
2710
|
+
model,
|
|
2711
|
+
data: redactProviderCacheValue(provider)
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
function extractProviderCacheSummaries(data) {
|
|
2716
|
+
const providers = [];
|
|
2717
|
+
const seen = new Set();
|
|
2718
|
+
const addProvider = (name, entry) => {
|
|
2719
|
+
const summary = summarizeProviderCacheEntry(name, entry);
|
|
2720
|
+
const key = `${summary.name}\u0000${summary.baseUrl}\u0000${summary.wireApi}`;
|
|
2721
|
+
if (seen.has(key)) return;
|
|
2722
|
+
seen.add(key);
|
|
2723
|
+
providers.push(summary);
|
|
2724
|
+
};
|
|
2725
|
+
const visitContainer = (container) => {
|
|
2726
|
+
if (!container) return;
|
|
2727
|
+
if (Array.isArray(container)) {
|
|
2728
|
+
for (const item of container) {
|
|
2729
|
+
if (!isPlainObject(item)) continue;
|
|
2730
|
+
addProvider(pickProviderCacheString(item, ['name', 'id', 'provider']) || `provider-${providers.length + 1}`, item);
|
|
2731
|
+
}
|
|
2732
|
+
return;
|
|
2733
|
+
}
|
|
2734
|
+
if (!isPlainObject(container)) return;
|
|
2735
|
+
for (const [name, entry] of Object.entries(container)) {
|
|
2736
|
+
if (isPlainObject(entry)) addProvider(name, entry);
|
|
2737
|
+
}
|
|
2738
|
+
};
|
|
2739
|
+
|
|
2740
|
+
if (isPlainObject(data)) {
|
|
2741
|
+
visitContainer(data.providers);
|
|
2742
|
+
visitContainer(data.configs);
|
|
2743
|
+
visitContainer(data.providerConfigs);
|
|
2744
|
+
visitContainer(data.items);
|
|
2745
|
+
if (providers.length === 0 && (data.base_url || data.baseUrl || data.url || data.endpoint)) {
|
|
2746
|
+
addProvider(pickProviderCacheString(data, ['name', 'id', 'provider']) || 'default', data);
|
|
2747
|
+
}
|
|
2748
|
+
} else if (Array.isArray(data)) {
|
|
2749
|
+
visitContainer(data);
|
|
2750
|
+
}
|
|
2751
|
+
return providers.sort((a, b) => String(a.name || '').localeCompare(String(b.name || '')));
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
function buildProviderCacheFileRecord(fileName) {
|
|
2755
|
+
const filePath = path.join(CODEXMATE_DIR, fileName);
|
|
2756
|
+
const displayPath = getProviderCacheDisplayPath(fileName);
|
|
2757
|
+
const record = {
|
|
2758
|
+
name: fileName,
|
|
2759
|
+
path: displayPath,
|
|
2760
|
+
displayPath,
|
|
2761
|
+
exists: false,
|
|
2762
|
+
ok: true,
|
|
2763
|
+
tooLarge: false,
|
|
2764
|
+
size: 0,
|
|
2765
|
+
mtime: '',
|
|
2766
|
+
data: null,
|
|
2767
|
+
providers: [],
|
|
2768
|
+
providerCount: 0,
|
|
2769
|
+
error: ''
|
|
2770
|
+
};
|
|
2771
|
+
if (!fs.existsSync(filePath)) {
|
|
2772
|
+
return record;
|
|
2773
|
+
}
|
|
2774
|
+
record.exists = true;
|
|
2775
|
+
try {
|
|
2776
|
+
const stat = fs.statSync(filePath);
|
|
2777
|
+
if (!stat.isFile()) {
|
|
2778
|
+
record.ok = false;
|
|
2779
|
+
record.error = '缓存路径不是普通文件,已跳过读取';
|
|
2780
|
+
return record;
|
|
2781
|
+
}
|
|
2782
|
+
record.size = Number(stat.size) || 0;
|
|
2783
|
+
record.mtime = stat.mtime instanceof Date && !Number.isNaN(stat.mtime.getTime())
|
|
2784
|
+
? stat.mtime.toISOString()
|
|
2785
|
+
: '';
|
|
2786
|
+
} catch (e) {
|
|
2787
|
+
record.ok = false;
|
|
2788
|
+
record.error = sanitizeProviderCacheErrorMessage(e && e.message ? e.message : String(e || '读取缓存文件状态失败'), fileName, '读取缓存文件状态失败');
|
|
2789
|
+
return record;
|
|
2790
|
+
}
|
|
2791
|
+
if (record.size > PROVIDER_CACHE_MAX_FILE_BYTES) {
|
|
2792
|
+
record.ok = false;
|
|
2793
|
+
record.tooLarge = true;
|
|
2794
|
+
record.error = `缓存文件过大,已跳过 JSON 读取(${record.size} bytes > ${PROVIDER_CACHE_MAX_FILE_BYTES} bytes)`;
|
|
2795
|
+
return record;
|
|
2796
|
+
}
|
|
2797
|
+
try {
|
|
2798
|
+
const content = stripUtf8Bom(fs.readFileSync(filePath, 'utf-8'));
|
|
2799
|
+
const parsed = content.trim() ? JSON.parse(content) : null;
|
|
2800
|
+
record.data = redactProviderCacheValue(parsed);
|
|
2801
|
+
record.providers = extractProviderCacheSummaries(parsed);
|
|
2802
|
+
record.providerCount = record.providers.length;
|
|
2803
|
+
} catch (e) {
|
|
2804
|
+
record.ok = false;
|
|
2805
|
+
record.error = sanitizeProviderCacheErrorMessage(e && e.message ? e.message : String(e || '读取缓存文件失败'), fileName);
|
|
2806
|
+
}
|
|
2807
|
+
return record;
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
function listProviderCacheFileNamesForGroup(groupKey) {
|
|
2811
|
+
const defaults = PROVIDER_CACHE_FILE_GROUPS[groupKey] || [];
|
|
2812
|
+
const names = new Set(defaults);
|
|
2813
|
+
try {
|
|
2814
|
+
if (fs.existsSync(CODEXMATE_DIR)) {
|
|
2815
|
+
for (const fileName of fs.readdirSync(CODEXMATE_DIR)) {
|
|
2816
|
+
if (typeof fileName !== 'string' || !fileName.endsWith('.json')) continue;
|
|
2817
|
+
if (groupKey === 'opencode' && /^opencode[-_]/i.test(fileName)) {
|
|
2818
|
+
names.add(fileName);
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
} catch (_) {}
|
|
2823
|
+
return Array.from(names).sort((a, b) => a.localeCompare(b));
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
function readProviderCacheRecords() {
|
|
2827
|
+
const groupLabels = {
|
|
2828
|
+
claude: 'Claude',
|
|
2829
|
+
codex: 'Codex',
|
|
2830
|
+
opencode: 'OpenCode'
|
|
2831
|
+
};
|
|
2832
|
+
const groups = Object.keys(PROVIDER_CACHE_FILE_GROUPS).map((key) => {
|
|
2833
|
+
const files = listProviderCacheFileNamesForGroup(key).map((fileName) => buildProviderCacheFileRecord(fileName));
|
|
2834
|
+
return {
|
|
2835
|
+
key,
|
|
2836
|
+
label: groupLabels[key] || key,
|
|
2837
|
+
files,
|
|
2838
|
+
existingCount: files.filter((file) => file && file.exists).length
|
|
2839
|
+
};
|
|
2840
|
+
});
|
|
2841
|
+
return {
|
|
2842
|
+
root: '~/.codexmate',
|
|
2843
|
+
maxFileBytes: PROVIDER_CACHE_MAX_FILE_BYTES,
|
|
2844
|
+
generatedAt: new Date().toISOString(),
|
|
2845
|
+
groups
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
function readProviderCacheJsonObject(fileName) {
|
|
2850
|
+
const filePath = path.join(CODEXMATE_DIR, fileName);
|
|
2851
|
+
try {
|
|
2852
|
+
if (!fs.existsSync(filePath)) return {};
|
|
2853
|
+
const stat = fs.statSync(filePath);
|
|
2854
|
+
if (!stat.isFile()) return {};
|
|
2855
|
+
const parsed = JSON.parse(stripUtf8Bom(fs.readFileSync(filePath, 'utf-8')) || '{}');
|
|
2856
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
2857
|
+
} catch (_) {
|
|
2858
|
+
return {};
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
function writeProviderCacheJsonObject(fileName, data) {
|
|
2863
|
+
ensureDir(CODEXMATE_DIR);
|
|
2864
|
+
const filePath = path.join(CODEXMATE_DIR, fileName);
|
|
2865
|
+
writeJsonAtomic(filePath, isPlainObject(data) ? data : {});
|
|
2866
|
+
try {
|
|
2867
|
+
fs.chmodSync(filePath, 0o600);
|
|
2868
|
+
} catch (_) {}
|
|
2869
|
+
return getProviderCacheDisplayPath(fileName);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
function normalizeProviderCacheProviderMap(rawProviders) {
|
|
2873
|
+
const providers = {};
|
|
2874
|
+
if (Array.isArray(rawProviders)) {
|
|
2875
|
+
for (const item of rawProviders) {
|
|
2876
|
+
if (!isPlainObject(item)) continue;
|
|
2877
|
+
const name = pickProviderCacheString(item, ['name', 'id', 'provider']);
|
|
2878
|
+
if (name) providers[name] = item;
|
|
2879
|
+
}
|
|
2880
|
+
return providers;
|
|
2881
|
+
}
|
|
2882
|
+
if (isPlainObject(rawProviders)) {
|
|
2883
|
+
for (const [name, entry] of Object.entries(rawProviders)) {
|
|
2884
|
+
if (isPlainObject(entry)) providers[name] = entry;
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
return providers;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
function removeProviderFromProviderCacheContainer(rawProviders, providerName) {
|
|
2891
|
+
const targetName = typeof providerName === 'string' ? providerName.trim() : '';
|
|
2892
|
+
if (!targetName) return { value: rawProviders, changed: false };
|
|
2893
|
+
|
|
2894
|
+
const matchesProviderName = (name) => String(name || '').trim() === targetName;
|
|
2895
|
+
if (Array.isArray(rawProviders)) {
|
|
2896
|
+
const filtered = rawProviders.filter((item) => {
|
|
2897
|
+
if (!isPlainObject(item)) return true;
|
|
2898
|
+
const itemName = pickProviderCacheString(item, ['name', 'id', 'provider']);
|
|
2899
|
+
return !matchesProviderName(itemName);
|
|
2900
|
+
});
|
|
2901
|
+
return { value: filtered, changed: filtered.length !== rawProviders.length };
|
|
2902
|
+
}
|
|
2903
|
+
if (!isPlainObject(rawProviders)) return { value: rawProviders, changed: false };
|
|
2904
|
+
|
|
2905
|
+
const next = { ...rawProviders };
|
|
2906
|
+
let changed = false;
|
|
2907
|
+
for (const [name, entry] of Object.entries(rawProviders)) {
|
|
2908
|
+
const entryName = isPlainObject(entry)
|
|
2909
|
+
? pickProviderCacheString(entry, ['name', 'id', 'provider'])
|
|
2910
|
+
: '';
|
|
2911
|
+
if (matchesProviderName(name) || matchesProviderName(entryName)) {
|
|
2912
|
+
delete next[name];
|
|
2913
|
+
changed = true;
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
return { value: next, changed };
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
function resolveProviderCacheDeleteGroups(groups) {
|
|
2920
|
+
const requested = Array.isArray(groups) ? groups : (groups ? [groups] : []);
|
|
2921
|
+
const normalized = requested
|
|
2922
|
+
.map((item) => String(item || '').trim().toLowerCase())
|
|
2923
|
+
.filter((item) => Object.prototype.hasOwnProperty.call(PROVIDER_CACHE_FILE_GROUPS, item));
|
|
2924
|
+
return normalized.length ? Array.from(new Set(normalized)) : Object.keys(PROVIDER_CACHE_FILE_GROUPS);
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
function removeProviderFromProviderCacheRecords(providerName, options = {}) {
|
|
2928
|
+
const targetName = typeof providerName === 'string' ? providerName.trim() : '';
|
|
2929
|
+
const summary = { removed: false, providerFiles: [], currentModelFiles: [] };
|
|
2930
|
+
if (!targetName) return summary;
|
|
2931
|
+
|
|
2932
|
+
const groups = resolveProviderCacheDeleteGroups(options.groups || options.group);
|
|
2933
|
+
const providerFiles = groups
|
|
2934
|
+
.flatMap((group) => PROVIDER_CACHE_FILE_GROUPS[group] || [])
|
|
2935
|
+
.filter((fileName) => PROVIDER_CACHE_PROVIDER_FILES.includes(fileName));
|
|
2936
|
+
const currentModelFiles = groups
|
|
2937
|
+
.flatMap((group) => PROVIDER_CACHE_FILE_GROUPS[group] || [])
|
|
2938
|
+
.filter((fileName) => PROVIDER_CACHE_CURRENT_MODEL_FILES.includes(fileName));
|
|
2939
|
+
|
|
2940
|
+
for (const fileName of Array.from(new Set(providerFiles))) {
|
|
2941
|
+
const existing = readProviderCacheJsonObject(fileName);
|
|
2942
|
+
if (!isPlainObject(existing) || !Object.prototype.hasOwnProperty.call(existing, 'providers')) continue;
|
|
2943
|
+
const removed = removeProviderFromProviderCacheContainer(existing.providers, targetName);
|
|
2944
|
+
if (!removed.changed) continue;
|
|
2945
|
+
writeProviderCacheJsonObject(fileName, {
|
|
2946
|
+
...existing,
|
|
2947
|
+
generatedAt: new Date().toISOString(),
|
|
2948
|
+
providers: removed.value
|
|
2949
|
+
});
|
|
2950
|
+
summary.removed = true;
|
|
2951
|
+
summary.providerFiles.push(fileName);
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
for (const fileName of Array.from(new Set(currentModelFiles))) {
|
|
2955
|
+
const existing = readProviderCacheJsonObject(fileName);
|
|
2956
|
+
if (!isPlainObject(existing) || !Object.prototype.hasOwnProperty.call(existing, targetName)) continue;
|
|
2957
|
+
const next = { ...existing };
|
|
2958
|
+
delete next[targetName];
|
|
2959
|
+
writeProviderCacheJsonObject(fileName, next);
|
|
2960
|
+
summary.removed = true;
|
|
2961
|
+
summary.currentModelFiles.push(fileName);
|
|
2962
|
+
}
|
|
2963
|
+
return summary;
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
function deleteProviderCacheRecord(params = {}) {
|
|
2967
|
+
const name = typeof params.name === 'string' ? params.name.trim() : '';
|
|
2968
|
+
if (!name) return { error: '名称不能为空' };
|
|
2969
|
+
const group = typeof params.group === 'string' ? params.group.trim().toLowerCase() : '';
|
|
2970
|
+
const groups = resolveProviderCacheDeleteGroups(group || params.groups);
|
|
2971
|
+
const summary = removeProviderFromProviderCacheRecords(name, { groups });
|
|
2972
|
+
return {
|
|
2973
|
+
success: true,
|
|
2974
|
+
name,
|
|
2975
|
+
groups,
|
|
2976
|
+
removed: summary.removed,
|
|
2977
|
+
providerFiles: summary.providerFiles,
|
|
2978
|
+
currentModelFiles: summary.currentModelFiles,
|
|
2979
|
+
records: readProviderCacheRecords()
|
|
2980
|
+
};
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
function readClaudeProviderCacheProvider(name) {
|
|
2984
|
+
const targetName = typeof name === 'string' ? name.trim() : '';
|
|
2985
|
+
if (!targetName) return null;
|
|
2986
|
+
const cached = normalizeProviderCacheProviderMap(readProviderCacheJsonObject('claude-providers.json').providers);
|
|
2987
|
+
const entry = cached[targetName];
|
|
2988
|
+
return isPlainObject(entry) ? { name: targetName, ...entry } : null;
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
function readClaudeProviderCacheConfigs() {
|
|
2992
|
+
const cached = normalizeProviderCacheProviderMap(readProviderCacheJsonObject('claude-providers.json').providers);
|
|
2993
|
+
const providers = [];
|
|
2994
|
+
for (const [name, entry] of Object.entries(cached)) {
|
|
2995
|
+
if (!name || !isPlainObject(entry)) continue;
|
|
2996
|
+
const baseUrl = typeof entry.baseUrl === 'string' ? entry.baseUrl.trim() : '';
|
|
2997
|
+
const model = typeof entry.model === 'string' ? entry.model.trim() : '';
|
|
2998
|
+
if (!baseUrl || !model) continue;
|
|
2999
|
+
providers.push({
|
|
3000
|
+
name,
|
|
3001
|
+
baseUrl,
|
|
3002
|
+
model,
|
|
3003
|
+
targetApi: normalizeClaudeTargetApi(entry.targetApi),
|
|
3004
|
+
hasKey: typeof entry.apiKey === 'string' && entry.apiKey.trim().length > 0,
|
|
3005
|
+
providerCacheRef: name,
|
|
3006
|
+
source: 'provider-cache'
|
|
3007
|
+
});
|
|
3008
|
+
}
|
|
3009
|
+
return { providers: providers.sort((a, b) => a.name.localeCompare(b.name)) };
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
function buildProviderCacheSyncProviders() {
|
|
3013
|
+
const configResult = readConfigOrVirtualDefault();
|
|
3014
|
+
if (hasConfigLoadError(configResult)) {
|
|
3015
|
+
return { error: (configResult.error && configResult.error.configPublicReason) || '读取 config.toml 失败' };
|
|
3016
|
+
}
|
|
3017
|
+
const config = configResult.config || {};
|
|
3018
|
+
const providers = isPlainObject(config.model_providers) ? config.model_providers : {};
|
|
3019
|
+
const currentModels = readCurrentModels();
|
|
3020
|
+
const activeProvider = typeof config.model_provider === 'string' ? config.model_provider.trim() : '';
|
|
3021
|
+
const activeModel = typeof config.model === 'string' ? config.model.trim() : '';
|
|
3022
|
+
const syncProviders = [];
|
|
3023
|
+
|
|
3024
|
+
for (const [name, provider] of Object.entries(providers)) {
|
|
3025
|
+
if (!name || !isPlainObject(provider) || isBuiltinManagedProvider(name)) continue;
|
|
3026
|
+
const bridgeType = typeof provider.codexmate_bridge === 'string' ? provider.codexmate_bridge.trim() : '';
|
|
3027
|
+
const isOpenaiBridgeProvider = bridgeType === 'openai'
|
|
3028
|
+
|| (typeof provider.base_url === 'string' && provider.base_url.includes('/bridge/openai/'));
|
|
3029
|
+
let baseUrl = typeof provider.base_url === 'string' ? provider.base_url.trim() : '';
|
|
3030
|
+
let apiKey = typeof provider.preferred_auth_method === 'string' ? provider.preferred_auth_method : '';
|
|
3031
|
+
if (isOpenaiBridgeProvider) {
|
|
3032
|
+
const upstream = resolveOpenaiBridgeUpstream(OPENAI_BRIDGE_SETTINGS_FILE, name);
|
|
3033
|
+
if (upstream && !upstream.error) {
|
|
3034
|
+
baseUrl = upstream.baseUrl || baseUrl;
|
|
3035
|
+
apiKey = upstream.apiKey || apiKey;
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
const wireApi = typeof provider.wire_api === 'string' && provider.wire_api.trim()
|
|
3039
|
+
? provider.wire_api.trim()
|
|
3040
|
+
: 'responses';
|
|
3041
|
+
const model = typeof currentModels[name] === 'string' && currentModels[name].trim()
|
|
3042
|
+
? currentModels[name].trim()
|
|
3043
|
+
: (activeProvider === name ? activeModel : '');
|
|
3044
|
+
syncProviders.push({
|
|
3045
|
+
name,
|
|
3046
|
+
baseUrl,
|
|
3047
|
+
apiKey,
|
|
3048
|
+
wireApi,
|
|
3049
|
+
model,
|
|
3050
|
+
bridge: bridgeType || (isOpenaiBridgeProvider ? 'openai' : '')
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
return { providers: syncProviders.sort((a, b) => a.name.localeCompare(b.name)) };
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
function mergeProviderCacheFile(fileName, nextProviders, buildEntry) {
|
|
3057
|
+
const existing = readProviderCacheJsonObject(fileName);
|
|
3058
|
+
const existingProviders = normalizeProviderCacheProviderMap(existing.providers);
|
|
3059
|
+
const providers = {};
|
|
3060
|
+
for (const provider of nextProviders) {
|
|
3061
|
+
const previous = isPlainObject(providers[provider.name]) ? providers[provider.name] : {};
|
|
3062
|
+
const cachedPrevious = isPlainObject(existingProviders[provider.name]) ? existingProviders[provider.name] : previous;
|
|
3063
|
+
providers[provider.name] = { ...cachedPrevious, ...buildEntry(provider) };
|
|
3064
|
+
}
|
|
3065
|
+
const next = {
|
|
3066
|
+
...existing,
|
|
3067
|
+
version: Number(existing.version) > 0 ? Number(existing.version) : 1,
|
|
3068
|
+
generatedAt: new Date().toISOString(),
|
|
3069
|
+
providers
|
|
3070
|
+
};
|
|
3071
|
+
const displayPath = writeProviderCacheJsonObject(fileName, next);
|
|
3072
|
+
return { path: displayPath, providerCount: Object.keys(providers).length };
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
function mergeProviderCacheCurrentModelsFile(fileName, nextProviders) {
|
|
3076
|
+
const existing = readProviderCacheJsonObject(fileName);
|
|
3077
|
+
const next = {};
|
|
3078
|
+
for (const provider of nextProviders) {
|
|
3079
|
+
if (provider.model) {
|
|
3080
|
+
next[provider.name] = provider.model;
|
|
3081
|
+
} else if (typeof existing[provider.name] === 'string' && existing[provider.name].trim()) {
|
|
3082
|
+
next[provider.name] = existing[provider.name];
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
const displayPath = writeProviderCacheJsonObject(fileName, next);
|
|
3086
|
+
return { path: displayPath, modelCount: Object.keys(next).length };
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
function syncProviderCacheRecords() {
|
|
3090
|
+
const built = buildProviderCacheSyncProviders();
|
|
3091
|
+
if (built.error) return { error: built.error };
|
|
3092
|
+
const providers = built.providers || [];
|
|
3093
|
+
if (providers.length === 0) {
|
|
3094
|
+
return { errorKey: 'modal.providerCache.noSyncableProviders', error: 'No syncable providers' };
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
const writtenFiles = [];
|
|
3098
|
+
writtenFiles.push(mergeProviderCacheFile('codex-providers.json', providers, (provider) => ({
|
|
3099
|
+
name: provider.name,
|
|
3100
|
+
base_url: provider.baseUrl,
|
|
3101
|
+
wire_api: provider.wireApi,
|
|
3102
|
+
preferred_auth_method: provider.apiKey,
|
|
3103
|
+
model: provider.model,
|
|
3104
|
+
...(provider.bridge ? { codexmate_bridge: provider.bridge } : {})
|
|
3105
|
+
})));
|
|
3106
|
+
writtenFiles.push(mergeProviderCacheCurrentModelsFile('codex-provider-current-models.json', providers));
|
|
3107
|
+
writtenFiles.push(mergeProviderCacheFile('claude-providers.json', providers, (provider) => ({
|
|
3108
|
+
name: provider.name,
|
|
3109
|
+
baseUrl: provider.baseUrl,
|
|
3110
|
+
apiKey: provider.apiKey,
|
|
3111
|
+
model: provider.model,
|
|
3112
|
+
targetApi: normalizeClaudeTargetApi(provider.wireApi),
|
|
3113
|
+
...(provider.bridge ? { bridge: provider.bridge } : {})
|
|
3114
|
+
})));
|
|
3115
|
+
writtenFiles.push(mergeProviderCacheFile('opencode-providers.json', providers, (provider) => ({
|
|
3116
|
+
name: provider.name,
|
|
3117
|
+
baseUrl: provider.baseUrl,
|
|
3118
|
+
apiKey: provider.apiKey,
|
|
3119
|
+
model: provider.model,
|
|
3120
|
+
disabled: false,
|
|
3121
|
+
...(provider.bridge ? { bridge: provider.bridge } : {})
|
|
3122
|
+
})));
|
|
3123
|
+
writtenFiles.push(mergeProviderCacheCurrentModelsFile('opencode-provider-current-models.json', providers));
|
|
3124
|
+
|
|
3125
|
+
return {
|
|
3126
|
+
success: true,
|
|
3127
|
+
summary: {
|
|
3128
|
+
providerCount: providers.length,
|
|
3129
|
+
fileCount: writtenFiles.length,
|
|
3130
|
+
writtenFiles
|
|
3131
|
+
},
|
|
3132
|
+
records: readProviderCacheRecords()
|
|
3133
|
+
};
|
|
3134
|
+
}
|
|
3135
|
+
|
|
2509
3136
|
function getProviderKey(params = {}) {
|
|
2510
3137
|
const name = typeof params.name === 'string' ? params.name.trim() : '';
|
|
2511
3138
|
if (!name) return { error: '名称不能为空' };
|
|
@@ -2694,6 +3321,7 @@ function performProviderDeletion(name, options = {}) {
|
|
|
2694
3321
|
|
|
2695
3322
|
writeCurrentModels(currentModels);
|
|
2696
3323
|
writeConfig(updatedContent.trimEnd() + lineEnding);
|
|
3324
|
+
removeProviderFromProviderCacheRecords(name);
|
|
2697
3325
|
|
|
2698
3326
|
return result;
|
|
2699
3327
|
}
|
|
@@ -9492,21 +10120,35 @@ async function applyToClaudeSettings(config = {}) {
|
|
|
9492
10120
|
let proxyStarted = false;
|
|
9493
10121
|
try {
|
|
9494
10122
|
assertToolConfigWriteAllowed('claude');
|
|
9495
|
-
const
|
|
9496
|
-
const
|
|
10123
|
+
const providerCacheRef = typeof config.providerCacheRef === 'string' ? config.providerCacheRef.trim() : '';
|
|
10124
|
+
const cachedProvider = providerCacheRef ? readClaudeProviderCacheProvider(providerCacheRef) : null;
|
|
10125
|
+
if (providerCacheRef && !cachedProvider) {
|
|
10126
|
+
return { success: false, mode: 'provider-cache', error: '缓存中的 Claude provider 不存在,请重新同步' };
|
|
10127
|
+
}
|
|
10128
|
+
const effectiveConfig = cachedProvider
|
|
10129
|
+
? {
|
|
10130
|
+
...config,
|
|
10131
|
+
apiKey: cachedProvider.apiKey || config.apiKey || '',
|
|
10132
|
+
baseUrl: cachedProvider.baseUrl || config.baseUrl || '',
|
|
10133
|
+
model: cachedProvider.model || config.model || '',
|
|
10134
|
+
targetApi: cachedProvider.targetApi || config.targetApi || 'responses'
|
|
10135
|
+
}
|
|
10136
|
+
: config;
|
|
10137
|
+
const apiKey = (effectiveConfig.apiKey || '').trim();
|
|
10138
|
+
const targetApi = normalizeClaudeTargetApi(effectiveConfig.targetApi);
|
|
9497
10139
|
if (!apiKey && targetApi !== 'ollama') {
|
|
9498
10140
|
return { success: false, mode: 'settings-file', error: '请先输入 API Key' };
|
|
9499
10141
|
}
|
|
9500
10142
|
|
|
9501
|
-
const configuredBaseUrl = typeof
|
|
10143
|
+
const configuredBaseUrl = typeof effectiveConfig.baseUrl === 'string' ? effectiveConfig.baseUrl.trim() : '';
|
|
9502
10144
|
const baseUrl = (configuredBaseUrl || (targetApi === 'ollama' ? 'http://127.0.0.1:11434' : 'https://open.bigmodel.cn/api/anthropic')).trim();
|
|
9503
|
-
const model = (
|
|
10145
|
+
const model = (effectiveConfig.model || DEFAULT_CLAUDE_MODEL).trim();
|
|
9504
10146
|
let settingsBaseUrl = baseUrl;
|
|
9505
10147
|
let settingsApiKey = apiKey;
|
|
9506
10148
|
let proxyResult = null;
|
|
9507
10149
|
|
|
9508
10150
|
if (targetApi === 'chat_completions' || targetApi === 'ollama') {
|
|
9509
|
-
const upstreamProviderName = typeof
|
|
10151
|
+
const upstreamProviderName = typeof effectiveConfig.name === 'string' ? effectiveConfig.name.trim() : '';
|
|
9510
10152
|
if (targetApi === 'chat_completions' && !configuredBaseUrl && !upstreamProviderName) {
|
|
9511
10153
|
return {
|
|
9512
10154
|
success: false,
|
|
@@ -11695,6 +12337,12 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
11695
12337
|
case 'get-tool-config-permissions':
|
|
11696
12338
|
result = { permissions: readToolConfigPermissions() };
|
|
11697
12339
|
break;
|
|
12340
|
+
case 'get-web-ui-preferences':
|
|
12341
|
+
result = { preferences: readWebUiPreferences() };
|
|
12342
|
+
break;
|
|
12343
|
+
case 'set-web-ui-preferences':
|
|
12344
|
+
result = setWebUiPreferences(params || {});
|
|
12345
|
+
break;
|
|
11698
12346
|
case 'set-tool-config-permission':
|
|
11699
12347
|
result = setToolConfigPermission(params || {});
|
|
11700
12348
|
break;
|
|
@@ -11839,6 +12487,18 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
11839
12487
|
case 'get-provider-key':
|
|
11840
12488
|
result = getProviderKey(params || {});
|
|
11841
12489
|
break;
|
|
12490
|
+
case 'get-provider-cache-records':
|
|
12491
|
+
result = readProviderCacheRecords();
|
|
12492
|
+
break;
|
|
12493
|
+
case 'get-claude-provider-cache-configs':
|
|
12494
|
+
result = readClaudeProviderCacheConfigs();
|
|
12495
|
+
break;
|
|
12496
|
+
case 'sync-provider-cache-records':
|
|
12497
|
+
result = syncProviderCacheRecords();
|
|
12498
|
+
break;
|
|
12499
|
+
case 'delete-provider-cache-record':
|
|
12500
|
+
result = deleteProviderCacheRecord(params || {});
|
|
12501
|
+
break;
|
|
11842
12502
|
case 'delete-provider':
|
|
11843
12503
|
result = deleteProviderFromConfig(params || {});
|
|
11844
12504
|
break;
|