codexmate 0.0.42 → 0.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +2 -0
  2. package/README.zh.md +2 -0
  3. package/cli/claude-proxy.js +611 -14
  4. package/cli/update.js +77 -7
  5. package/cli.js +191 -23
  6. package/package.json +2 -2
  7. package/web-ui/app.js +39 -10
  8. package/web-ui/logic.claude.mjs +65 -2
  9. package/web-ui/logic.runtime.mjs +0 -7
  10. package/web-ui/modules/app.methods.agents.mjs +6 -6
  11. package/web-ui/modules/app.methods.claude-config.mjs +65 -49
  12. package/web-ui/modules/app.methods.codex-config.mjs +10 -10
  13. package/web-ui/modules/app.methods.index.mjs +1 -1
  14. package/web-ui/modules/app.methods.install.mjs +129 -1
  15. package/web-ui/modules/app.methods.openclaw-persist.mjs +1 -1
  16. package/web-ui/modules/app.methods.providers.mjs +17 -16
  17. package/web-ui/modules/app.methods.runtime.mjs +27 -21
  18. package/web-ui/modules/app.methods.session-actions.mjs +25 -20
  19. package/web-ui/modules/app.methods.session-timeline.mjs +0 -1
  20. package/web-ui/modules/app.methods.startup-claude.mjs +29 -3
  21. package/web-ui/modules/app.methods.tool-config-permissions.mjs +3 -0
  22. package/web-ui/modules/i18n/locales/en.mjs +65 -1
  23. package/web-ui/modules/i18n/locales/ja.mjs +65 -1
  24. package/web-ui/modules/i18n/locales/vi.mjs +77 -2
  25. package/web-ui/modules/i18n/locales/zh.mjs +66 -2
  26. package/web-ui/partials/index/layout-header.html +24 -0
  27. package/web-ui/partials/index/modals-basic.html +18 -1
  28. package/web-ui/partials/index/panel-config-claude.html +5 -2
  29. package/web-ui/partials/index/panel-config-codex.html +2 -1
  30. package/web-ui/res/web-ui-render.precompiled.js +115 -22
  31. package/web-ui/styles/controls-forms.css +5 -5
  32. package/web-ui/styles/layout-shell.css +145 -0
  33. package/web-ui/styles/responsive.css +12 -0
  34. package/web-ui/styles/titles-cards.css +10 -4
@@ -69,13 +69,21 @@ export function normalizeClaudeConfig(config) {
69
69
  const useKey = normalizeClaudeValue(safe.useKey);
70
70
  const externalCredentialType = normalizeClaudeValue(safe.externalCredentialType)
71
71
  || (apiKey ? '' : (authToken ? 'auth-token' : (useKey ? 'claude-code-use-key' : '')));
72
+ const targetApiRaw = normalizeClaudeValue(safe.targetApi).toLowerCase();
73
+ let targetApi = 'responses';
74
+ if (targetApiRaw === 'chat_completions' || targetApiRaw === 'chat-completions' || targetApiRaw === 'chat/completions') {
75
+ targetApi = 'chat_completions';
76
+ } else if (targetApiRaw === 'ollama') {
77
+ targetApi = 'ollama';
78
+ }
72
79
  return {
73
80
  apiKey,
74
81
  baseUrl: normalizeClaudeValue(safe.baseUrl),
75
82
  model: normalizeClaudeValue(safe.model),
76
83
  authToken,
77
84
  useKey,
78
- externalCredentialType
85
+ externalCredentialType,
86
+ targetApi
79
87
  };
80
88
  }
81
89
 
@@ -102,6 +110,60 @@ function normalizeClaudeComparableUrl(value) {
102
110
  return trimmed.replace(/\/+$/g, '');
103
111
  }
104
112
 
