codexmate 0.0.43 → 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.
@@ -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: '',
@@ -36,7 +40,7 @@ function getClaudeConfigValidationForContext(vm, mode = 'add') {
36
40
  errors.name = vm.t('validation.claude.nameExists');
37
41
  }
38
42
 
39
- if (!apiKey && !externalCredentialType) {
43
+ if (!apiKey && !externalCredentialType && targetApi !== 'ollama') {
40
44
  errors.apiKey = vm.t('validation.claude.apiKeyRequired');
41
45
  }
42
46
 
@@ -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
  };
@@ -88,7 +93,7 @@ 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) {
96
+ if (!this.claudeConfigs[name].apiKey && !this.claudeConfigs[name].externalCredentialType && this.claudeConfigs[name].targetApi !== 'ollama') {
92
97
  this.showMessage(this.t('toast.claude.apiKeyRequired'), 'error');
93
98
  return;
94
99
  }
@@ -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;
@@ -157,8 +165,10 @@ export function createClaudeConfigMethods(options = {}) {
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
174
  this.showMessage(this.t('toast.operation.success'), 'success');
@@ -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() {
@@ -185,13 +195,15 @@ export function createClaudeConfigMethods(options = {}) {
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) {
206
+ if (!config.apiKey && config.targetApi !== 'ollama') {
195
207
  this.showMessage(this.t('toast.claude.savedWithoutKey'), 'info');
196
208
  this.closeEditConfigModal();
197
209
  if (name === this.currentClaudeConfig) {
@@ -200,9 +212,9 @@ 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
219
  this.showMessage(res.error || this.t('toast.apply.fail'), 'error');
208
220
  } else {
@@ -226,8 +238,10 @@ export function createClaudeConfigMethods(options = {}) {
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) {
@@ -271,16 +285,16 @@ 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
290
  return this.showMessage(this.t('toast.claude.externalAuth'), 'info');
277
291
  }
278
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
299
  this.showMessage(res.error || this.t('toast.apply.fail'), 'error');
286
300
  } else {
@@ -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
 
@@ -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
  };
@@ -1,4 +1,5 @@
1
- export function createInstallMethods() {
1
+ export function createInstallMethods(options = {}) {
2
+ const { api } = options;
2
3
  return {
3
4
  normalizeInstallPackageManager(value) {
4
5
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
@@ -16,6 +17,133 @@ export function createInstallMethods() {
16
17
  return 'install';
17
18
  },
18
19
 
20
+ normalizePackageVersion(value) {
21
+ const normalized = typeof value === 'string' ? value.trim().replace(/^v/i, '') : '';
22
+ return /^\d+(?:\.\d+){0,2}(?:[-+][0-9A-Za-z.-]+)?$/.test(normalized) ? normalized : '';
23
+ },
24
+
25
+ comparePackageVersions(left, right) {
26
+ const normalizeParts = (value) => {
27
+ const normalized = this.normalizePackageVersion(value);
28
+ if (!normalized) return null;
29
+ return normalized.split(/[+-]/)[0].split('.').map((part) => Number.parseInt(part, 10) || 0);
30
+ };
31
+ const a = normalizeParts(left);
32
+ const b = normalizeParts(right);
33
+ if (!a || !b) return 0;
34
+ for (let i = 0; i < 3; i += 1) {
35
+ const diff = (a[i] || 0) - (b[i] || 0);
36
+ if (diff < 0) return -1;
37
+ if (diff > 0) return 1;
38
+ }
39
+ return 0;
40
+ },
41
+
42
+ isAppUpdateAvailable() {
43
+ const current = this.normalizePackageVersion(this.appVersion);
44
+ const latest = this.normalizePackageVersion(this.appLatestVersion);
45
+ if (!current || !latest) return false;
46
+ return this.comparePackageVersions(current, latest) < 0;
47
+ },
48
+
49
+ isAppVersionStatusVisible() {
50
+ return !!(this.appVersion || this.appLatestVersion || this.appVersionStatusLoading || this.appVersionStatusChecked || this.appVersionStatusError);
51
+ },
52
+
53
+ appVersionStatusKind() {
54
+ if (this.appVersionStatusLoading) return 'loading';
55
+ if (this.appVersionStatusError) return 'error';
56
+ if (this.isAppUpdateAvailable()) return 'available';
57
+ if (this.appVersionStatusChecked) return 'current';
58
+ return 'idle';
59
+ },
60
+
61
+ appUpdateNoticeText() {
62
+ if (this.appVersionStatusLoading) return this.t('side.update.checking');
63
+ if (this.appVersionStatusError) return this.t('side.update.retry');
64
+ const latest = this.normalizePackageVersion(this.appLatestVersion);
65
+ if (this.isAppUpdateAvailable()) return latest
66
+ ? this.t('side.update.availableWithVersion', { version: latest })
67
+ : this.t('side.update.available');
68
+ if (this.appVersionStatusChecked) return this.t('side.update.upToDate');
69
+ return this.t('side.update.check');
70
+ },
71
+
72
+ appUpdateNoticeMeta() {
73
+ if (this.appVersionStatusLoading) return this.t('side.update.checkingMeta');
74
+ if (this.appVersionStatusError) return this.appVersionStatusError;
75
+ const current = this.normalizePackageVersion(this.appVersion);
76
+ const latest = this.normalizePackageVersion(this.appLatestVersion);
77
+ if (current && latest) {
78
+ return this.t('side.update.metaVersions', { current, latest });
79
+ }
80
+ if (current) {
81
+ return this.t('side.update.currentOnly', { current });
82
+ }
83
+ return this.t('side.update.meta');
84
+ },
85
+
86
+ appVersionStatusTitle() {
87
+ const source = typeof this.appVersionStatusSource === 'string' ? this.appVersionStatusSource.trim() : '';
88
+ const checkedAt = typeof this.appVersionStatusCheckedAt === 'string' ? this.appVersionStatusCheckedAt.trim() : '';
89
+ const suffix = [source, checkedAt].filter(Boolean).join(' · ');
90
+ const meta = this.appUpdateNoticeMeta();
91
+ return suffix ? `${meta} · ${suffix}` : meta;
92
+ },
93
+
94
+ handleAppVersionStatusClick() {
95
+ if (this.isAppUpdateAvailable()) {
96
+ this.openAppUpdateDocs();
97
+ return;
98
+ }
99
+ void this.loadAppVersionStatus({ silent: false, force: true });
100
+ },
101
+
102
+ async loadAppVersionStatus(options = {}) {
103
+ if (typeof api !== 'function') return false;
104
+ if (this.appVersionStatusLoading) return false;
105
+ this.appVersionStatusLoading = true;
106
+ this.appVersionStatusError = '';
107
+ try {
108
+ const res = await api('version-status', options.force ? { force: true } : {});
109
+ if (res && res.currentVersion && !this.appVersion) {
110
+ this.appVersion = this.normalizePackageVersion(res.currentVersion) || String(res.currentVersion || '');
111
+ }
112
+ if (res && res.latestVersion) {
113
+ this.appLatestVersion = this.normalizePackageVersion(res.latestVersion) || String(res.latestVersion || '');
114
+ }
115
+ if (res && typeof res.source === 'string') {
116
+ this.appVersionStatusSource = res.source;
117
+ }
118
+ if (res && typeof res.checkedAt === 'string') {
119
+ this.appVersionStatusCheckedAt = res.checkedAt;
120
+ }
121
+ if (res && res.error) {
122
+ this.appVersionStatusError = res.error;
123
+ this.appVersionStatusChecked = true;
124
+ if (!options.silent) this.showMessage(res.error, 'error');
125
+ return false;
126
+ }
127
+ this.appVersionStatusChecked = true;
128
+ return true;
129
+ } catch (e) {
130
+ const message = e && e.message ? e.message : this.t('side.update.checkFailed');
131
+ this.appVersionStatusError = message;
132
+ this.appVersionStatusChecked = true;
133
+ if (!options.silent) this.showMessage(message, 'error');
134
+ return false;
135
+ } finally {
136
+ this.appVersionStatusLoading = false;
137
+ }
138
+ },
139
+
140
+ openAppUpdateDocs() {
141
+ this.installCommandAction = 'update';
142
+ if (typeof this.switchMainTab === 'function') {
143
+ this.switchMainTab('docs');
144
+ }
145
+ },
146
+
19
147
  normalizeInstallRegistryPreset(value) {
20
148
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
21
149
  if (normalized === 'default' || normalized === 'npmmirror' || normalized === 'tencent' || normalized === 'custom') {
@@ -412,11 +412,16 @@ export function createSessionActionMethods(options = {}) {
412
412
  const model = typeof payload.model === 'string' && payload.model.trim()
413
413
  ? payload.model.trim()
414
414
  : 'glm-4.7';
415
- if (!baseUrl || !apiKey) return '';
415
+ const targetApiRaw = typeof payload.targetApi === 'string' ? payload.targetApi.trim().toLowerCase() : '';
416
+ const targetApi = targetApiRaw === 'chat_completions' || targetApiRaw === 'chat-completions' || targetApiRaw === 'chat/completions'
417
+ ? 'chat_completions'
418
+ : (targetApiRaw === 'ollama' ? 'ollama' : 'responses');
419
+ if (!baseUrl || (!apiKey && targetApi !== 'ollama')) return '';
416
420
  const urlArg = this.quoteShellArg(baseUrl);
417
421
  const keyArg = this.quoteShellArg(apiKey);
418
422
  const modelArg = this.quoteShellArg(model);
419
- return `${this.getShareCommandPrefixInvocation()} claude ${urlArg} ${keyArg} ${modelArg}`;
423
+ const targetArg = targetApi !== 'responses' ? ` --target-api ${this.quoteShellArg(targetApi)}` : '';
424
+ return `${this.getShareCommandPrefixInvocation()} claude ${urlArg} ${keyArg} ${modelArg}${targetArg}`;
420
425
  },
421
426
 
422
427
  async copyProviderShareCommand(provider) {
@@ -354,7 +354,6 @@ export function createSessionTimelineMethods() {
354
354
  shortEdge,
355
355
  maxTouchPoints: touchPoints,
356
356
  userAgent,
357
- isMobileUa,
358
357
  coarsePointer,
359
358
  noHover
360
359
  });
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  findDuplicateClaudeConfigName,
3
3
  getClaudeModelCatalogForBaseUrl,
4
+ isLikelyBuiltinClaudeProxySettingsEnv,
5
+ matchBuiltinClaudeProxyConfigFromSettings,
4
6
  matchClaudeConfigFromSettings,
5
7
  normalizeClaudeConfig,
6
8
  normalizeClaudeSettingsEnv,
@@ -241,6 +243,14 @@ export function createStartupClaudeMethods(options = {}) {
241
243
  return matchClaudeConfigFromSettings(this.claudeConfigs, env);
242
244
  },
243
245
 
246
+ matchBuiltinClaudeProxyConfigFromSettings(env) {
247
+ return matchBuiltinClaudeProxyConfigFromSettings(this.claudeConfigs, env, this.currentClaudeConfig);
248
+ },
249
+
250
+ shouldSuppressClaudeSettingsImport(env) {
251
+ return isLikelyBuiltinClaudeProxySettingsEnv(env);
252
+ },
253
+
244
254
  findDuplicateClaudeConfigName(config) {
245
255
  return findDuplicateClaudeConfigName(this.claudeConfigs, config);
246
256
  },
@@ -256,7 +266,8 @@ export function createStartupClaudeMethods(options = {}) {
256
266
  baseUrl: next.baseUrl,
257
267
  model: next.model || previous.model || 'glm-4.7',
258
268
  hasKey: !!(next.apiKey || externalCredentialType),
259
- externalCredentialType
269
+ externalCredentialType,
270
+ targetApi: next.targetApi || previous.targetApi || 'responses'
260
271
  };
261
272
  },
262
273
 
@@ -332,7 +343,8 @@ export function createStartupClaudeMethods(options = {}) {
332
343
  }
333
344
  return;
334
345
  }
335
- const matchName = this.matchClaudeConfigFromSettings((res && res.env) || {});
346
+ const settingsEnv = (res && res.env) || {};
347
+ const matchName = this.matchClaudeConfigFromSettings(settingsEnv);
336
348
  if (matchName) {
337
349
  if (this.currentClaudeConfig !== matchName) {
338
350
  this.currentClaudeConfig = matchName;
@@ -341,7 +353,18 @@ export function createStartupClaudeMethods(options = {}) {
341
353
  this.refreshClaudeModelContext({ silentError: silentModelError });
342
354
  return;
343
355
  }
344
- const importedName = this.ensureClaudeConfigFromSettings((res && res.env) || {});
356
+ const builtinProxyMatch = this.matchBuiltinClaudeProxyConfigFromSettings(settingsEnv);
357
+ if (builtinProxyMatch) {
358
+ if (this.currentClaudeConfig !== builtinProxyMatch) {
359
+ this.currentClaudeConfig = builtinProxyMatch;
360
+ try { localStorage.setItem('currentClaudeConfig', builtinProxyMatch); } catch (_) {}
361
+ }
362
+ this.refreshClaudeModelContext({ silentError: silentModelError });
363
+ return;
364
+ }
365
+ const importedName = this.shouldSuppressClaudeSettingsImport(settingsEnv)
366
+ ? ''
367
+ : this.ensureClaudeConfigFromSettings(settingsEnv);
345
368
  if (importedName) {
346
369
  if (this.currentClaudeConfig !== importedName) {
347
370
  this.currentClaudeConfig = importedName;
@@ -145,6 +145,17 @@ const en = Object.freeze({
145
145
  'side.overview.doctor.meta': 'Overview / Diagnostics',
146
146
  'side.docs.cliInstall': 'CLI Install',
147
147
  'side.docs.cliInstall.meta': 'Install / Update / Uninstall',
148
+ 'side.update.available': 'Update available',
149
+ 'side.update.availableWithVersion': 'Update available v{version}',
150
+ 'side.update.meta': 'Open update command',
151
+ 'side.update.metaVersions': 'Current v{current} → latest v{latest}',
152
+ 'side.update.checkFailed': 'Failed to check latest version',
153
+ 'side.update.check': 'Check for updates',
154
+ 'side.update.checking': 'Checking for updates…',
155
+ 'side.update.checkingMeta': 'Contacting npm registry',
156
+ 'side.update.upToDate': 'Up to date',
157
+ 'side.update.currentOnly': 'Current v{current}',
158
+ 'side.update.retry': 'Retry version check',
148
159
  'side.config.codex': 'Codex',
149
160
  'side.config.codex.meta': 'Provider / Model',
150
161
  'side.config.claude': 'Claude Code',
@@ -1138,6 +1149,13 @@ const en = Object.freeze({
1138
1149
  'claude.model': 'Model',
1139
1150
  'claude.model.placeholder': 'e.g. claude-3-7-sonnet',
1140
1151
  'claude.model.hint': 'Model changes are saved and applied to the current config automatically.',
1152
+ 'claude.targetApi.label': 'Target API',
1153
+ 'claude.targetApi.responses': 'Anthropic',
1154
+ 'claude.targetApi.chatCompletions': 'OpenAI Chat Completions (/v1/chat/completions)',
1155
+ 'claude.targetApi.ollama': 'Ollama Chat (/api/chat)',
1156
+ 'claude.targetApi.chatCompletionsBadge': 'OpenAI Chat Completions',
1157
+ 'claude.targetApi.ollamaBadge': 'Ollama',
1158
+ 'claude.targetApi.hint': 'When Chat Completions or Ollama is selected, the Claude-compatible proxy performs the built-in conversion without changing the Codex provider wire_api; Ollama does not require an API key.',
1141
1159
  'claude.health.title': 'Config health check',
1142
1160
  'claude.health.run': 'Run check',
1143
1161
  'claude.health.running': 'Checking...',
@@ -146,6 +146,17 @@ const ja = Object.freeze({
146
146
  'side.overview.doctor.meta': '概要 / 診断 / ジャンプ',
147
147
  'side.docs.cliInstall': 'CLI インストール',
148
148
  'side.docs.cliInstall.meta': 'インストール / 更新 / 削除',
149
+ 'side.update.available': '新しいバージョンがあります',
150
+ 'side.update.availableWithVersion': '新しいバージョン v{version}',
151
+ 'side.update.meta': '更新コマンドを開く',
152
+ 'side.update.metaVersions': '現在 v{current} → 最新 v{latest}',
153
+ 'side.update.checkFailed': '最新バージョンの確認に失敗しました',
154
+ 'side.update.check': '更新を確認',
155
+ 'side.update.checking': '更新を確認中…',
156
+ 'side.update.checkingMeta': 'npm registry に接続中',
157
+ 'side.update.upToDate': '最新です',
158
+ 'side.update.currentOnly': '現在 v{current}',
159
+ 'side.update.retry': 'バージョン確認を再試行',
149
160
  'side.config.codex': 'Codex',
150
161
  'side.config.codex.meta': 'Provider / Model',
151
162
  'side.config.claude': 'Claude Code',
@@ -1131,6 +1142,13 @@ const ja = Object.freeze({
1131
1142
  'claude.model': 'モデル',
1132
1143
  'claude.model.placeholder': '例: claude-3-7-sonnet',
1133
1144
  'claude.model.hint': 'モデル変更後は自動保存され、現在の設定に適用されます。',
1145
+ 'claude.targetApi.label': 'ターゲット API',
1146
+ 'claude.targetApi.responses': 'Anthropic',
1147
+ 'claude.targetApi.chatCompletions': 'OpenAI Chat Completions (/v1/chat/completions)',
1148
+ 'claude.targetApi.ollama': 'Ollama Chat (/api/chat)',
1149
+ 'claude.targetApi.chatCompletionsBadge': 'OpenAI Chat Completions',
1150
+ 'claude.targetApi.ollamaBadge': 'Ollama',
1151
+ 'claude.targetApi.hint': 'Chat Completions または Ollama を選ぶと Claude 互換プロキシが内蔵変換します。Codex provider の wire_api は変更しません。Ollama では API Key は不要です。',
1134
1152
  'claude.health.title': '設定ヘルスチェック',
1135
1153
  'claude.health.run': 'チェック実行',
1136
1154
  'claude.health.running': 'チェック中...',
@@ -106,6 +106,17 @@ const vi = Object.freeze({
106
106
  'side.overview.doctor.meta': 'Tổng quan / Chẩn đoán',
107
107
  'side.docs.cliInstall': 'Cài CLI',
108
108
  'side.docs.cliInstall.meta': 'Cài đặt / Cập nhật / Gỡ',
109
+ 'side.update.available': 'Có bản cập nhật',
110
+ 'side.update.availableWithVersion': 'Có bản cập nhật v{version}',
111
+ 'side.update.meta': 'Mở lệnh cập nhật',
112
+ 'side.update.metaVersions': 'Hiện tại v{current} → mới nhất v{latest}',
113
+ 'side.update.checkFailed': 'Không thể kiểm tra phiên bản mới nhất',
114
+ 'side.update.check': 'Kiểm tra cập nhật',
115
+ 'side.update.checking': 'Đang kiểm tra cập nhật…',
116
+ 'side.update.checkingMeta': 'Đang kết nối npm registry',
117
+ 'side.update.upToDate': 'Đã là phiên bản mới nhất',
118
+ 'side.update.currentOnly': 'Hiện tại v{current}',
119
+ 'side.update.retry': 'Thử kiểm tra phiên bản lại',
109
120
  'side.config.codex': 'Codex',
110
121
  'side.config.codex.meta': 'Provider / Model',
111
122
  'side.config.claude': 'Claude Code',
@@ -237,6 +248,15 @@ const vi = Object.freeze({
237
248
  'dashboard.doctor.checking': 'Đang kiểm tra...',
238
249
  'dashboard.doctor.export': 'Xuất báo cáo',
239
250
 
251
+ // Claude target API
252
+ 'claude.targetApi.label': 'API đích',
253
+ 'claude.targetApi.responses': 'Anthropic',
254
+ 'claude.targetApi.chatCompletions': 'OpenAI Chat Completions (/v1/chat/completions)',
255
+ 'claude.targetApi.ollama': 'Ollama Chat (/api/chat)',
256
+ 'claude.targetApi.chatCompletionsBadge': 'OpenAI Chat Completions',
257
+ 'claude.targetApi.ollamaBadge': 'Ollama',
258
+ 'claude.targetApi.hint': 'Khi chọn Chat Completions hoặc Ollama, proxy tương thích Claude sẽ chuyển đổi nội bộ mà không thay đổi wire_api của Codex provider; Ollama không cần API key.',
259
+
240
260
  // Toasts
241
261
  'toast.copy.empty': 'Không có gì để sao chép',
242
262
  'toast.copy.ok': 'Đã sao chép',
@@ -145,6 +145,17 @@ const zh = Object.freeze({
145
145
  'side.overview.doctor.meta': '总览 / 诊断 / 跳转',
146
146
  'side.docs.cliInstall': 'CLI 安装',
147
147
  'side.docs.cliInstall.meta': '安装 / 升级 / 卸载',
148
+ 'side.update.available': '有新版本',
149
+ 'side.update.availableWithVersion': '有新版本 v{version}',
150
+ 'side.update.meta': '点击查看更新命令',
151
+ 'side.update.metaVersions': '当前 v{current} → 最新 v{latest}',
152
+ 'side.update.checkFailed': '检查最新版本失败',
153
+ 'side.update.check': '检查更新',
154
+ 'side.update.checking': '正在检查更新…',
155
+ 'side.update.checkingMeta': '正在连接 npm registry',
156
+ 'side.update.upToDate': '已是最新',
157
+ 'side.update.currentOnly': '当前 v{current}',
158
+ 'side.update.retry': '重试版本检查',
148
159
  'side.config.codex': 'Codex',
149
160
  'side.config.codex.meta': 'Provider / Model',
150
161
  'side.config.claude': 'Claude Code',
@@ -1141,6 +1152,13 @@ const zh = Object.freeze({
1141
1152
  'claude.model': '模型',
1142
1153
  'claude.model.placeholder': '例如: claude-3-7-sonnet',
1143
1154
  'claude.model.hint': '模型修改后会自动保存并应用到当前配置。',
1155
+ 'claude.targetApi.label': '目标 API',
1156
+ 'claude.targetApi.responses': 'Anthropic',
1157
+ 'claude.targetApi.chatCompletions': 'OpenAI Chat Completions (/v1/chat/completions)',
1158
+ 'claude.targetApi.ollama': 'Ollama Chat (/api/chat)',
1159
+ 'claude.targetApi.chatCompletionsBadge': 'OpenAI Chat Completions',
1160
+ 'claude.targetApi.ollamaBadge': 'Ollama',
1161
+ 'claude.targetApi.hint': '选择 Chat Completions 或 Ollama 时由 Claude 兼容代理内建转换,不修改 Codex provider 的 wire_api;Ollama 可不填 API Key。',
1144
1162
  'claude.health.title': '配置健康检查',
1145
1163
  'claude.health.run': '运行检查',
1146
1164
  'claude.health.running': '检查中...',
@@ -1,4 +1,16 @@
1
1
  <div id="app" class="container" v-cloak>
2
+ <div v-if="!sessionStandalone" class="mobile-brand-bar">
3
+ <div class="mobile-brand-title">Codex Mate<span v-if="appVersion" class="brand-version"> v{{ appVersion }}</span></div>
4
+ <button
5
+ v-if="isAppVersionStatusVisible()"
6
+ type="button"
7
+ :class="['mobile-update-chip', 'mobile-update-chip--' + appVersionStatusKind()]"
8
+ :title="appVersionStatusTitle()"
9
+ @click="handleAppVersionStatusClick">
10
+ <span class="side-update-dot"></span>
11
+ <span class="mobile-update-text">{{ appUpdateNoticeText() }}</span>
12
+ </button>
13
+ </div>
2
14
  <div v-if="!sessionStandalone" class="top-tabs" role="tablist" :aria-label="t('nav.topTabs.aria')">
3
15
  <button type="button" class="top-tab"
4
16
  id="tab-dashboard"
@@ -110,6 +122,18 @@
110
122
  <div class="brand-kicker">Codex Mate<span v-if="appVersion" class="brand-version"> v{{ appVersion }}</span></div>
111
123
  </div>
112
124
  </div>
125
+ <button
126
+ v-if="isAppVersionStatusVisible()"
127
+ type="button"
128
+ :class="['side-update-notice', 'side-update-notice--' + appVersionStatusKind()]"
129
+ :title="appVersionStatusTitle()"
130
+ @click="handleAppVersionStatusClick">
131
+ <span class="side-update-dot"></span>
132
+ <span class="side-update-copy">
133
+ <span class="side-update-title">{{ appUpdateNoticeText() }}</span>
134
+ <span class="side-update-meta">{{ appUpdateNoticeMeta() }}</span>
135
+ </span>
136
+ </button>
113
137
  </div>
114
138
 
115
139
  <div class="side-rail-nav">
@@ -165,6 +165,15 @@
165
165
  <input v-model="newClaudeConfig.model" :class="['form-input', { invalid: !!claudeConfigFieldError('add', 'model') }]" :placeholder="t('placeholder.modelExample')" autocomplete="off" spellcheck="false">
166
166
  <div v-if="claudeConfigFieldError('add', 'model')" class="form-hint form-error">{{ claudeConfigFieldError('add', 'model') }}</div>
167
167
  </div>
168
+ <div class="form-group">
169
+ <label class="form-label">{{ t('claude.targetApi.label') }}</label>
170
+ <select v-model="newClaudeConfig.targetApi" class="form-input">
171
+ <option value="responses">{{ t('claude.targetApi.responses') }}</option>
172
+ <option value="chat_completions">{{ t('claude.targetApi.chatCompletions') }}</option>
173
+ <option value="ollama">{{ t('claude.targetApi.ollama') }}</option>
174
+ </select>
175
+ <div class="form-hint">{{ t('claude.targetApi.hint') }}</div>
176
+ </div>
168
177
 
169
178
  <div class="btn-group">
170
179
  <button class="btn btn-cancel" @click="closeClaudeConfigModal">{{ t('common.cancel') }}</button>
@@ -204,6 +213,15 @@
204
213
  <input v-model="editingConfig.model" :class="['form-input', { invalid: !!claudeConfigFieldError('edit', 'model') }]" :placeholder="t('placeholder.modelExample')" autocomplete="off" spellcheck="false">
205
214
  <div v-if="claudeConfigFieldError('edit', 'model')" class="form-hint form-error">{{ claudeConfigFieldError('edit', 'model') }}</div>
206
215
  </div>
216
+ <div class="form-group">
217
+ <label class="form-label">{{ t('claude.targetApi.label') }}</label>
218
+ <select v-model="editingConfig.targetApi" class="form-input">
219
+ <option value="responses">{{ t('claude.targetApi.responses') }}</option>
220
+ <option value="chat_completions">{{ t('claude.targetApi.chatCompletions') }}</option>
221
+ <option value="ollama">{{ t('claude.targetApi.ollama') }}</option>
222
+ </select>
223
+ <div class="form-hint">{{ t('claude.targetApi.hint') }}</div>
224
+ </div>
207
225
 
208
226
  <div class="btn-group">
209
227
  <button class="btn btn-cancel" @click="closeEditConfigModal">{{ t('common.cancel') }}</button>
@@ -260,4 +278,3 @@
260
278
  </div>
261
279
  </div>
262
280
  </div>
263
-
@@ -34,6 +34,7 @@
34
34
  <label class="settings-toggle-row tool-config-write-toggle">
35
35
  <input
36
36
  type="checkbox"
37
+ autocomplete="off"
37
38
  :checked="isToolConfigWriteAllowed('claude')"
38
39
  :disabled="toolConfigPermissionSaving.claude"
39
40
  @change="setToolConfigPermission('claude', $event.target.checked)">
@@ -145,10 +146,12 @@
145
146
  </div>
146
147
  <div v-for="(config, name) in claudeConfigs" :key="name" :class="['card', { active: currentClaudeConfig === name }]" @click="applyClaudeConfig(name)" @keydown.enter.self.prevent="applyClaudeConfig(name)" @keydown.space.self.prevent="applyClaudeConfig(name)" tabindex="0" role="button" :aria-current="currentClaudeConfig === name ? 'true' : null">
147
148
  <div class="card-leading">
148
- <div class="card-icon">{{ name.charAt(0).toUpperCase() }}</div>
149
+ <div class="card-icon">{{ name.charAt(0).toUpperCase() }}<span v-if="config.targetApi === 'chat_completions' || config.targetApi === 'ollama'" class="card-icon-dot" :title="t('config.transformProvider.title')"></span></div>
149
150
  <div class="card-content">
150
151
  <div class="card-title">{{ name }}</div>
151
152
  <div class="card-subtitle card-subtitle-model">{{ config.model || t('claude.model.unset') }}</div>
153
+ <div class="card-subtitle" v-if="config.targetApi === 'chat_completions'">{{ t('claude.targetApi.chatCompletionsBadge') }}</div>
154
+ <div class="card-subtitle" v-else-if="config.targetApi === 'ollama'">{{ t('claude.targetApi.ollamaBadge') }}</div>
152
155
  <div class="card-subtitle card-subtitle-url" v-if="config.baseUrl">{{ config.baseUrl }}</div>
153
156
  </div>
154
157
  </div>
@@ -34,6 +34,7 @@
34
34
  <label class="settings-toggle-row tool-config-write-toggle">
35
35
  <input
36
36
  type="checkbox"
37
+ autocomplete="off"
37
38
  :checked="isToolConfigWriteAllowed('codex')"
38
39
  :disabled="toolConfigPermissionSaving.codex"
39
40
  @change="setToolConfigPermission('codex', $event.target.checked)">
@@ -203,4 +204,4 @@
203
204
  </div>
204
205
  </div>
205
206
  </template>
206
- </div>
207
+ </div>