codexmate 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/openai-bridge-retry.js +17 -9
- package/cli/openai-bridge-runtime.js +14 -1
- package/cli/openai-bridge.js +20 -10
- package/package.json +1 -1
- package/web-ui/app.js +2 -2
- package/web-ui/modules/app.methods.agents.mjs +2 -2
- package/web-ui/modules/app.methods.openclaw-core.mjs +87 -0
- package/web-ui/modules/app.methods.providers.mjs +5 -32
- package/web-ui/modules/i18n/locales/en.mjs +0 -2
- package/web-ui/modules/i18n/locales/ja.mjs +0 -2
- package/web-ui/modules/i18n/locales/vi.mjs +0 -2
- package/web-ui/modules/i18n/locales/zh-tw.mjs +0 -2
- package/web-ui/modules/i18n/locales/zh.mjs +4 -6
- package/web-ui/partials/index/modal-config-template-agents.html +8 -4
- package/web-ui/partials/index/modals-basic.html +0 -26
- package/web-ui/partials/index/panel-config-openclaw.html +42 -14
- package/web-ui/res/web-ui-render.precompiled.js +72 -90
- package/web-ui/styles/layout-shell.css +1 -0
- package/web-ui/styles/modals-core.css +79 -0
- package/web-ui/styles/openclaw-structured.css +117 -71
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
const DEFAULT_BRIDGE_MAX_RETRIES =
|
|
2
|
-
const MIN_BRIDGE_MAX_RETRIES = 2;
|
|
3
|
-
const MAX_BRIDGE_MAX_RETRIES = 10;
|
|
1
|
+
const DEFAULT_BRIDGE_MAX_RETRIES = Infinity;
|
|
4
2
|
const BASE_TRANSIENT_RETRY_DELAY_MS = 200;
|
|
5
3
|
const MAX_TRANSIENT_RETRY_DELAY_MS = 5000;
|
|
6
4
|
|
|
7
5
|
function normalizeBridgeMaxRetries(value, fallback = DEFAULT_BRIDGE_MAX_RETRIES) {
|
|
8
6
|
const raw = Number(value);
|
|
9
7
|
const fallbackRaw = Number(fallback);
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
if (Number.isFinite(raw) && raw >= 0) return Math.floor(raw);
|
|
9
|
+
if (Number.isFinite(fallbackRaw) && fallbackRaw >= 0) return Math.floor(fallbackRaw);
|
|
10
|
+
return DEFAULT_BRIDGE_MAX_RETRIES;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
function isTransientHttpStatus(status) {
|
|
15
|
+
const code = Number(status);
|
|
16
|
+
if (code === 408 || code === 409 || code === 425 || code === 429) return true;
|
|
17
|
+
return code >= 500 && code <= 599;
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
function isTransientNetworkError(error) {
|
|
@@ -30,7 +36,7 @@ function getTransientRetryDelayMs(attempt) {
|
|
|
30
36
|
async function retryTransientRequest(executor, options = {}) {
|
|
31
37
|
const maxRetries = normalizeBridgeMaxRetries(options && options.maxRetries);
|
|
32
38
|
let lastResult = null;
|
|
33
|
-
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
39
|
+
for (let attempt = 0; attempt === 0 || attempt <= maxRetries; attempt += 1) {
|
|
34
40
|
if (attempt > 0) {
|
|
35
41
|
const delay = getTransientRetryDelayMs(attempt);
|
|
36
42
|
// eslint-disable-next-line no-await-in-loop
|
|
@@ -43,9 +49,12 @@ async function retryTransientRequest(executor, options = {}) {
|
|
|
43
49
|
const result = await executor(attempt);
|
|
44
50
|
lastResult = result;
|
|
45
51
|
if (!result) return result;
|
|
52
|
+
if (result.status && result.status > 0) {
|
|
53
|
+
if (!isTransientHttpStatus(result.status)) return result;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
46
56
|
if (result.ok) return result;
|
|
47
57
|
if (result.retry) return result;
|
|
48
|
-
if (result.status && result.status > 0) return result;
|
|
49
58
|
if (!isTransientNetworkError(result.error)) return result;
|
|
50
59
|
}
|
|
51
60
|
return lastResult;
|
|
@@ -53,10 +62,9 @@ async function retryTransientRequest(executor, options = {}) {
|
|
|
53
62
|
|
|
54
63
|
module.exports = {
|
|
55
64
|
DEFAULT_BRIDGE_MAX_RETRIES,
|
|
56
|
-
MIN_BRIDGE_MAX_RETRIES,
|
|
57
|
-
MAX_BRIDGE_MAX_RETRIES,
|
|
58
65
|
normalizeBridgeMaxRetries,
|
|
59
66
|
isTransientNetworkError,
|
|
67
|
+
isTransientHttpStatus,
|
|
60
68
|
getTransientRetryDelayMs,
|
|
61
69
|
retryTransientRequest
|
|
62
70
|
};
|
|
@@ -1472,7 +1472,20 @@ function streamChatCompletionsAsResponsesSse(targetUrl, options = {}) {
|
|
|
1472
1472
|
response: {
|
|
1473
1473
|
id: state.responseId,
|
|
1474
1474
|
model: state.model,
|
|
1475
|
-
created_at: state.createdAt
|
|
1475
|
+
created_at: state.createdAt,
|
|
1476
|
+
status: 'in_progress',
|
|
1477
|
+
output: []
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
1480
|
+
writeSse(res, 'response.in_progress', {
|
|
1481
|
+
type: 'response.in_progress',
|
|
1482
|
+
sequence_number: state.nextSeq(),
|
|
1483
|
+
response: {
|
|
1484
|
+
id: state.responseId,
|
|
1485
|
+
model: state.model,
|
|
1486
|
+
created_at: state.createdAt,
|
|
1487
|
+
status: 'in_progress',
|
|
1488
|
+
output: []
|
|
1476
1489
|
}
|
|
1477
1490
|
});
|
|
1478
1491
|
|
package/cli/openai-bridge.js
CHANGED
|
@@ -87,7 +87,6 @@ function normalizeOpenaiUpstreamBaseUrl(rawValue) {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
const {
|
|
90
|
-
normalizeBridgeMaxRetries,
|
|
91
90
|
parseJsonOrError,
|
|
92
91
|
extractChatCompletionResult,
|
|
93
92
|
convertResponsesRequestToChatCompletions,
|
|
@@ -114,11 +113,10 @@ function normalizeUpstreamEntry(entry) {
|
|
|
114
113
|
const apiKey = normalizeText(entry.apiKey || entry.api_key || entry.key || '');
|
|
115
114
|
const headersRaw = entry.headers || entry.extraHeaders || entry.extra_headers || null;
|
|
116
115
|
const headers = normalizeHeadersMap(headersRaw);
|
|
117
|
-
const maxRetries = normalizeBridgeMaxRetries(entry.maxRetries ?? entry.max_retries);
|
|
118
116
|
if (!baseUrl || !isValidHttpUrl(baseUrl)) {
|
|
119
117
|
return null;
|
|
120
118
|
}
|
|
121
|
-
return { baseUrl, apiKey, headers
|
|
119
|
+
return { baseUrl, apiKey, headers };
|
|
122
120
|
}
|
|
123
121
|
|
|
124
122
|
function normalizeHeadersMap(value) {
|
|
@@ -169,7 +167,6 @@ function upsertOpenaiBridgeProvider(filePath, providerName, upstreamBaseUrl, api
|
|
|
169
167
|
const baseUrl = normalizeOpenaiUpstreamBaseUrl(upstreamBaseUrl);
|
|
170
168
|
const key = normalizeText(apiKey);
|
|
171
169
|
const nextHeaders = normalizeHeadersMap(headers);
|
|
172
|
-
const maxRetries = normalizeBridgeMaxRetries(options && options.maxRetries);
|
|
173
170
|
|
|
174
171
|
if (!name) {
|
|
175
172
|
return { error: 'Provider name is required' };
|
|
@@ -191,7 +188,6 @@ function upsertOpenaiBridgeProvider(filePath, providerName, upstreamBaseUrl, api
|
|
|
191
188
|
baseUrl,
|
|
192
189
|
apiKey: key,
|
|
193
190
|
headers: Object.keys(nextHeaders).length ? nextHeaders : existingHeaders,
|
|
194
|
-
maxRetries
|
|
195
191
|
}
|
|
196
192
|
}
|
|
197
193
|
};
|
|
@@ -384,7 +380,7 @@ function createOpenaiBridgeHttpHandler(options = {}) {
|
|
|
384
380
|
maxBytes: maxUpstreamBytes,
|
|
385
381
|
httpAgent,
|
|
386
382
|
httpsAgent
|
|
387
|
-
})
|
|
383
|
+
}));
|
|
388
384
|
if (!result.ok) {
|
|
389
385
|
res.writeHead(502, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
390
386
|
res.end(JSON.stringify({ error: `Upstream request failed: ${result.error}` }));
|
|
@@ -446,7 +442,7 @@ function createOpenaiBridgeHttpHandler(options = {}) {
|
|
|
446
442
|
res,
|
|
447
443
|
model: typeof chatBody.model === 'string' ? chatBody.model : '',
|
|
448
444
|
toolTypesByName: converted.toolTypesByName || {}
|
|
449
|
-
})
|
|
445
|
+
}));
|
|
450
446
|
if (!streamed.ok) {
|
|
451
447
|
if (res.writableEnded || res.destroyed) {
|
|
452
448
|
return;
|
|
@@ -471,7 +467,7 @@ function createOpenaiBridgeHttpHandler(options = {}) {
|
|
|
471
467
|
maxBytes: maxUpstreamBytes,
|
|
472
468
|
httpAgent,
|
|
473
469
|
httpsAgent
|
|
474
|
-
})
|
|
470
|
+
}));
|
|
475
471
|
if (!upstreamResult.ok) {
|
|
476
472
|
res.writeHead(502, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
477
473
|
res.end(JSON.stringify({ error: `Upstream request failed: ${upstreamResult.error}` }));
|
|
@@ -514,8 +510,23 @@ function createOpenaiBridgeHttpHandler(options = {}) {
|
|
|
514
510
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
515
511
|
res.end(JSON.stringify(ensureResponseMetadata(responsesPayload)));
|
|
516
512
|
} catch (e) {
|
|
513
|
+
if (res.writableEnded || res.destroyed) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const message = e && e.message ? e.message : 'Internal Error';
|
|
517
|
+
if (res.headersSent) {
|
|
518
|
+
try {
|
|
519
|
+
res.end();
|
|
520
|
+
} catch (_) {
|
|
521
|
+
// Headers are already committed. Close the socket instead of leaving the client waiting forever.
|
|
522
|
+
if (!res.destroyed && typeof res.destroy === 'function') {
|
|
523
|
+
res.destroy(e);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
517
528
|
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
518
|
-
res.end(JSON.stringify({ error:
|
|
529
|
+
res.end(JSON.stringify({ error: message }));
|
|
519
530
|
}
|
|
520
531
|
})();
|
|
521
532
|
|
|
@@ -543,7 +554,6 @@ module.exports = {
|
|
|
543
554
|
extractChatCompletionResult,
|
|
544
555
|
buildResponsesPayloadFromChatResult,
|
|
545
556
|
retryTransientRequest,
|
|
546
|
-
normalizeBridgeMaxRetries,
|
|
547
557
|
normalizeOpenaiUpstreamBaseUrl,
|
|
548
558
|
extractResponsesOutputText,
|
|
549
559
|
shouldFallbackFromUpstreamResponses,
|
package/package.json
CHANGED
package/web-ui/app.js
CHANGED
|
@@ -290,9 +290,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
290
290
|
appVersionStatusChecked: false,
|
|
291
291
|
appVersionStatusCheckedAt: '',
|
|
292
292
|
appVersionStatusSource: '',
|
|
293
|
-
newProvider: { name: '', url: '', key: '', model: '', useTransform: false
|
|
293
|
+
newProvider: { name: '', url: '', key: '', model: '', useTransform: false },
|
|
294
294
|
resetConfigLoading: false,
|
|
295
|
-
editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false, useTransform: false
|
|
295
|
+
editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false, useTransform: false },
|
|
296
296
|
newModelName: '',
|
|
297
297
|
currentClaudeConfig: '',
|
|
298
298
|
currentClaudeModel: '',
|
|
@@ -206,13 +206,13 @@ export function createAgentsMethods(options = {}) {
|
|
|
206
206
|
this.agentsContext = 'openclaw-workspace';
|
|
207
207
|
this.agentsWorkspaceFileName = fileName;
|
|
208
208
|
this.agentsModalTitle = tr('modal.agents.title.openclawWorkspaceFile', `OpenClaw 工作区文件: ${fileName}`, { fileName });
|
|
209
|
-
this.agentsModalHint = tr('modal.agents.hint.openclawWorkspaceFile',
|
|
209
|
+
this.agentsModalHint = tr('modal.agents.hint.openclawWorkspaceFile', `Workspace / ${fileName}`, { fileName });
|
|
210
210
|
return;
|
|
211
211
|
}
|
|
212
212
|
this.agentsContext = context === 'openclaw' ? 'openclaw' : 'codex';
|
|
213
213
|
if (this.agentsContext === 'openclaw') {
|
|
214
214
|
this.agentsModalTitle = tr('modal.agents.title.openclaw', 'OpenClaw AGENTS.md 编辑器');
|
|
215
|
-
this.agentsModalHint = tr('modal.agents.hint.openclaw', '
|
|
215
|
+
this.agentsModalHint = tr('modal.agents.hint.openclaw', 'Workspace / AGENTS.md');
|
|
216
216
|
} else {
|
|
217
217
|
this.agentsModalTitle = tr('modal.agents.title.default', 'AGENTS.md 编辑器');
|
|
218
218
|
this.agentsModalHint = tr('modal.agents.hint.default', '保存后会写入目标 AGENTS.md(与 config.toml 同级)。');
|
|
@@ -224,6 +224,31 @@ function readPreferredProviderModels(records) {
|
|
|
224
224
|
return [];
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
function readNestedValue(record, path) {
|
|
228
|
+
if (!isPlainRecord(record)) return undefined;
|
|
229
|
+
let cursor = record;
|
|
230
|
+
for (const key of path) {
|
|
231
|
+
if (!isPlainRecord(cursor) || !Object.prototype.hasOwnProperty.call(cursor, key)) {
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
cursor = cursor[key];
|
|
235
|
+
}
|
|
236
|
+
return cursor;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function formatOpenclawSummaryValue(value, fallback = '未配置') {
|
|
240
|
+
if (Array.isArray(value)) {
|
|
241
|
+
const list = value
|
|
242
|
+
.map(item => (typeof item === 'string' ? item.trim() : String(item || '').trim()))
|
|
243
|
+
.filter(Boolean);
|
|
244
|
+
return list.length ? list.join(' / ') : fallback;
|
|
245
|
+
}
|
|
246
|
+
if (value === true) return '启用';
|
|
247
|
+
if (value === false) return '关闭';
|
|
248
|
+
const text = typeof value === 'string' ? value.trim() : String(value ?? '').trim();
|
|
249
|
+
return text || fallback;
|
|
250
|
+
}
|
|
251
|
+
|
|
227
252
|
export function createOpenclawCoreMethods() {
|
|
228
253
|
return {
|
|
229
254
|
getOpenclawParser() {
|
|
@@ -472,6 +497,68 @@ export function createOpenclawCoreMethods() {
|
|
|
472
497
|
};
|
|
473
498
|
},
|
|
474
499
|
|
|
500
|
+
getOpenclawConfigSummary(config) {
|
|
501
|
+
const content = config && typeof config.content === 'string' ? config.content : '';
|
|
502
|
+
if (!content.trim()) {
|
|
503
|
+
return [];
|
|
504
|
+
}
|
|
505
|
+
const parsed = this.parseOpenclawContent(content, { allowEmpty: true });
|
|
506
|
+
if (!parsed.ok || !isPlainRecord(parsed.data)) {
|
|
507
|
+
return [
|
|
508
|
+
{ key: 'parse-error', label: '配置状态', value: '解析失败', tone: 'warning' }
|
|
509
|
+
];
|
|
510
|
+
}
|
|
511
|
+
const data = parsed.data;
|
|
512
|
+
const agentDefaults = readNestedValue(data, ['agents', 'defaults']);
|
|
513
|
+
const modelConfig = isPlainRecord(agentDefaults) ? agentDefaults.model : undefined;
|
|
514
|
+
const primaryModel = isPlainRecord(modelConfig)
|
|
515
|
+
? modelConfig.primary
|
|
516
|
+
: (typeof modelConfig === 'string' ? modelConfig : readNestedValue(data, ['agent', 'model']));
|
|
517
|
+
const fallbacks = isPlainRecord(modelConfig) && Array.isArray(modelConfig.fallbacks)
|
|
518
|
+
? modelConfig.fallbacks
|
|
519
|
+
: [];
|
|
520
|
+
const workspace = isPlainRecord(agentDefaults) ? agentDefaults.workspace : '';
|
|
521
|
+
const browser = isPlainRecord(data.browser) ? data.browser : {};
|
|
522
|
+
const browserMode = browser.enabled === false
|
|
523
|
+
? '关闭'
|
|
524
|
+
: (browser.headless === true ? 'Headless' : (browser.headless === false ? '可见窗口' : '未声明'));
|
|
525
|
+
const executablePath = typeof browser.executablePath === 'string' ? browser.executablePath.trim() : '';
|
|
526
|
+
const providerNames = collectDistinctProviderKeys(
|
|
527
|
+
readNestedValue(data, ['models', 'providers']),
|
|
528
|
+
data.providers
|
|
529
|
+
);
|
|
530
|
+
return [
|
|
531
|
+
{ key: 'primary', label: '默认模型', value: formatOpenclawSummaryValue(primaryModel) },
|
|
532
|
+
{ key: 'fallbacks', label: 'Fallback', value: formatOpenclawSummaryValue(fallbacks) },
|
|
533
|
+
{ key: 'workspace', label: 'Workspace', value: formatOpenclawSummaryValue(workspace) },
|
|
534
|
+
{ key: 'browser', label: 'Browser', value: browserMode },
|
|
535
|
+
{ key: 'browser-path', label: 'Chrome', value: formatOpenclawSummaryValue(executablePath) },
|
|
536
|
+
{ key: 'providers', label: 'Providers', value: providerNames.length ? providerNames.join(' / ') : '未配置' }
|
|
537
|
+
];
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
getOpenclawStatusSummaryItems() {
|
|
541
|
+
const current = this.openclawConfigs && this.currentOpenclawConfig
|
|
542
|
+
? this.openclawConfigs[this.currentOpenclawConfig]
|
|
543
|
+
: null;
|
|
544
|
+
const configPath = this.openclawConfigPath || '~/.openclaw/openclaw.json';
|
|
545
|
+
return [
|
|
546
|
+
{ key: 'config-path', label: 'Config', value: configPath, tone: this.openclawConfigExists ? 'ok' : 'warning' },
|
|
547
|
+
...this.getOpenclawConfigSummary(current || {}).slice(0, 4)
|
|
548
|
+
];
|
|
549
|
+
},
|
|
550
|
+
|
|
551
|
+
getOpenclawQuickWorkspaceFiles() {
|
|
552
|
+
return ['SOUL.md', 'USER.md', 'TOOLS.md', 'HEARTBEAT.md'];
|
|
553
|
+
},
|
|
554
|
+
|
|
555
|
+
openOpenclawQuickWorkspaceFile(fileName) {
|
|
556
|
+
const normalized = typeof fileName === 'string' ? fileName.trim() : '';
|
|
557
|
+
if (!normalized) return;
|
|
558
|
+
this.openclawWorkspaceFileName = normalized;
|
|
559
|
+
this.openOpenclawWorkspaceEditor();
|
|
560
|
+
},
|
|
561
|
+
|
|
475
562
|
syncOpenclawQuickFromText(options = {}) {
|
|
476
563
|
const silent = !!options.silent;
|
|
477
564
|
const parsed = this.parseOpenclawContent(this.openclawEditing.content, { allowEmpty: true });
|
|
@@ -41,13 +41,6 @@ function findProviderByName(list, name) {
|
|
|
41
41
|
return (Array.isArray(list) ? list : []).find((item) => item && normalizeText(item.name) === target) || null;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function normalizeBridgeMaxRetries(value, fallback = 2) {
|
|
45
|
-
const raw = Number(value);
|
|
46
|
-
const fallbackRaw = Number(fallback);
|
|
47
|
-
const base = Number.isFinite(raw) ? raw : (Number.isFinite(fallbackRaw) ? fallbackRaw : 2);
|
|
48
|
-
return Math.min(10, Math.max(2, Math.floor(base)));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
44
|
function normalizeProviderDraftState(target) {
|
|
52
45
|
if (!target || typeof target !== 'object') return;
|
|
53
46
|
if (typeof target.name === 'string') {
|
|
@@ -62,9 +55,6 @@ function normalizeProviderDraftState(target) {
|
|
|
62
55
|
if (typeof target.key === 'string') {
|
|
63
56
|
target.key = target.key.trim();
|
|
64
57
|
}
|
|
65
|
-
if (target.useTransform || target.openaiBridgeMaxRetries !== undefined) {
|
|
66
|
-
target.openaiBridgeMaxRetries = normalizeBridgeMaxRetries(target.openaiBridgeMaxRetries);
|
|
67
|
-
}
|
|
68
58
|
}
|
|
69
59
|
|
|
70
60
|
function maskKeyLocal(key) {
|
|
@@ -87,13 +77,11 @@ function getProviderValidationForContext(vm, mode = 'add') {
|
|
|
87
77
|
const model = normalizeText(draft && draft.model);
|
|
88
78
|
const key = normalizeText(draft && draft.key);
|
|
89
79
|
const useTransform = !!(draft && draft.useTransform);
|
|
90
|
-
const openaiBridgeMaxRetries = normalizeBridgeMaxRetries(draft && draft.openaiBridgeMaxRetries);
|
|
91
80
|
const errors = {
|
|
92
81
|
name: '',
|
|
93
82
|
url: '',
|
|
94
83
|
key: '',
|
|
95
84
|
model: '',
|
|
96
|
-
openaiBridgeMaxRetries: ''
|
|
97
85
|
};
|
|
98
86
|
|
|
99
87
|
if (mode === 'add') {
|
|
@@ -124,10 +112,6 @@ function getProviderValidationForContext(vm, mode = 'add') {
|
|
|
124
112
|
errors.model = '模型名称必填';
|
|
125
113
|
}
|
|
126
114
|
|
|
127
|
-
if (useTransform && openaiBridgeMaxRetries < 2) {
|
|
128
|
-
errors.openaiBridgeMaxRetries = '重试次数最小为 2';
|
|
129
|
-
}
|
|
130
|
-
|
|
131
115
|
return {
|
|
132
116
|
mode,
|
|
133
117
|
name,
|
|
@@ -135,9 +119,8 @@ function getProviderValidationForContext(vm, mode = 'add') {
|
|
|
135
119
|
key,
|
|
136
120
|
model,
|
|
137
121
|
useTransform,
|
|
138
|
-
openaiBridgeMaxRetries,
|
|
139
122
|
errors,
|
|
140
|
-
ok: !errors.name && !errors.url && !errors.key && !errors.model
|
|
123
|
+
ok: !errors.name && !errors.url && !errors.key && !errors.model
|
|
141
124
|
};
|
|
142
125
|
}
|
|
143
126
|
|
|
@@ -191,7 +174,7 @@ export function createProvidersMethods(options = {}) {
|
|
|
191
174
|
normalizeProviderDraftState(this.newProvider);
|
|
192
175
|
const validation = getProviderValidationForContext(this, 'add');
|
|
193
176
|
if (!validation.ok) {
|
|
194
|
-
return this.showMessage(validation.errors.name || validation.errors.url || validation.errors.key || validation.errors.model ||
|
|
177
|
+
return this.showMessage(validation.errors.name || validation.errors.url || validation.errors.key || validation.errors.model || this.t('toast.provider.fieldsRequired'), 'error');
|
|
195
178
|
}
|
|
196
179
|
|
|
197
180
|
try {
|
|
@@ -203,7 +186,6 @@ export function createProvidersMethods(options = {}) {
|
|
|
203
186
|
};
|
|
204
187
|
if (this.newProvider && this.newProvider.useTransform) {
|
|
205
188
|
payload.useTransform = true;
|
|
206
|
-
payload.openaiBridgeMaxRetries = validation.openaiBridgeMaxRetries;
|
|
207
189
|
}
|
|
208
190
|
const suggestedModel = validation.model;
|
|
209
191
|
const res = await api('add-provider', payload);
|
|
@@ -218,7 +200,6 @@ export function createProvidersMethods(options = {}) {
|
|
|
218
200
|
url: validation.url,
|
|
219
201
|
upstreamUrl: '',
|
|
220
202
|
codexmate_bridge: payload.useTransform ? 'openai' : '',
|
|
221
|
-
openaiBridgeMaxRetries: payload.useTransform ? validation.openaiBridgeMaxRetries : undefined,
|
|
222
203
|
key: maskKeyLocal(payload.key),
|
|
223
204
|
hasKey: !!payload.key,
|
|
224
205
|
models: suggestedModel ? [{ id: suggestedModel, name: suggestedModel, cost: null, contextWindow: undefined, maxTokens: undefined }] : [],
|
|
@@ -345,7 +326,6 @@ export function createProvidersMethods(options = {}) {
|
|
|
345
326
|
key: '',
|
|
346
327
|
model: '',
|
|
347
328
|
useTransform: isTransform,
|
|
348
|
-
openaiBridgeMaxRetries: normalizeBridgeMaxRetries(provider.openaiBridgeMaxRetries)
|
|
349
329
|
};
|
|
350
330
|
this.showAddProviderKey = false;
|
|
351
331
|
this.showAddModal = true;
|
|
@@ -358,7 +338,6 @@ export function createProvidersMethods(options = {}) {
|
|
|
358
338
|
key: '',
|
|
359
339
|
model: '',
|
|
360
340
|
useTransform: false,
|
|
361
|
-
openaiBridgeMaxRetries: 2
|
|
362
341
|
};
|
|
363
342
|
this.showAddProviderKey = false;
|
|
364
343
|
this.showAddModal = true;
|
|
@@ -387,7 +366,6 @@ export function createProvidersMethods(options = {}) {
|
|
|
387
366
|
? provider.nonEditable
|
|
388
367
|
: this.isNonDeletableProvider(provider),
|
|
389
368
|
useTransform: isTransformProvider,
|
|
390
|
-
openaiBridgeMaxRetries: normalizeBridgeMaxRetries(provider.openaiBridgeMaxRetries)
|
|
391
369
|
};
|
|
392
370
|
this._editProviderOriginalKey = '';
|
|
393
371
|
this._editProviderRealKeyLoaded = false;
|
|
@@ -428,9 +406,6 @@ export function createProvidersMethods(options = {}) {
|
|
|
428
406
|
&& res.baseUrl.trim()
|
|
429
407
|
) {
|
|
430
408
|
this.editingProvider.url = normalizeProviderUrl(res.baseUrl);
|
|
431
|
-
if (res.maxRetries !== undefined) {
|
|
432
|
-
this.editingProvider.openaiBridgeMaxRetries = normalizeBridgeMaxRetries(res.maxRetries);
|
|
433
|
-
}
|
|
434
409
|
}
|
|
435
410
|
} catch (_) {
|
|
436
411
|
// ignore
|
|
@@ -447,13 +422,12 @@ export function createProvidersMethods(options = {}) {
|
|
|
447
422
|
normalizeProviderDraftState(this.editingProvider);
|
|
448
423
|
const validation = getProviderValidationForContext(this, 'edit');
|
|
449
424
|
if (!validation.ok) {
|
|
450
|
-
return this.showMessage(validation.errors.name || validation.errors.url ||
|
|
425
|
+
return this.showMessage(validation.errors.name || validation.errors.url || this.t('toast.provider.urlRequired'), 'error');
|
|
451
426
|
}
|
|
452
427
|
|
|
453
428
|
const params = { name: validation.name, url: validation.url };
|
|
454
429
|
if (this.editingProvider && this.editingProvider.useTransform) {
|
|
455
430
|
params.useTransform = true;
|
|
456
|
-
params.openaiBridgeMaxRetries = validation.openaiBridgeMaxRetries;
|
|
457
431
|
}
|
|
458
432
|
if (this._editProviderRealKeyLoaded) {
|
|
459
433
|
const currentKey = typeof this.editingProvider.key === 'string' ? this.editingProvider.key : '';
|
|
@@ -479,7 +453,6 @@ export function createProvidersMethods(options = {}) {
|
|
|
479
453
|
key: keyUpdated ? maskKeyLocal(params.key) : p.key,
|
|
480
454
|
hasKey: keyUpdated ? !!params.key : p.hasKey,
|
|
481
455
|
codexmate_bridge: params.useTransform ? 'openai' : p.codexmate_bridge,
|
|
482
|
-
openaiBridgeMaxRetries: params.useTransform ? validation.openaiBridgeMaxRetries : p.openaiBridgeMaxRetries
|
|
483
456
|
};
|
|
484
457
|
}
|
|
485
458
|
return p;
|
|
@@ -497,7 +470,7 @@ export function createProvidersMethods(options = {}) {
|
|
|
497
470
|
this.showEditProviderKey = false;
|
|
498
471
|
this._editProviderOriginalKey = '';
|
|
499
472
|
this._editProviderRealKeyLoaded = false;
|
|
500
|
-
this.editingProvider = { name: '', url: '', key: '', readOnly: false, nonEditable: false, useTransform: false
|
|
473
|
+
this.editingProvider = { name: '', url: '', key: '', readOnly: false, nonEditable: false, useTransform: false };
|
|
501
474
|
},
|
|
502
475
|
|
|
503
476
|
toggleEditProviderKey() {
|
|
@@ -580,7 +553,7 @@ export function createProvidersMethods(options = {}) {
|
|
|
580
553
|
closeAddModal() {
|
|
581
554
|
this.showAddModal = false;
|
|
582
555
|
this.showAddProviderKey = false;
|
|
583
|
-
this.newProvider = { name: nextCodexProviderName(this.providersList), url: '', key: '', model: '', useTransform: false
|
|
556
|
+
this.newProvider = { name: nextCodexProviderName(this.providersList), url: '', key: '', model: '', useTransform: false };
|
|
584
557
|
},
|
|
585
558
|
|
|
586
559
|
toggleAddProviderKey() {
|
|
@@ -517,8 +517,6 @@ const en = Object.freeze({
|
|
|
517
517
|
'modal.claudeConfigEdit.title': 'Edit Claude Code config',
|
|
518
518
|
'field.useBuiltinTransform': 'Use built-in transform (OpenAI compatible)',
|
|
519
519
|
'hint.useBuiltinTransform': 'When enabled, base_url points to codexmate built-in transform service; Codex token is fixed to codexmate.',
|
|
520
|
-
'field.transformMaxRetries': 'Built-in transform retries',
|
|
521
|
-
'hint.transformMaxRetries': 'Default 2, minimum 2; number of retries after the first failed attempt.',
|
|
522
520
|
|
|
523
521
|
// Config template / agents modals
|
|
524
522
|
'modal.configTemplate.label': 'config.toml template',
|
|
@@ -519,8 +519,6 @@ const ja = Object.freeze({
|
|
|
519
519
|
'modal.claudeConfigEdit.title': 'Claude Code 設定編集',
|
|
520
520
|
'field.useBuiltinTransform': '内蔵変換を使用(OpenAI 形式互換)',
|
|
521
521
|
'hint.useBuiltinTransform': '有効時:書き込まれる base_url は codexmate 内蔵変換サービスを指します。Codex のトークンは codexmate に固定されます。',
|
|
522
|
-
'field.transformMaxRetries': '内蔵変換の再試行回数',
|
|
523
|
-
'hint.transformMaxRetries': '既定値は 2、最小値も 2。初回失敗後に再試行する最大回数です。',
|
|
524
522
|
|
|
525
523
|
// Config template / agents modals
|
|
526
524
|
'modal.configTemplate.label': 'config.toml テンプレート',
|
|
@@ -737,8 +737,6 @@ const vi = Object.freeze({
|
|
|
737
737
|
'modal.claudeConfigEdit.title': 'Chỉnh sửa cấu hình Claude Code',
|
|
738
738
|
'field.useBuiltinTransform': 'Dùng transform tích hợp (tương thích OpenAI)',
|
|
739
739
|
'hint.useBuiltinTransform': 'Khi bật, base_url trỏ đến dịch vụ transform tích hợp của codexmate.',
|
|
740
|
-
'field.transformMaxRetries': 'Số lần thử lại transform tích hợp',
|
|
741
|
-
'hint.transformMaxRetries': 'Mặc định 2, tối thiểu 2; số lần thử lại sau lần gọi đầu thất bại.',
|
|
742
740
|
'modal.configTemplate.title': 'Trình soạn thảo template cấu hình (xác nhận thủ công)',
|
|
743
741
|
'modal.configTemplate.label': 'Template config.toml',
|
|
744
742
|
'modal.configTemplate.placeholder': 'Chỉnh sửa template config.toml tại đây',
|
|
@@ -517,8 +517,6 @@ const zhTw = Object.freeze({
|
|
|
517
517
|
'modal.claudeConfigEdit.title': '編輯 Claude Code 設定',
|
|
518
518
|
'field.useBuiltinTransform': '使用內建轉換(兼容 OpenAI 格式)',
|
|
519
519
|
'hint.useBuiltinTransform': '開啟後:寫入的 base_url 會指向 codexmate 內建轉換服務;Codex 使用的令牌固定為 codexmate。',
|
|
520
|
-
'field.transformMaxRetries': '內建轉換重試次數',
|
|
521
|
-
'hint.transformMaxRetries': '預設 2,最小 2;表示失敗後最多重試的次數。',
|
|
522
520
|
|
|
523
521
|
// Config template / agents modals
|
|
524
522
|
'modal.configTemplate.label': 'config.toml 模板',
|
|
@@ -517,8 +517,6 @@ const zh = Object.freeze({
|
|
|
517
517
|
'modal.claudeConfigEdit.title': '编辑 Claude Code 配置',
|
|
518
518
|
'field.useBuiltinTransform': '使用内建转换(兼容 OpenAI 格式)',
|
|
519
519
|
'hint.useBuiltinTransform': '开启后:写入的 base_url 会指向 codexmate 内建转换服务;Codex 使用的令牌固定为 codexmate。',
|
|
520
|
-
'field.transformMaxRetries': '内建转换重试次数',
|
|
521
|
-
'hint.transformMaxRetries': '默认 2,最小 2;表示失败后最多重试的次数。',
|
|
522
520
|
|
|
523
521
|
// Config template / agents modals
|
|
524
522
|
'modal.configTemplate.label': 'config.toml 模板',
|
|
@@ -944,9 +942,9 @@ const zh = Object.freeze({
|
|
|
944
942
|
'modal.agents.hint.claudeProjectGlobal': '保存后会写入 ~/.claude/CLAUDE.md。',
|
|
945
943
|
'modal.agents.contentLabel.claudeProject': 'CLAUDE.md 内容',
|
|
946
944
|
'modal.agents.placeholder.claudeProject': '在这里编辑 CLAUDE.md',
|
|
947
|
-
'modal.agents.hint.openclaw': '
|
|
945
|
+
'modal.agents.hint.openclaw': 'Workspace / AGENTS.md',
|
|
948
946
|
'modal.agents.title.openclawWorkspaceFile': 'OpenClaw 工作区文件: {fileName}',
|
|
949
|
-
'modal.agents.hint.openclawWorkspaceFile': '
|
|
947
|
+
'modal.agents.hint.openclawWorkspaceFile': 'Workspace / {fileName}',
|
|
950
948
|
'config.url.unset': '未设 URL',
|
|
951
949
|
'config.model.unset': '未设置模型',
|
|
952
950
|
'config.badge.system': '系统',
|
|
@@ -1516,11 +1514,11 @@ const zh = Object.freeze({
|
|
|
1516
1514
|
'openclaw.applyHint': '写入 ~/.openclaw/openclaw.json,支持 JSON5。',
|
|
1517
1515
|
'openclaw.workspace.title': 'OpenClaw 工作区',
|
|
1518
1516
|
'openclaw.configs.hint': '选择常用配置,或进入编辑器维护完整 JSON5。',
|
|
1519
|
-
'openclaw.agents.hint': '
|
|
1517
|
+
'openclaw.agents.hint': 'Workspace / AGENTS.md',
|
|
1520
1518
|
'openclaw.agents.open': '打开 AGENTS.md',
|
|
1521
1519
|
'openclaw.workspaceFile': '工作区文件',
|
|
1522
1520
|
'openclaw.workspace.placeholder': '例如: SOUL.md',
|
|
1523
|
-
'openclaw.workspace.hint': '
|
|
1521
|
+
'openclaw.workspace.hint': 'Workspace .md',
|
|
1524
1522
|
'openclaw.workspace.open': '打开工作区文件',
|
|
1525
1523
|
'openclaw.configured': '已配置',
|
|
1526
1524
|
'openclaw.notConfigured': '未配置',
|
|
@@ -117,8 +117,8 @@
|
|
|
117
117
|
</div>
|
|
118
118
|
</div>
|
|
119
119
|
|
|
120
|
-
<div class="modal-editor-body">
|
|
121
|
-
<div class="form-group">
|
|
120
|
+
<div class="modal-editor-body" :class="{ 'modal-editor-body--openclaw-workspace': agentsContext === 'openclaw-workspace' }">
|
|
121
|
+
<div class="form-group agents-target-file-group">
|
|
122
122
|
<label class="form-label">{{ t('modal.agents.targetFile') }}</label>
|
|
123
123
|
<div class="form-hint">
|
|
124
124
|
{{ agentsPath || t('common.notLoaded') }}
|
|
@@ -129,8 +129,12 @@
|
|
|
129
129
|
</div>
|
|
130
130
|
|
|
131
131
|
|
|
132
|
-
<div class="form-group">
|
|
133
|
-
<label class="form-label">
|
|
132
|
+
<div class="form-group agents-content-group">
|
|
133
|
+
<label class="form-label">
|
|
134
|
+
{{ agentsContext === 'openclaw-workspace'
|
|
135
|
+
? `${agentsWorkspaceFileName || '工作区文件'} 内容`
|
|
136
|
+
: t(agentsContext === 'claude-project' ? 'modal.agents.contentLabel.claudeProject' : 'modal.agents.contentLabel') }}
|
|
137
|
+
</label>
|
|
134
138
|
<div
|
|
135
139
|
v-if="!agentsLoading && (hasAgentsContentChanged() || agentsDiffVisible)"
|
|
136
140
|
class="agents-diff-save-alert">
|
|
@@ -53,19 +53,6 @@
|
|
|
53
53
|
{{ t('field.useBuiltinTransform') }}
|
|
54
54
|
</label>
|
|
55
55
|
</div>
|
|
56
|
-
<div class="form-group" v-if="newProvider.useTransform">
|
|
57
|
-
<label class="form-label">{{ t('field.transformMaxRetries') }}</label>
|
|
58
|
-
<input
|
|
59
|
-
v-model.number="newProvider.openaiBridgeMaxRetries"
|
|
60
|
-
type="number"
|
|
61
|
-
min="2"
|
|
62
|
-
max="10"
|
|
63
|
-
step="1"
|
|
64
|
-
:class="['form-input', { invalid: !!providerFieldError('add', 'openaiBridgeMaxRetries') }]"
|
|
65
|
-
@blur="normalizeProviderDraft('add')">
|
|
66
|
-
<div class="form-hint">{{ t('hint.transformMaxRetries') }}</div>
|
|
67
|
-
<div v-if="providerFieldError('add', 'openaiBridgeMaxRetries')" class="form-hint form-error">{{ providerFieldError('add', 'openaiBridgeMaxRetries') }}</div>
|
|
68
|
-
</div>
|
|
69
56
|
|
|
70
57
|
<div class="btn-group">
|
|
71
58
|
<button class="btn btn-cancel" @click="closeAddModal">{{ t('common.cancel') }}</button>
|
|
@@ -104,19 +91,6 @@
|
|
|
104
91
|
</button>
|
|
105
92
|
</div>
|
|
106
93
|
</div>
|
|
107
|
-
<div class="form-group" v-if="editingProvider.useTransform">
|
|
108
|
-
<label class="form-label">{{ t('field.transformMaxRetries') }}</label>
|
|
109
|
-
<input
|
|
110
|
-
v-model.number="editingProvider.openaiBridgeMaxRetries"
|
|
111
|
-
type="number"
|
|
112
|
-
min="2"
|
|
113
|
-
max="10"
|
|
114
|
-
step="1"
|
|
115
|
-
:class="['form-input', { invalid: !!providerFieldError('edit', 'openaiBridgeMaxRetries') }]"
|
|
116
|
-
@blur="normalizeProviderDraft('edit')">
|
|
117
|
-
<div class="form-hint">{{ t('hint.transformMaxRetries') }}</div>
|
|
118
|
-
<div v-if="providerFieldError('edit', 'openaiBridgeMaxRetries')" class="form-hint form-error">{{ providerFieldError('edit', 'openaiBridgeMaxRetries') }}</div>
|
|
119
|
-
</div>
|
|
120
94
|
|
|
121
95
|
<div class="btn-group">
|
|
122
96
|
<button class="btn btn-cancel" @click="closeEditModal">{{ t('common.cancel') }}</button>
|