113
+ function isLoopbackClaudeProxyUrl(value) {
114
+ const raw = normalizeClaudeComparableUrl(value);
115
+ if (!raw) return false;
116
+ try {
117
+ const parsed = new URL(raw);
118
+ if (parsed.protocol !== 'http:') return false;
119
+ const host = normalizeClaudeValue(parsed.hostname).toLowerCase();
120
+ return host === '127.0.0.1' || host === 'localhost' || host === '[::1]' || host === '::1';
121
+ } catch (_) {
122
+ return false;
123
+ }
124
+ }
125
+
126
+ export function isLikelyBuiltinClaudeProxySettingsEnv(env = {}) {
127
+ const normalized = normalizeClaudeSettingsEnv(env);
128
+ return !!(
129
+ normalized.baseUrl
130
+ && normalized.model
131
+ && /^[a-f0-9]{48}$/i.test(normalized.apiKey)
132
+ && isLoopbackClaudeProxyUrl(normalized.baseUrl)
133
+ );
134
+ }
135
+
136
+ function isClaudeTransformConfig(config = {}) {
137
+ const targetApi = normalizeClaudeConfig(config).targetApi;
138
+ return targetApi === 'chat_completions' || targetApi === 'ollama';
139
+ }
140
+
141
+ export function matchBuiltinClaudeProxyConfigFromSettings(claudeConfigs = {}, env = {}, preferredName = '') {
142
+ if (!isLikelyBuiltinClaudeProxySettingsEnv(env)) {
143
+ return '';
144
+ }
145
+ const normalizedSettings = normalizeClaudeSettingsEnv(env);
146
+ const preferred = normalizeClaudeValue(preferredName);
147
+ if (preferred && claudeConfigs && claudeConfigs[preferred]) {
148
+ const config = normalizeClaudeConfig(claudeConfigs[preferred]);
149
+ if (isClaudeTransformConfig(config) && config.model === normalizedSettings.model) {
150
+ return preferred;
151
+ }
152
+ }
153
+
154
+ const matches = [];
155
+ for (const [name, config] of Object.entries(claudeConfigs || {})) {
156
+ const normalizedConfig = normalizeClaudeConfig(config);
157
+ if (!isClaudeTransformConfig(normalizedConfig)) {
158
+ continue;
159
+ }
160
+ if (normalizedConfig.model === normalizedSettings.model) {
161
+ matches.push(name);
162
+ }
163
+ }
164
+ return matches.length === 1 ? matches[0] : '';
165
+ }
166
+
105
167
  function hasClaudeCredential(config = {}) {
106
168
  return !!(config.apiKey || config.authToken || config.useKey);
107
169
  }
@@ -156,7 +218,8 @@ export function findDuplicateClaudeConfigName(claudeConfigs = {}, config) {
156
218
  continue;
157
219
  }
158
220
  if (normalizeClaudeComparableUrl(normalizedExisting.baseUrl) !== comparableUrl
159
- || normalizedExisting.model !== normalized.model) {
221
+ || normalizedExisting.model !== normalized.model
222
+ || normalizedExisting.targetApi !== normalized.targetApi) {
160
223
  continue;
161
224
  }
