Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 145 additions & 15 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ const {
readOpenaiBridgeSettings,
resolveOpenaiBridgeUpstream
} = require('./cli/openai-bridge');
const {
createLocalBridgeHttpHandler
} = require('./cli/local-bridge');
const {
createOpenclawConfigController
} = require('./cli/openclaw-config');
Expand Down Expand Up @@ -188,6 +191,7 @@ const INIT_MARK_FILE = path.join(CONFIG_DIR, 'codexmate-init.json');
const BUILTIN_PROXY_SETTINGS_FILE = path.join(CONFIG_DIR, 'codexmate-proxy.json');
const BUILTIN_CLAUDE_PROXY_SETTINGS_FILE = path.join(CONFIG_DIR, 'codexmate-claude-proxy.json');
const OPENAI_BRIDGE_SETTINGS_FILE = path.join(CONFIG_DIR, 'codexmate-openai-bridge.json');
const LOCAL_BRIDGE_SETTINGS_FILE = path.join(CONFIG_DIR, 'codexmate-local-bridge.json');
const CODEX_SESSIONS_DIR = path.join(CONFIG_DIR, 'sessions');
const SESSION_TRASH_DIR = path.join(CONFIG_DIR, 'codexmate-session-trash');
const SESSION_TRASH_FILES_DIR = path.join(SESSION_TRASH_DIR, 'files');
Expand Down Expand Up @@ -268,6 +272,7 @@ const DEFAULT_EXTRACT_SUFFIXES = Object.freeze(['.json']);
const g_taskRunControllers = new Map();
let g_taskQueueProcessor = null;
const BUILTIN_PROXY_PROVIDER_NAME = 'codexmate-proxy';
const BUILTIN_LOCAL_PROVIDER_NAME = 'local';
const DEFAULT_BUILTIN_PROXY_SETTINGS = Object.freeze({
enabled: false,
host: '127.0.0.1',
Expand Down Expand Up @@ -330,6 +335,16 @@ const openaiBridgeHandler = createOpenaiBridgeHttpHandler({
httpsAgent: HTTPS_KEEP_ALIVE_AGENT
});

const localBridgeHandler = createLocalBridgeHttpHandler({
readConfigFn: readConfig,
openaiBridgeFile: OPENAI_BRIDGE_SETTINGS_FILE,
localBridgeSettingsFile: LOCAL_BRIDGE_SETTINGS_FILE,
expectedToken: typeof process.env.CODEXMATE_HTTP_TOKEN === 'string' ? process.env.CODEXMATE_HTTP_TOKEN.trim() : '',
maxBodySize: MAX_API_BODY_SIZE,
httpAgent: HTTP_KEEP_ALIVE_AGENT,
httpsAgent: HTTPS_KEEP_ALIVE_AGENT
});

function resolveWebPort() {
const raw = process.env.CODEXMATE_PORT;
if (!raw) return DEFAULT_WEB_PORT;
Expand Down Expand Up @@ -589,16 +604,17 @@ model_auto_compact_token_limit = ${DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT}
disable_response_storage = true
approval_policy = "never"
sandbox_mode = "danger-full-access"
model_provider = "maxx"
model_provider = "local"
personality = "pragmatic"
web_search = "live"

[model_providers.maxx]
name = "maxx"
base_url = "https://maxx-direct.cloverstd.com"
[model_providers.local]
name = "local"
base_url = "http://127.0.0.1:3737/bridge/local/v1"
wire_api = "responses"
requires_openai_auth = false
preferred_auth_method = "sk-"
requires_openai_auth = true
preferred_auth_method = "codexmate"
codexmate_bridge = "local"
request_max_retries = 4
stream_max_retries = 10
stream_idle_timeout_ms = 300000
Expand All @@ -620,12 +636,16 @@ function isBuiltinProxyProvider(providerName) {
return typeof providerName === 'string' && providerName.trim().toLowerCase() === BUILTIN_PROXY_PROVIDER_NAME.toLowerCase();
}

function isLocalProvider(providerName) {
return typeof providerName === 'string' && providerName.trim().toLowerCase() === BUILTIN_LOCAL_PROVIDER_NAME.toLowerCase();
}

function isReservedProviderNameForCreation(providerName) {
return false;
return isLocalProvider(providerName);
}

function isBuiltinManagedProvider(providerName) {
return isBuiltinProxyProvider(providerName);
return isBuiltinProxyProvider(providerName) || isLocalProvider(providerName);
}

function isNonDeletableProvider(providerName) {
Expand Down Expand Up @@ -1661,6 +1681,7 @@ const {
DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT,
CODEXMATE_MANAGED_MARKER,
BUILTIN_PROXY_PROVIDER_NAME,
BUILTIN_LOCAL_PROVIDER_NAME,
EMPTY_CONFIG_FALLBACK_TEMPLATE
});

Expand Down Expand Up @@ -2059,7 +2080,7 @@ function addProviderToConfig(params = {}) {
return { error: '提供商名称不可用' };
}
if (isBuiltinProxyProvider(name) && !allowManaged) {
return { error: 'codexmate-proxy 为保留名称,不可手动添加' };
return { error: `${"codexmate-proxy"} 为保留名称,不可手动添加` }; // keep literal for codexmate-proxy
}

ensureConfigDir();
Expand Down Expand Up @@ -2159,7 +2180,7 @@ function updateProviderInConfig(params = {}) {
return { error: 'URL 仅支持 http/https' };
}
if (isNonEditableProvider(name) && !allowManaged) {
return { error: 'codexmate-proxy 为保留名称,不可编辑' };
return { error: `${name} 为保留名称,不可编辑` };
}

try {
Expand All @@ -2174,7 +2195,7 @@ function deleteProviderFromConfig(params = {}) {
const name = typeof params.name === 'string' ? params.name.trim() : '';
if (!name) return { error: '名称不能为空' };
if (isNonDeletableProvider(name)) {
return { error: 'codexmate-proxy 为保留名称,不可删除' };
return { error: `${name} 为保留名称,不可删除` };
}
if (!fs.existsSync(CONFIG_FILE)) {
return { error: 'config.toml 不存在' };
Expand Down Expand Up @@ -2202,7 +2223,7 @@ function deleteProviderFromConfig(params = {}) {
function performProviderDeletion(name, options = {}) {
const silent = !!options.silent;
if (isNonDeletableProvider(name)) {
const msg = 'codexmate-proxy 为保留名称,不可删除';
const msg = `${name} 为保留名称,不可删除`;
if (!silent) console.error('错误:', msg);
return { error: msg };
}
Expand Down Expand Up @@ -5423,6 +5444,100 @@ async function ensureBuiltinProxyForCodexDefault(params = {}) {
return { error: '该功能已移除' };
}

function readLocalBridgeSettings() {
const defaults = { enabled: false, lastActiveProvider: '', lastModel: '', excludedProviders: [] };
try {
if (!fs.existsSync(LOCAL_BRIDGE_SETTINGS_FILE)) return defaults;
const raw = JSON.parse(fs.readFileSync(LOCAL_BRIDGE_SETTINGS_FILE, 'utf-8'));
return {
enabled: !!raw.enabled,
lastActiveProvider: typeof raw.lastActiveProvider === 'string' ? raw.lastActiveProvider.trim() : '',
lastModel: typeof raw.lastModel === 'string' ? raw.lastModel.trim() : '',
excludedProviders: Array.isArray(raw.excludedProviders) ? raw.excludedProviders.filter(p => typeof p === 'string') : []
};
} catch (e) {
return defaults;
}
}

function writeLocalBridgeSettings(settings) {
fs.writeFileSync(LOCAL_BRIDGE_SETTINGS_FILE, JSON.stringify(settings, null, 2), 'utf-8');
}

function toggleLocalBridgeProvider(params = {}) {
const enable = !!params.enable;
const settings = readLocalBridgeSettings();
try {
const config = readConfig();
const currentProvider = typeof config.model_provider === 'string' ? config.model_provider.trim() : '';
const currentModel = typeof config.model === 'string' ? config.model.trim() : '';

if (enable) {
if (currentProvider === 'local') return { success: true, enabled: true, notice: '已启用 local 转换' };
settings.lastActiveProvider = currentProvider;
settings.lastModel = currentModel;
settings.enabled = true;
writeLocalBridgeSettings(settings);
let content = fs.readFileSync(CONFIG_FILE, 'utf-8');
content = content.replace(/^(model_provider\s*=\s*)(["']).*?(["'])/m, `$1$2local$3`);
writeConfig(content);
return { success: true, enabled: true, previousProvider: currentProvider };
} else {
if (currentProvider !== 'local') {
settings.enabled = false;
writeLocalBridgeSettings(settings);
return { success: true, enabled: false, notice: 'local 转换未启用' };
}
const restoreProvider = settings.lastActiveProvider || '';
if (!restoreProvider) {
settings.enabled = false;
writeLocalBridgeSettings(settings);
return { success: true, enabled: false, notice: '已关闭 local 转换(无历史 provider 可恢复)' };
}
let content = fs.readFileSync(CONFIG_FILE, 'utf-8');
content = content.replace(/^(model_provider\s*=\s*)(["']).*?(["'])/m, `$1$2${restoreProvider}$3`);
if (settings.lastModel) {
content = content.replace(/^(model\s*=\s*)(["']).*?(["'])/m, `$1$2${settings.lastModel}$3`);
}
writeConfig(content);
settings.enabled = false;
writeLocalBridgeSettings(settings);
return { success: true, enabled: false, restoredProvider: restoreProvider, restoredModel: settings.lastModel };
Comment on lines +5475 to +5505
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Sync auth.json when the active provider changes.

This function flips model_provider, but unlike cmdSwitch() and applyConfigTemplate(), it never refreshes AUTH_FILE. After enabling local, requests can keep using the previous provider credential; after disabling it, they can keep using codexmate. The toggle looks successful in config.toml, but the next request can authenticate against the wrong target.

Suggested fix
         if (enable) {
             if (currentProvider === 'local') return { success: true, enabled: true, notice: '已启用 local 转换' };
             settings.lastActiveProvider = currentProvider;
             settings.lastModel = currentModel;
             settings.enabled = true;
             writeLocalBridgeSettings(settings);
             let content = fs.readFileSync(CONFIG_FILE, 'utf-8');
             content = content.replace(/^(model_provider\s*=\s*)(["']).*?(["'])/m, `$1$2local$3`);
             writeConfig(content);
+            const localProvider = config.model_providers && config.model_providers[BUILTIN_LOCAL_PROVIDER_NAME];
+            updateAuthJson(localProvider && localProvider.preferred_auth_method ? localProvider.preferred_auth_method : '');
             return { success: true, enabled: true, previousProvider: currentProvider };
         } else {
             if (currentProvider !== 'local') {
                 settings.enabled = false;
                 writeLocalBridgeSettings(settings);
                 return { success: true, enabled: false, notice: 'local 转换未启用' };
             }
             const restoreProvider = settings.lastActiveProvider || '';
+            const restoreConfig = config.model_providers && config.model_providers[restoreProvider];
             if (!restoreProvider) {
                 settings.enabled = false;
                 writeLocalBridgeSettings(settings);
                 return { success: true, enabled: false, notice: '已关闭 local 转换(无历史 provider 可恢复)' };
             }
             let content = fs.readFileSync(CONFIG_FILE, 'utf-8');
             content = content.replace(/^(model_provider\s*=\s*)(["']).*?(["'])/m, `$1$2${restoreProvider}$3`);
             if (settings.lastModel) {
                 content = content.replace(/^(model\s*=\s*)(["']).*?(["'])/m, `$1$2${settings.lastModel}$3`);
             }
             writeConfig(content);
+            updateAuthJson(restoreConfig && restoreConfig.preferred_auth_method ? restoreConfig.preferred_auth_method : '');
             settings.enabled = false;
             writeLocalBridgeSettings(settings);
             return { success: true, enabled: false, restoredProvider: restoreProvider, restoredModel: settings.lastModel };
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli.js` around lines 5475 - 5505, The toggle code updates CONFIG_FILE and
local bridge settings but never updates AUTH_FILE, so authentication can remain
tied to the previous provider; modify the enable/disable branches in this block
(the code that reads/writes CONFIG_FILE, uses settings,
writeLocalBridgeSettings, writeConfig) to also refresh/sync auth credentials
whenever the active provider changes—mirror the behavior used by cmdSwitch() and
applyConfigTemplate(): determine the new active provider (local or
restoreProvider), then update AUTH_FILE accordingly (either copy/replace the
provider credentials or call the existing auth-sync helper if one exists) before
returning so requests use the correct credential set.

Comment on lines +5491 to +5505
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against restoring a provider that no longer exists.

lastActiveProvider is persisted state, not a live config reference. If that provider was deleted or replaced while local stayed active, this branch writes an invalid model_provider and returns success, leaving the config pointed at a nonexistent provider.

Suggested fix
             const restoreProvider = settings.lastActiveProvider || '';
-            if (!restoreProvider) {
+            const restoreConfig = config.model_providers && config.model_providers[restoreProvider];
+            if (!restoreProvider || !restoreConfig) {
                 settings.enabled = false;
                 writeLocalBridgeSettings(settings);
-                return { success: true, enabled: false, notice: '已关闭 local 转换(无历史 provider 可恢复)' };
+                return { success: true, enabled: false, notice: '已关闭 local 转换(历史 provider 不存在,未恢复)' };
             }
             let content = fs.readFileSync(CONFIG_FILE, 'utf-8');
             content = content.replace(/^(model_provider\s*=\s*)(["']).*?(["'])/m, `$1$2${restoreProvider}$3`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const restoreProvider = settings.lastActiveProvider || '';
if (!restoreProvider) {
settings.enabled = false;
writeLocalBridgeSettings(settings);
return { success: true, enabled: false, notice: '已关闭 local 转换(无历史 provider 可恢复)' };
}
let content = fs.readFileSync(CONFIG_FILE, 'utf-8');
content = content.replace(/^(model_provider\s*=\s*)(["']).*?(["'])/m, `$1$2${restoreProvider}$3`);
if (settings.lastModel) {
content = content.replace(/^(model\s*=\s*)(["']).*?(["'])/m, `$1$2${settings.lastModel}$3`);
}
writeConfig(content);
settings.enabled = false;
writeLocalBridgeSettings(settings);
return { success: true, enabled: false, restoredProvider: restoreProvider, restoredModel: settings.lastModel };
const restoreProvider = settings.lastActiveProvider || '';
const restoreConfig = config.model_providers && config.model_providers[restoreProvider];
if (!restoreProvider || !restoreConfig) {
settings.enabled = false;
writeLocalBridgeSettings(settings);
return { success: true, enabled: false, notice: '已关闭 local 转换(历史 provider 不存在,未恢复)' };
}
let content = fs.readFileSync(CONFIG_FILE, 'utf-8');
content = content.replace(/^(model_provider\s*=\s*)(["']).*?(["'])/m, `$1$2${restoreProvider}$3`);
if (settings.lastModel) {
content = content.replace(/^(model\s*=\s*)(["']).*?(["'])/m, `$1$2${settings.lastModel}$3`);
}
writeConfig(content);
settings.enabled = false;
writeLocalBridgeSettings(settings);
return { success: true, enabled: false, restoredProvider: restoreProvider, restoredModel: settings.lastModel };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli.js` around lines 5491 - 5505, The code unconditionally writes
settings.lastActiveProvider (restoreProvider) into CONFIG_FILE, which can point
to a deleted provider; before replacing model_provider validate that
restoreProvider exists in the current provider list (e.g., parse CONFIG_FILE or
load the providers registry used by your app) and only perform the
content.replace/writeConfig if the provider is found; if not found, set
settings.enabled = false, call writeLocalBridgeSettings(settings), and return a
failure/notice (or choose a safe default provider) instead of writing an invalid
model_provider; reference restoreProvider, settings.lastActiveProvider,
CONFIG_FILE, model_provider, writeConfig, and writeLocalBridgeSettings when
making the check and early-return.

}
} catch (e) {
return { error: e && e.message ? e.message : '操作失败' };
}
}

function getLocalBridgeStatus() {
const settings = readLocalBridgeSettings();
let currentProvider = '';
try {
const config = readConfig();
currentProvider = typeof config.model_provider === 'string' ? config.model_provider.trim() : '';
} catch (e) { /* ignore */ }
return {
enabled: settings.enabled,
active: currentProvider === 'local',
excludedProviders: settings.excludedProviders,
lastActiveProvider: settings.lastActiveProvider,
lastModel: settings.lastModel
};
}

function setLocalBridgeExcludedProviders(params = {}) {
const names = Array.isArray(params.names) ? params.names.filter(n => typeof n === 'string' && n.trim()) : [];
const settings = readLocalBridgeSettings();
settings.excludedProviders = names;
writeLocalBridgeSettings(settings);
return { success: true, excludedProviders: names };
}

function getLocalBridgeExcludedProviders() {
const settings = readLocalBridgeSettings();
return { excludedProviders: settings.excludedProviders };
}

function removeClaudeSessionIndexEntry(indexPath, sessionFilePath, sessionId) {
if (!indexPath || !fs.existsSync(indexPath)) {
return { removed: false, entry: null };
Expand Down Expand Up @@ -8132,8 +8247,8 @@ function cmdAdd(name, baseUrl, apiKey, silent = false, options = {}) {
throw new Error('提供商名称不可用');
}
if (isBuiltinProxyProvider(providerName)) {
if (!silent) console.error('错误: codexmate-proxy 为保留名称,不可手动添加');
throw new Error('codexmate-proxy 为保留名称,不可手动添加');
if (!silent) console.error(`错误: ${providerName} 为保留名称,不可手动添加`);
throw new Error(`${providerName} 为保留名称,不可手动添加`);
}
if (!isValidHttpUrl(providerBaseUrl)) {
if (!silent) console.error('错误: URL 仅支持 http/https');
Expand Down Expand Up @@ -8229,7 +8344,7 @@ function cmdUpdate(name, baseUrl, apiKey, silent = false, options = {}) {
throw new Error('提供商名称必填');
}
if (isNonEditableProvider(name) && !allowManaged) {
const msg = 'codexmate-proxy 为保留名称,不可编辑';
const msg = `${name} 为保留名称,不可编辑`;
if (!silent) console.error(`错误: ${msg}`);
throw new Error(msg);
}
Expand Down Expand Up @@ -9961,6 +10076,9 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
});
res.end(body, 'utf-8');
};
if (typeof localBridgeHandler === 'function' && localBridgeHandler(req, res)) {
return;
}
if (typeof openaiBridgeHandler === 'function' && openaiBridgeHandler(req, res)) {
return;
}
Expand Down Expand Up @@ -10497,6 +10615,18 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
case 'proxy-apply-provider':
result = applyBuiltinProxyProvider(params || {});
break;
case 'local-bridge-toggle':
result = toggleLocalBridgeProvider(params || {});
break;
case 'local-bridge-status':
result = getLocalBridgeStatus();
break;
case 'local-bridge-set-excluded':
result = setLocalBridgeExcludedProviders(params || {});
break;
case 'local-bridge-get-excluded':
result = getLocalBridgeExcludedProviders();
break;
case 'workflow-list':
result = listWorkflowDefinitions();
break;
Expand Down
42 changes: 30 additions & 12 deletions cli/config-bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function createConfigBootstrapController(deps = {}) {
DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT,
CODEXMATE_MANAGED_MARKER,
BUILTIN_PROXY_PROVIDER_NAME,
BUILTIN_LOCAL_PROVIDER_NAME,
EMPTY_CONFIG_FALLBACK_TEMPLATE
} = deps;

Expand Down Expand Up @@ -58,6 +59,7 @@ function createConfigBootstrapController(deps = {}) {
if (!Array.isArray(DEFAULT_MODELS)) throw new Error('createConfigBootstrapController 缺少 DEFAULT_MODELS');
if (!CODEXMATE_MANAGED_MARKER) throw new Error('createConfigBootstrapController 缺少 CODEXMATE_MANAGED_MARKER');
if (!BUILTIN_PROXY_PROVIDER_NAME) throw new Error('createConfigBootstrapController 缺少 BUILTIN_PROXY_PROVIDER_NAME');
if (!BUILTIN_LOCAL_PROVIDER_NAME) throw new Error('createConfigBootstrapController 缺少 BUILTIN_LOCAL_PROVIDER_NAME');
if (typeof EMPTY_CONFIG_FALLBACK_TEMPLATE !== 'string') throw new Error('createConfigBootstrapController 缺少 EMPTY_CONFIG_FALLBACK_TEMPLATE');

let initNotice = '';
Expand Down Expand Up @@ -118,17 +120,18 @@ function createConfigBootstrapController(deps = {}) {
return `${CODEXMATE_MANAGED_MARKER}
# codexmate-initialized-at: ${initializedAt}

model_provider = "openai"
model_provider = "local"
model = "${defaultModel}"
model_context_window = ${DEFAULT_MODEL_CONTEXT_WINDOW}
model_auto_compact_token_limit = ${DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT}

[model_providers.openai]
name = "openai"
base_url = "https://api.openai.com/v1"
[model_providers.local]
name = "local"
base_url = "http://127.0.0.1:3737/bridge/local/v1"
wire_api = "responses"
requires_openai_auth = false
preferred_auth_method = ""
requires_openai_auth = true
preferred_auth_method = "codexmate"
codexmate_bridge = "local"
request_max_retries = 4
stream_max_retries = 10
stream_idle_timeout_ms = 300000
Expand All @@ -145,9 +148,8 @@ stream_idle_timeout_ms = 300000
const currentProvider = typeof safeConfig.model_provider === 'string' ? safeConfig.model_provider.trim() : '';
const hasRemovedBuiltin = !!(providers && providers[BUILTIN_PROXY_PROVIDER_NAME]);
const currentIsRemovedBuiltin = currentProvider === BUILTIN_PROXY_PROVIDER_NAME;
const currentIsRemovedVirtualLocal = currentProvider === 'local' && !(providers && isPlainObject(providers.local));

if (!hasRemovedBuiltin && !currentIsRemovedBuiltin && !currentIsRemovedVirtualLocal) {
if (!hasRemovedBuiltin && !currentIsRemovedBuiltin) {
return safeConfig;
}

Expand All @@ -163,11 +165,26 @@ stream_idle_timeout_ms = 300000
return {
...safeConfig,
model_providers: nextProviders,
model_provider: (currentIsRemovedBuiltin || currentIsRemovedVirtualLocal) ? fallbackProvider : safeConfig.model_provider,
model: (currentIsRemovedBuiltin || currentIsRemovedVirtualLocal) ? fallbackModel : safeConfig.model
model_provider: currentIsRemovedBuiltin ? fallbackProvider : safeConfig.model_provider,
model: currentIsRemovedBuiltin ? fallbackModel : safeConfig.model
};
}

function ensureLocalProviderSection() {
if (!fs.existsSync(CONFIG_FILE)) return;
let content;
try {
content = fs.readFileSync(CONFIG_FILE, 'utf-8');
} catch (e) {
return;
}
// Check if [model_providers.local] section already exists
if (/\[model_providers\.local\]/.test(content)) return;

const localSection = `\n[model_providers.local]\nname = "local"\nbase_url = "http://127.0.0.1:3737/bridge/local/v1"\nwire_api = "responses"\nrequires_openai_auth = true\npreferred_auth_method = "codexmate"\ncodexmate_bridge = "local"\nrequest_max_retries = 4\nstream_max_retries = 10\nstream_idle_timeout_ms = 300000\n`;
fs.appendFileSync(CONFIG_FILE, localSection, 'utf-8');
}

function readConfigOrVirtualDefault() {
if (fs.existsSync(CONFIG_FILE)) {
try {
Expand Down Expand Up @@ -260,7 +277,7 @@ stream_idle_timeout_ms = 300000
ensureConfigDir();

const initializedAt = new Date().toISOString();
const defaultProvider = 'openai';
const defaultProvider = 'local';
const defaultModel = DEFAULT_MODELS[0] || 'gpt-4';
const forceResetExistingConfig = process.env.CODEXMATE_FORCE_RESET_EXISTING_CONFIG === '1';
const mark = readJsonFile(INIT_MARK_FILE, null);
Expand All @@ -273,6 +290,7 @@ stream_idle_timeout_ms = 300000
initNotice = '检测到配置缺失,已自动重建默认配置。';
return { notice: initNotice };
}
ensureLocalProviderSection();
ensureSupportFiles(defaultProvider, defaultModel);
return { notice: '' };
}
Expand Down Expand Up @@ -338,7 +356,7 @@ stream_idle_timeout_ms = 300000
function resetConfigToDefault() {
ensureConfigDir();
const initializedAt = new Date().toISOString();
const defaultProvider = 'openai';
const defaultProvider = 'local';
const defaultModel = DEFAULT_MODELS[0] || 'gpt-4';

let backupFile = '';
Expand Down
Loading
Loading