162
225
  if (normalized.apiKey && normalizedExisting.apiKey === normalized.apiKey) {
@@ -101,10 +101,6 @@ export function shouldForceCompactLayoutMode(options = {}) {
101
101
  const screenHeight = Number(options.screenHeight || 0);
102
102
  const shortEdge = Number(options.shortEdge || (screenWidth > 0 && screenHeight > 0 ? Math.min(screenWidth, screenHeight) : 0));
103
103
  const maxTouchPoints = Number(options.maxTouchPoints || 0);
104
- const userAgent = typeof options.userAgent === 'string' ? options.userAgent : '';
105
- const isMobileUa = typeof options.isMobileUa === 'boolean'
106
- ? options.isMobileUa
107
- : /(Android|iPhone|iPad|iPod|Mobile)/i.test(userAgent);
108
104
  const coarsePointer = !!options.coarsePointer;
109
105
  const noHover = !!options.noHover;
110
106
  const isSmallPhysicalScreen = shortEdge > 0 && shortEdge <= 920;
@@ -115,9 +111,6 @@ export function shouldForceCompactLayoutMode(options = {}) {
115
111
  if (!isNarrowViewport) {
116
112
  return false;
117
113
  }
118
- if (isMobileUa) {
119
- return true;
120
- }
121
114
  return pointerSuggestsTouchOnly && maxTouchPoints > 0;
122
115
  }
123
116
 
@@ -60,7 +60,7 @@ export function createAgentsMethods(options = {}) {
60
60
  if (!isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
61
61
  return;
62
62
  }
63
- this.showMessage('加载文件失败', 'error');
63
+ this.showMessage(this.t('toast.load.fail'), 'error');
64
64
  } finally {
65
65
  if (isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
66
66
  this.agentsLoading = false;
@@ -92,7 +92,7 @@ export function createAgentsMethods(options = {}) {
92
92
  if (!isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
93
93
  return;
94
94
  }
95
- this.showMessage('加载文件失败', 'error');
95
+ this.showMessage(this.t('toast.load.fail'), 'error');
96
96
  } finally {
97
97
  if (isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
98
98
  this.agentsLoading = false;
@@ -127,7 +127,7 @@ export function createAgentsMethods(options = {}) {
127
127
  if (!isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
128
128
  return;
129
129
  }
130
- this.showMessage('加载文件失败', 'error');
130
+ this.showMessage(this.t('toast.load.fail'), 'error');
131
131
  } finally {
132
132
  if (isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
133
133
  this.agentsLoading = false;
@@ -171,7 +171,7 @@ export function createAgentsMethods(options = {}) {
171
171
  if (!isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
172
172
  return;
173
173
  }
174
- this.showMessage('加载文件失败', 'error');
174
+ this.showMessage(this.t('toast.load.fail'), 'error');
175
175
  } finally {
176
176
  if (isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
177
177
  this.agentsLoading = false;
@@ -588,7 +588,7 @@ export function createAgentsMethods(options = {}) {
588
588
  }
589
589
  if (!this.agentsDiffVisible) {
590
590
  if (!this.hasAgentsContentChanged()) {
591
- this.showMessage('未检测到改动', 'info');
591
+ this.showMessage(this.t('toast.noChanges'), 'info');
592
592
  return;
593
593
  }
594
594
  await this.prepareAgentsDiff();
@@ -642,7 +642,7 @@ export function createAgentsMethods(options = {}) {
642
642
  this.showMessage(successLabel, 'success');
643
643
  this.closeAgentsModal({ force: true });
644
644
  } catch (e) {
645
- this.showMessage('保存失败', 'error');
645
+ this.showMessage(this.t('toast.save.fail'), 'error');
646
646
  } finally {
647
647
  this.agentsSaving = false;
648
648
  }
@@ -23,6 +23,10 @@ function getClaudeConfigValidationForContext(vm, mode = 'add') {
23
23
  const externalCredentialType = normalizeClaudeText(draft && draft.externalCredentialType);
24
24
  const baseUrl = normalizeClaudeBaseUrl(draft && draft.baseUrl);
25
25
  const model = normalizeClaudeText(draft && draft.model);
26
+ const targetApiRaw = normalizeClaudeText(draft && draft.targetApi).toLowerCase();
27
+ const targetApi = targetApiRaw === 'chat_completions' || targetApiRaw === 'chat-completions' || targetApiRaw === 'chat/completions'
28
+ ? 'chat_completions'
29
+ : (targetApiRaw === 'ollama' ? 'ollama' : 'responses');
26
30
  const errors = {
27
31
  name: '',
28
32
  apiKey: '',
@@ -31,23 +35,23 @@ function getClaudeConfigValidationForContext(vm, mode = 'add') {
31
35
  };
32
36
 
33
37
  if (!name) {
34
- errors.name = '配置名称不能为空';
38
+ errors.name = vm.t('validation.claude.nameRequired');
35
39
  } else if (mode === 'add' && vm.claudeConfigs && vm.claudeConfigs[name]) {
36
- errors.name = '名称已存在';
40
+ errors.name = vm.t('validation.claude.nameExists');
37
41
  }
38
42
 
39
- if (!apiKey && !externalCredentialType) {
40
- errors.apiKey = 'API Key 必填';
43
+ if (!apiKey && !externalCredentialType && targetApi !== 'ollama') {
44
+ errors.apiKey = vm.t('validation.claude.apiKeyRequired');
41
45
  }
42
46
 
43
47
  if (!baseUrl) {
44
- errors.baseUrl = 'Base URL 必填';
48
+ errors.baseUrl = vm.t('validation.claude.baseUrlRequired');
45
49
  } else if (!isValidClaudeHttpUrl(baseUrl)) {
46
- errors.baseUrl = 'Base URL 仅支持 http/https';
50
+ errors.baseUrl = vm.t('validation.claude.baseUrlHttpOnly');
47
51
  }
48
52
 
49
53
  if (!model) {
50
- errors.model = '模型名称必填';
54
+ errors.model = vm.t('validation.claude.modelRequired');
51
55
  }
52
56
 
53
57
  return {
@@ -57,6 +61,7 @@ function getClaudeConfigValidationForContext(vm, mode = 'add') {
57
61
  externalCredentialType,
58
62
  baseUrl,
59
63
  model,
64
+ targetApi,
60
65
  errors,
61
66
  ok: !errors.name && !errors.apiKey && !errors.baseUrl && !errors.model
62
67
  };
@@ -79,7 +84,7 @@ export function createClaudeConfigMethods(options = {}) {
79
84
  }
80
85
  const model = (this.currentClaudeModel || '').trim();
81
86
  if (!model) {
82
- this.showMessage('请输入模型', 'error');
87
+ this.showMessage(this.t('toast.claude.modelRequired'), 'error');
83
88
  return;
84
89
  }
85
90
  const existing = this.claudeConfigs[name] || {};
@@ -88,8 +93,8 @@ export function createClaudeConfigMethods(options = {}) {
88
93
  this.claudeConfigs[name] = this.mergeClaudeConfig(existing, { model });
89
94
  this.saveClaudeConfigs();
90
95
  this.updateClaudeModelsCurrent();
91
- if (!this.claudeConfigs[name].apiKey && !this.claudeConfigs[name].externalCredentialType) {
92
- this.showMessage('请先配置 API Key', 'error');
96
+ if (!this.claudeConfigs[name].apiKey && !this.claudeConfigs[name].externalCredentialType && this.claudeConfigs[name].targetApi !== 'ollama') {
97
+ this.showMessage(this.t('toast.claude.apiKeyRequired'), 'error');
93
98
  return;
94
99
  }
95
100
  this.applyClaudeConfig(name);
@@ -115,8 +120,10 @@ export function createClaudeConfigMethods(options = {}) {
115
120
  this.newClaudeConfig = {
116
121
  name: '',
117
122
  apiKey: config.apiKey || '',
123
+ externalCredentialType: config.externalCredentialType || '',
118
124
  baseUrl: config.baseUrl || '',
119
- model: config.model || ''
125
+ model: config.model || '',
126
+ targetApi: config.targetApi || 'responses'
120
127
  };
121
128
  this.showAddClaudeConfigKey = false;
122
129
  this.showClaudeConfigModal = true;
@@ -144,7 +151,8 @@ export function createClaudeConfigMethods(options = {}) {
144
151
  apiKey: config.apiKey || '',
145
152
  externalCredentialType: config.externalCredentialType || '',
146
153
  baseUrl: config.baseUrl || '',
147
- model: config.model || ''
154
+ model: config.model || '',
155
+ targetApi: config.targetApi || 'responses'
148
156
  };
149
157
  this.showEditClaudeConfigKey = false;
150
158
  this.showEditConfigModal = true;
@@ -153,15 +161,17 @@ export function createClaudeConfigMethods(options = {}) {
153
161
  updateConfig() {
154
162
  const validation = getClaudeConfigValidationForContext(this, 'edit');
155
163
  if (!validation.ok) {
156
- return this.showMessage(validation.errors.name || validation.errors.apiKey || validation.errors.baseUrl || validation.errors.model || '请检查 Claude 配置', 'error');
164
+ return this.showMessage(validation.errors.name || validation.errors.apiKey || validation.errors.baseUrl || validation.errors.model || this.t('toast.claude.checkConfig'), 'error');
157
165
  }
158
166
  const name = validation.name;
159
167
  this.editingConfig.apiKey = validation.apiKey;
168
+ this.editingConfig.externalCredentialType = validation.externalCredentialType;
160
169
  this.editingConfig.baseUrl = validation.baseUrl;
161
170
  this.editingConfig.model = validation.model;
171
+ this.editingConfig.targetApi = validation.targetApi;
162
172
  this.claudeConfigs[name] = this.mergeClaudeConfig(this.claudeConfigs[name], this.editingConfig);
163
173
  this.saveClaudeConfigs();
164
- this.showMessage('操作成功', 'success');
174
+ this.showMessage(this.t('toast.operation.success'), 'success');
165
175
  this.closeEditConfigModal();
166
176
  if (name === this.currentClaudeConfig) {
167
177
  this.refreshClaudeModelContext();
@@ -171,7 +181,7 @@ export function createClaudeConfigMethods(options = {}) {
171
181
  closeEditConfigModal() {
172
182
  this.showEditConfigModal = false;
173
183
  this.showEditClaudeConfigKey = false;
174
- this.editingConfig = { name: '', apiKey: '', externalCredentialType: '', baseUrl: '', model: '' };
184
+ this.editingConfig = { name: '', apiKey: '', externalCredentialType: '', baseUrl: '', model: '', targetApi: 'responses' };
175
185
  },
176
186
 
177
187
  toggleEditClaudeConfigKey() {
@@ -181,18 +191,20 @@ export function createClaudeConfigMethods(options = {}) {
181
191
  async saveAndApplyConfig() {
182
192
  const validation = getClaudeConfigValidationForContext(this, 'edit');
183
193
  if (!validation.ok) {
184
- return this.showMessage(validation.errors.name || validation.errors.apiKey || validation.errors.baseUrl || validation.errors.model || '请检查 Claude 配置', 'error');
194
+ return this.showMessage(validation.errors.name || validation.errors.apiKey || validation.errors.baseUrl || validation.errors.model || this.t('toast.claude.checkConfig'), 'error');
185
195
  }
186
196
  const name = validation.name;
187
197
  this.editingConfig.apiKey = validation.apiKey;
198
+ this.editingConfig.externalCredentialType = validation.externalCredentialType;
188
199
  this.editingConfig.baseUrl = validation.baseUrl;
189
200
  this.editingConfig.model = validation.model;
201
+ this.editingConfig.targetApi = validation.targetApi;
190
202
  this.claudeConfigs[name] = this.mergeClaudeConfig(this.claudeConfigs[name], this.editingConfig);
191
203
  this.saveClaudeConfigs();
192
204
 
193
205
  const config = this.claudeConfigs[name];
194
- if (!config.apiKey) {
195
- this.showMessage('已保存(未填写 API Key)', 'info');
206
+ if (!config.apiKey && config.targetApi !== 'ollama') {
207
+ this.showMessage(this.t('toast.claude.savedWithoutKey'), 'info');
196
208
  this.closeEditConfigModal();
197
209
  if (name === this.currentClaudeConfig) {
198
210
  this.refreshClaudeModelContext();
@@ -200,58 +212,60 @@ export function createClaudeConfigMethods(options = {}) {
200
212
  return;
201
213
  }
202
214
 
203
- const _claudeKey = `${name}|${config.apiKey || ""}|${config.baseUrl || ""}|${config.model || ""}`;
215
+ const _claudeKey = `${name}|${config.apiKey || ""}|${config.baseUrl || ""}|${config.model || ""}|${config.targetApi || "responses"}`;
204
216
  try {
205
- const res = await api('apply-claude-config', { config });
217
+ const res = await api('apply-claude-config', { config: { ...config, name } });
206
218
  if (res.error || res.success === false) {
207
- this.showMessage(res.error || '应用配置失败', 'error');
219
+ this.showMessage(res.error || this.t('toast.apply.fail'), 'error');
208
220
  } else {
209
221
  this.currentClaudeConfig = name;
210
222
  if (this._lastAppliedClaudeKey !== _claudeKey) {
211
- this.showMessage('Claude 配置已生效', 'success');
223
+ this.showMessage(this.t('toast.claude.applied'), 'success');
212
224
  this._lastAppliedClaudeKey = _claudeKey;
213
225
  }
214
226
  this.closeEditConfigModal();
215
227
  this.refreshClaudeModelContext();
216
228
  }
217
229
  } catch (_) {
218
- this.showMessage('应用配置失败', 'error');
230
+ this.showMessage(this.t('toast.apply.fail'), 'error');
219
231
  }
220
232
  },
221
233
 
222
234
  addClaudeConfig() {
223
235
  const validation = getClaudeConfigValidationForContext(this, 'add');
224
236
  if (!validation.ok) {
225
- return this.showMessage(validation.errors.name || validation.errors.apiKey || validation.errors.baseUrl || validation.errors.model || '请检查 Claude 配置', 'error');
237
+ return this.showMessage(validation.errors.name || validation.errors.apiKey || validation.errors.baseUrl || validation.errors.model || this.t('toast.claude.checkConfig'), 'error');
226
238
  }
227
239
  this.newClaudeConfig.name = validation.name;
228
240
  this.newClaudeConfig.apiKey = validation.apiKey;
241
+ this.newClaudeConfig.externalCredentialType = validation.externalCredentialType;
229
242
  this.newClaudeConfig.baseUrl = validation.baseUrl;
230
243
  this.newClaudeConfig.model = validation.model;
244
+ this.newClaudeConfig.targetApi = validation.targetApi;
231
245
  const name = validation.name;
232
246
  const duplicateName = this.findDuplicateClaudeConfigName(this.newClaudeConfig);
233
247
  if (duplicateName) {
234
- return this.showMessage('配置已存在', 'info');
248
+ return this.showMessage(this.t('toast.claude.exists'), 'info');
235
249
  }
236
250
 
237
251
  this.claudeConfigs[name] = this.mergeClaudeConfig({}, this.newClaudeConfig);
238
252
 
239
253
  this.currentClaudeConfig = name;
240
254
  this.saveClaudeConfigs();
241
- this.showMessage('操作成功', 'success');
255
+ this.showMessage(this.t('toast.operation.success'), 'success');
242
256
  this.closeClaudeConfigModal();
243
257
  this.refreshClaudeModelContext();
244
258
  },
245
259
 
246
260
  async deleteClaudeConfig(name) {
247
261
  if (Object.keys(this.claudeConfigs).length <= 1) {
248
- return this.showMessage('至少保留一项', 'error');
262
+ return this.showMessage(this.t('toast.claude.keepOne'), 'error');
249
263
  }
250
264
  const confirmed = await this.requestConfirmDialog({
251
- title: '删除 Claude 配置',
252
- message: `确定删除配置 "${name}"?`,
253
- confirmText: '删除',
254
- cancelText: '取消',
265
+ title: this.t('modal.claudeDelete.title'),
266
+ message: this.t('modal.claudeDelete.message', { name }),
267
+ confirmText: this.t('modal.claudeDelete.confirm'),
268
+ cancelText: this.t('modal.claudeDelete.cancel'),
255
269
  danger: true
256
270
  });
257
271
  if (!confirmed) return;
@@ -261,7 +275,7 @@ export function createClaudeConfigMethods(options = {}) {
261
275
  this.currentClaudeConfig = Object.keys(this.claudeConfigs)[0];
262
276
  }
263
277
  this.saveClaudeConfigs();
264
- this.showMessage('操作成功', 'success');
278
+ this.showMessage(this.t('toast.operation.success'), 'success');
265
279
  this.refreshClaudeModelContext();
266
280
  },
267
281
 
@@ -271,26 +285,26 @@ export function createClaudeConfigMethods(options = {}) {
271
285
  this.refreshClaudeModelContext();
272
286
  const config = this.claudeConfigs[name];
273
287
 
274
- if (!config.apiKey) {
288
+ if (!config.apiKey && config.targetApi !== 'ollama') {
275
289
  if (config.externalCredentialType) {
276
- return this.showMessage('使用外部认证,无需 API Key', 'info');
290
+ return this.showMessage(this.t('toast.claude.externalAuth'), 'info');
277
291
  }
278
- return this.showMessage('请先配置 API Key', 'error');
292
+ return this.showMessage(this.t('toast.claude.apiKeyRequired'), 'error');
279
293
  }
280
294
 
281
- const _claudeKey2 = `${name}|${config.apiKey || ""}|${config.baseUrl || ""}|${config.model || ""}`;
295
+ const _claudeKey2 = `${name}|${config.apiKey || ""}|${config.baseUrl || ""}|${config.model || ""}|${config.targetApi || "responses"}`;
282
296
  try {
283
- const res = await api('apply-claude-config', { config });
297
+ const res = await api('apply-claude-config', { config: { ...config, name } });
284
298
  if (res.error || res.success === false) {
285
- this.showMessage(res.error || '应用配置失败', 'error');
299
+ this.showMessage(res.error || this.t('toast.apply.fail'), 'error');
286
300
  } else {
287
301
  if (this._lastAppliedClaudeKey !== _claudeKey2) {
288
- this.showMessage('配置已应用', 'success');
302
+ this.showMessage(this.t('toast.apply.success'), 'success');
289
303
  this._lastAppliedClaudeKey = _claudeKey2;
290
304
  }
291
305
  }
292
306
  } catch (_) {
293
- this.showMessage('应用配置失败', 'error');
307
+ this.showMessage(this.t('toast.apply.fail'), 'error');
294
308
  }
295
309
  },
296
310
 
@@ -300,8 +314,10 @@ export function createClaudeConfigMethods(options = {}) {
300
314
  this.newClaudeConfig = {
301
315
  name: '',
302
316
  apiKey: '',
317
+ externalCredentialType: '',
303
318
  baseUrl: '',
304
- model: ''
319
+ model: '',
320
+ targetApi: 'responses'
305
321
  };
306
322
  },
307
323
 
@@ -328,12 +344,12 @@ export function createClaudeConfigMethods(options = {}) {
328
344
  return;
329
345
  }
330
346
  if (enable) {
331
- this.showMessage('Claude 本地负载均衡已启用', 'success');
347
+ this.showMessage(this.t('toast.claude.balanceEnabled'), 'success');
332
348
  } else {
333
- this.showMessage('Claude 本地负载均衡已关闭', 'success');
349
+ this.showMessage(this.t('toast.claude.balanceDisabled'), 'success');
334
350
  }
335
351
  } catch (e) {
336
- this.showMessage('操作失败', 'error');
352
+ this.showMessage(this.t('toast.operation.fail'), 'error');
337
353
  }
338
354
  },
339
355
 
@@ -379,18 +395,18 @@ export function createClaudeConfigMethods(options = {}) {
379
395
 
380
396
  const candidates = this.claudeLocalBridgeCandidateProviders();
381
397
  if (candidates.length === 0) {
382
- return this.showMessage('请先添加并配置至少一个 Claude 提供商', 'error');
398
+ return this.showMessage(this.t('toast.claude.balanceRequireProvider'), 'error');
383
399
  }
384
400
 
385
401
  try {
386
402
  const res = await api('claude-local-bridge-toggle', { enable: true });
387
403
  if (res.error) {
388
- this.showMessage(res.error || '启用本地负载均衡失败', 'error');
404
+ this.showMessage(res.error || this.t('toast.claude.balanceEnableFail'), 'error');
389
405
  return;
390
406
  }
391
- this.showMessage('Claude 本地负载均衡已启用', 'success');
407
+ this.showMessage(this.t('toast.claude.balanceEnabled'), 'success');
392
408
  } catch (e) {
393
- this.showMessage('启用本地负载均衡失败', 'error');
409
+ this.showMessage(this.t('toast.claude.balanceEnableFail'), 'error');
394
410
  }
395
411
  },
396
412
 
@@ -405,7 +421,7 @@ export function createClaudeConfigMethods(options = {}) {
405
421
  this.configTemplateContext = 'claude';
406
422
  this.showConfigTemplateModal = true;
407
423
  } catch (e) {
408
- this.showMessage('加载 Claude settings 失败', 'error');
424
+ this.showMessage(this.t('toast.claude.loadSettingsFail'), 'error');
409
425
  }
410
426
  }
411
427
  };
@@ -66,7 +66,7 @@ export function createCodexConfigMethods(options = {}) {
66
66
  const maxLabel = res.maxMessages === 'all' ? 'all' : res.maxMessages;
67
67
  this.showMessage(`会话导出完成(已截断:最多 ${maxLabel} 条消息)`, 'info');
68
68
  } else {
69
- this.showMessage('操作成功', 'success');
69
+ this.showMessage(this.t('toast.operation.success'), 'success');
70
70
  }
71
71
  } catch (e) {
72
72
  this.showMessage('导出失败', 'error');
@@ -325,7 +325,7 @@ export function createCodexConfigMethods(options = {}) {
325
325
  if (hasResponseError(res)) {
326
326
  this.healthCheckResult = null;
327
327
  if (!silent) {
328
- this.showMessage(getResponseMessage(res, '检查失败'), 'error');
328
+ this.showMessage(getResponseMessage(res, this.t('toast.check.fail')), 'error');
329
329
  }
330
330
  return;
331
331
  }
@@ -342,13 +342,13 @@ export function createCodexConfigMethods(options = {}) {
342
342
  this.healthCheckBatchDone = total;
343
343
  this.healthCheckBatchFailed = errors + warns;
344
344
  if (!silent && res.ok) {
345
- this.showMessage('检查通过', 'success');
345
+ this.showMessage(this.t('toast.check.success'), 'success');
346
346
  }
347
347
  return;
348
348
  }
349
349
  this.healthCheckResult = null;
350
350
  if (!silent) {
351
- this.showMessage('检查失败', 'error');
351
+ this.showMessage(this.t('toast.check.fail'), 'error');
352
352
  }
353
353
  return;
354
354
  }
@@ -473,7 +473,7 @@ export function createCodexConfigMethods(options = {}) {
473
473
  } else {
474
474
  this.healthCheckResult = null;
475
475
  if (!silent) {
476
- this.showMessage('检查失败', 'error');
476
+ this.showMessage(this.t('toast.check.fail'), 'error');
477
477
  }
478
478
  }
479
479
  } catch (e) {
@@ -561,7 +561,7 @@ export function createCodexConfigMethods(options = {}) {
561
561
  this.configTemplateContext = 'codex';
562
562
  this.showConfigTemplateModal = true;
563
563
  } catch (e) {
564
- this.showMessage('加载模板失败', 'error');
564
+ this.showMessage(this.t('toast.template.loadFail'), 'error');
565
565
  }
566
566
  },
567
567
 
@@ -797,7 +797,7 @@ export function createCodexConfigMethods(options = {}) {
797
797
  return;
798
798
  }
799
799
  if (!this.configTemplateContent || !this.configTemplateContent.trim()) {
800
- this.showMessage('模板不能为空', 'error');
800
+ this.showMessage(this.t('toast.template.empty'), 'error');
801
801
  return;
802
802
  }
803
803
 
@@ -822,15 +822,15 @@ export function createCodexConfigMethods(options = {}) {
822
822
  this.showMessage(res.error, 'error');
823
823
  return;
824
824
  }
825
- this.showMessage('模板已应用', 'success');
825
+ this.showMessage(this.t('toast.template.applied'), 'success');
826
826
  this.closeConfigTemplateModal({ force: true });
827
827
  try {
828
828
  await this.loadAll();
829
829
  } catch (_) {
830
- this.showMessage('模板已应用,但界面刷新失败,请手动刷新', 'error');
830
+ this.showMessage(this.t('toast.template.appliedButRefreshFail'), 'error');
831
831
  }
832
832
  } catch (e) {
833
- this.showMessage('应用模板失败', 'error');
833
+ this.showMessage(this.t('toast.template.applyFail'), 'error');
834
834
  } finally {
835
835
  this.configTemplateApplying = false;
836
836
  }
@@ -89,7 +89,7 @@ export function createAppMethods() {
89
89
  api,
90
90
  defaultOpenclawTemplate: DEFAULT_OPENCLAW_TEMPLATE
91
91
  }),
92
- ...createInstallMethods(),
92
+ ...createInstallMethods({ api }),
93
93
  ...createRuntimeMethods({ api }),
94
94
  ...createTaskOrchestrationMethods({ api })
95
95
  };