codex-overleaf-link 1.3.6 → 1.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -21
- package/extension/src/shared/agentTranscript.js +273 -10
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/failureReasons.js +578 -0
- package/extension/src/shared/i18n.js +175 -2
- package/extension/src/shared/pathRedaction.js +143 -0
- package/extension/src/shared/sessionState.js +175 -5
- package/extension/src/shared/storageDb.js +226 -4
- package/native-host/src/codexPromptAssembly.js +10 -2
- package/native-host/src/codexSessionRunner.js +33 -1
- package/package.json +1 -1
|
@@ -38,6 +38,12 @@
|
|
|
38
38
|
skipped: true,
|
|
39
39
|
pending: true
|
|
40
40
|
};
|
|
41
|
+
var VALID_TRACKED_CHANGE_STATUSES = {
|
|
42
|
+
pending: true,
|
|
43
|
+
accepted: true,
|
|
44
|
+
rejected: true,
|
|
45
|
+
needs_review: true
|
|
46
|
+
};
|
|
41
47
|
var SECRET_REDACTION_PATTERNS = [
|
|
42
48
|
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g,
|
|
43
49
|
/\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi,
|
|
@@ -232,6 +238,27 @@
|
|
|
232
238
|
function buildSessionRecord(input) {
|
|
233
239
|
var now = new Date().toISOString();
|
|
234
240
|
var titleSource = input.titleSource === 'manual' ? 'manual' : 'auto';
|
|
241
|
+
var updatedAt = typeof input.updatedAt === 'string' ? input.updatedAt : now;
|
|
242
|
+
// Welcome-panel + write-guard v1.3.8 add-on (Task 3): persist the four
|
|
243
|
+
// Recent-projects fields on every session record so the cross-project
|
|
244
|
+
// query (`listRecentProjectsAcrossAccount`) can filter / sort / render
|
|
245
|
+
// without touching the raw `task` text.
|
|
246
|
+
//
|
|
247
|
+
// `accountScopeId` is derived via the page-side injection point — T4
|
|
248
|
+
// installs the real implementation on `window.codexOverleafDeriveAccountScopeId`.
|
|
249
|
+
// For T3 the fallback is `() => null`; records persisted with a null scope
|
|
250
|
+
// surface `accountScopeUnavailable: true` and are filtered out of the
|
|
251
|
+
// cross-project query (spec §5.2 degraded mode).
|
|
252
|
+
var safeTaskSummary = typeof input.safeTaskSummary === 'string'
|
|
253
|
+
? input.safeTaskSummary
|
|
254
|
+
: deriveSafeTaskSummaryFromInput(input);
|
|
255
|
+
var accountScopeId = resolveAccountScopeId(input);
|
|
256
|
+
var accountScopeUnavailable = typeof input.accountScopeUnavailable === 'boolean'
|
|
257
|
+
? input.accountScopeUnavailable
|
|
258
|
+
: !accountScopeId;
|
|
259
|
+
var lastActivityAt = typeof input.lastActivityAt === 'string' && input.lastActivityAt
|
|
260
|
+
? input.lastActivityAt
|
|
261
|
+
: updatedAt;
|
|
235
262
|
return {
|
|
236
263
|
id: input.id || generateId('ses'),
|
|
237
264
|
projectId: input.projectId || '',
|
|
@@ -249,10 +276,77 @@
|
|
|
249
276
|
speedTier: typeof input.speedTier === 'string' ? input.speedTier : '',
|
|
250
277
|
requireReviewing: input.requireReviewing !== false,
|
|
251
278
|
createdAt: typeof input.createdAt === 'string' ? input.createdAt : now,
|
|
252
|
-
updatedAt:
|
|
279
|
+
updatedAt: updatedAt,
|
|
280
|
+
lastActivityAt: lastActivityAt,
|
|
281
|
+
accountScopeId: accountScopeId,
|
|
282
|
+
accountScopeUnavailable: accountScopeUnavailable,
|
|
283
|
+
safeTaskSummary: safeTaskSummary
|
|
253
284
|
};
|
|
254
285
|
}
|
|
255
286
|
|
|
287
|
+
function resolveAccountScopeId(input) {
|
|
288
|
+
if (input && typeof input.accountScopeId === 'string' && input.accountScopeId) {
|
|
289
|
+
return input.accountScopeId;
|
|
290
|
+
}
|
|
291
|
+
var globalScope = typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : null);
|
|
292
|
+
var derive = globalScope && globalScope.window && globalScope.window.codexOverleafDeriveAccountScopeId;
|
|
293
|
+
if (!(derive instanceof Function)) {
|
|
294
|
+
// No injection installed (T3 fallback): tests + early page-load reads
|
|
295
|
+
// both land here. Returning `null` puts the session in degraded mode
|
|
296
|
+
// and excludes it from the cross-project query.
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
var value = derive();
|
|
301
|
+
return typeof value === 'string' && value ? value : null;
|
|
302
|
+
} catch (_error) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function deriveSafeTaskSummaryFromInput(input) {
|
|
308
|
+
var SessionState = loadSessionState();
|
|
309
|
+
if (!SessionState || !(SessionState.computeSafeTaskSummary instanceof Function)) {
|
|
310
|
+
return '';
|
|
311
|
+
}
|
|
312
|
+
var task = typeof input.task === 'string' && input.task ? input.task : '';
|
|
313
|
+
if (!task && Array.isArray(input.runs) && input.runs.length) {
|
|
314
|
+
var firstRun = input.runs[0];
|
|
315
|
+
if (firstRun && typeof firstRun.task === 'string') {
|
|
316
|
+
task = firstRun.task;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return SessionState.computeSafeTaskSummary(task);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Module-local lazy require of the sibling sessionState module. CommonJS
|
|
323
|
+
// resolves once; subsequent calls hit the require cache. We avoid an import
|
|
324
|
+
// at the top of the file so the module surface remains friendly to the
|
|
325
|
+
// page-side IIFE wrapper (no `require` available in that context — the page
|
|
326
|
+
// build uses the global `CodexOverleafSessionState` via the IIFE branch).
|
|
327
|
+
var _sessionStateCache = null;
|
|
328
|
+
function loadSessionState() {
|
|
329
|
+
if (_sessionStateCache !== null) {
|
|
330
|
+
return _sessionStateCache || null;
|
|
331
|
+
}
|
|
332
|
+
var globalScope = typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : null);
|
|
333
|
+
if (globalScope && globalScope.CodexOverleafSessionState) {
|
|
334
|
+
_sessionStateCache = globalScope.CodexOverleafSessionState;
|
|
335
|
+
return _sessionStateCache;
|
|
336
|
+
}
|
|
337
|
+
if (typeof require === 'function') {
|
|
338
|
+
try {
|
|
339
|
+
_sessionStateCache = require('./sessionState');
|
|
340
|
+
return _sessionStateCache;
|
|
341
|
+
} catch (_error) {
|
|
342
|
+
_sessionStateCache = false;
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
_sessionStateCache = false;
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
256
350
|
function buildTurnRecord(input) {
|
|
257
351
|
var now = new Date().toISOString();
|
|
258
352
|
return {
|
|
@@ -542,7 +636,7 @@
|
|
|
542
636
|
}
|
|
543
637
|
|
|
544
638
|
function compactRunForStorage(run) {
|
|
545
|
-
|
|
639
|
+
var compact = {
|
|
546
640
|
id: run.id,
|
|
547
641
|
task: normalizeDisplayTextForStorage(run.task || 'untitled task', SESSION_STORAGE_LIMITS.taskChars),
|
|
548
642
|
mode: typeof run.mode === 'string' ? redactSecretLikeText(run.mode) : '',
|
|
@@ -551,6 +645,7 @@
|
|
|
551
645
|
speedTier: typeof run.speedTier === 'string' ? redactSecretLikeText(run.speedTier) : '',
|
|
552
646
|
status: normalizeRunStatus(run.status),
|
|
553
647
|
statusText: normalizeDisplayTextForStorage(run.statusText, SESSION_STORAGE_LIMITS.statusTextChars),
|
|
648
|
+
runProjectId: normalizeProjectPrefKey(run.runProjectId),
|
|
554
649
|
startedAt: typeof run.startedAt === 'string' ? redactSecretLikeText(run.startedAt) : '',
|
|
555
650
|
finishedAt: typeof run.finishedAt === 'string' ? redactSecretLikeText(run.finishedAt) : '',
|
|
556
651
|
events: compactRunEventsForStorage(run.events),
|
|
@@ -562,6 +657,10 @@
|
|
|
562
657
|
undoExpectedFiles: [],
|
|
563
658
|
undoStatus: normalizeDisplayTextForStorage(run.undoStatus, SESSION_STORAGE_LIMITS.statusTextChars)
|
|
564
659
|
};
|
|
660
|
+
if (VALID_TRACKED_CHANGE_STATUSES[run.trackedChangeStatus] === true) {
|
|
661
|
+
compact.trackedChangeStatus = run.trackedChangeStatus;
|
|
662
|
+
}
|
|
663
|
+
return compact;
|
|
565
664
|
}
|
|
566
665
|
|
|
567
666
|
function compactRunEventsForStorage(events) {
|
|
@@ -746,7 +845,19 @@
|
|
|
746
845
|
}
|
|
747
846
|
|
|
748
847
|
function normalizeRunStatus(status) {
|
|
749
|
-
|
|
848
|
+
// Welcome-panel + write-guard v1.3.8 add-on (Task 2/3): the run-status
|
|
849
|
+
// enum gained three post-navigation values. The storage normalizer must
|
|
850
|
+
// accept them so a settled run round-trips intact through `buildSessionRecord`.
|
|
851
|
+
// Unknown legacy values fall through to `completed` (the historical default).
|
|
852
|
+
return [
|
|
853
|
+
'pending',
|
|
854
|
+
'running',
|
|
855
|
+
'completed',
|
|
856
|
+
'failed',
|
|
857
|
+
'background_completed',
|
|
858
|
+
'needs_review_after_navigation',
|
|
859
|
+
'abandoned_after_navigation'
|
|
860
|
+
].indexOf(status) !== -1 ? status : 'completed';
|
|
750
861
|
}
|
|
751
862
|
|
|
752
863
|
function normalizeEventStatus(status) {
|
|
@@ -954,6 +1065,113 @@
|
|
|
954
1065
|
return result;
|
|
955
1066
|
}
|
|
956
1067
|
|
|
1068
|
+
// Welcome-panel + write-guard v1.3.8 add-on (Task 3): the Recent-projects
|
|
1069
|
+
// dashboard variant calls `listRecentProjectsAcrossAccount` to get the
|
|
1070
|
+
// sorted, deduped, capped list of projects in the current account scope.
|
|
1071
|
+
//
|
|
1072
|
+
// Contract (spec §5.6.1):
|
|
1073
|
+
// Input: { accountScopeId: string, limit?: number = 10 }
|
|
1074
|
+
// Output: Array<{ projectId, lastActivityAt, safeTaskSummary, primaryStatusBadge }>
|
|
1075
|
+
//
|
|
1076
|
+
// Fail-closed: if `accountScopeId` is falsy, returns `[]`. This is the
|
|
1077
|
+
// privacy floor — degraded mode must never leak across accounts.
|
|
1078
|
+
//
|
|
1079
|
+
// Implementation: full scan of the sessions store, group by `projectId`,
|
|
1080
|
+
// keep the row with the largest `lastActivityAt`, sort desc, cap at
|
|
1081
|
+
// `limit`. Full scan is acceptable for v1 given the small session count
|
|
1082
|
+
// (max ~12 per project × small number of projects). A future index on
|
|
1083
|
+
// `accountScopeId + lastActivityAt` is possible without a contract change.
|
|
1084
|
+
function listRecentProjectsAcrossAccount(options) {
|
|
1085
|
+
var accountScopeId = options && options.accountScopeId;
|
|
1086
|
+
var limit = options && Number.isFinite(options.limit) ? options.limit : 10;
|
|
1087
|
+
if (!accountScopeId) {
|
|
1088
|
+
return Promise.resolve([]);
|
|
1089
|
+
}
|
|
1090
|
+
return getAllSessions().then(function (all) {
|
|
1091
|
+
return filterRecentProjectsAcrossAccount(all, { accountScopeId: accountScopeId, limit: limit });
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// Pure helper extracted from `listRecentProjectsAcrossAccount` so the
|
|
1096
|
+
// filtering / dedupe / sort / cap behavior can be tested without an
|
|
1097
|
+
// IndexedDB stub. Same contract as the async wrapper; takes the raw session
|
|
1098
|
+
// records as an array instead of opening the database.
|
|
1099
|
+
function filterRecentProjectsAcrossAccount(sessions, options) {
|
|
1100
|
+
var accountScopeId = options && options.accountScopeId;
|
|
1101
|
+
var limit = options && Number.isFinite(options.limit) ? options.limit : 10;
|
|
1102
|
+
if (!accountScopeId) {
|
|
1103
|
+
return [];
|
|
1104
|
+
}
|
|
1105
|
+
var byProject = {}; // projectId → session
|
|
1106
|
+
var all = Array.isArray(sessions) ? sessions : [];
|
|
1107
|
+
for (var i = 0; i < all.length; i++) {
|
|
1108
|
+
var s = all[i];
|
|
1109
|
+
if (!s) continue;
|
|
1110
|
+
if (s.accountScopeId !== accountScopeId) continue;
|
|
1111
|
+
if (typeof s.lastActivityAt !== 'string' || !s.lastActivityAt) continue;
|
|
1112
|
+
if (typeof s.projectId !== 'string' || !s.projectId) continue;
|
|
1113
|
+
var prev = byProject[s.projectId];
|
|
1114
|
+
if (!prev || prev.lastActivityAt < s.lastActivityAt) {
|
|
1115
|
+
byProject[s.projectId] = s;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
var survivors = [];
|
|
1119
|
+
var keys = Object.keys(byProject);
|
|
1120
|
+
for (var j = 0; j < keys.length; j++) {
|
|
1121
|
+
survivors.push(byProject[keys[j]]);
|
|
1122
|
+
}
|
|
1123
|
+
survivors.sort(function (a, b) {
|
|
1124
|
+
return b.lastActivityAt.localeCompare(a.lastActivityAt);
|
|
1125
|
+
});
|
|
1126
|
+
var rows = [];
|
|
1127
|
+
for (var k = 0; k < survivors.length && k < limit; k++) {
|
|
1128
|
+
var session = survivors[k];
|
|
1129
|
+
rows.push({
|
|
1130
|
+
projectId: session.projectId,
|
|
1131
|
+
lastActivityAt: session.lastActivityAt,
|
|
1132
|
+
safeTaskSummary: typeof session.safeTaskSummary === 'string' ? session.safeTaskSummary : '',
|
|
1133
|
+
primaryStatusBadge: derivePrimaryStatusBadge(session)
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
return rows;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// Per spec §5.10:
|
|
1140
|
+
// 1. trackedChangeStatus if set (pending/accepted/rejected/needs_review)
|
|
1141
|
+
// 2. else run status (running/completed/failed/background_completed/
|
|
1142
|
+
// needs_review_after_navigation/abandoned_after_navigation)
|
|
1143
|
+
// 3. fallback `pending`.
|
|
1144
|
+
function derivePrimaryStatusBadge(session) {
|
|
1145
|
+
var runs = session && Array.isArray(session.runs) ? session.runs : [];
|
|
1146
|
+
if (!runs.length) return 'pending';
|
|
1147
|
+
var latestRun = runs[runs.length - 1];
|
|
1148
|
+
if (!latestRun) return 'pending';
|
|
1149
|
+
if (typeof latestRun.trackedChangeStatus === 'string' && latestRun.trackedChangeStatus) {
|
|
1150
|
+
return latestRun.trackedChangeStatus;
|
|
1151
|
+
}
|
|
1152
|
+
if (typeof latestRun.status === 'string' && latestRun.status) {
|
|
1153
|
+
return latestRun.status;
|
|
1154
|
+
}
|
|
1155
|
+
return 'pending';
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// Full scan of the sessions store, used by the cross-project query above.
|
|
1159
|
+
// We deliberately do not go through `getAllByIndex` because the existing
|
|
1160
|
+
// session indexes are keyed by `projectId` / `updatedAt` — neither matches
|
|
1161
|
+
// the account-scope filter. A direct `getAll` over the whole store is the
|
|
1162
|
+
// simplest correct read.
|
|
1163
|
+
function getAllSessions() {
|
|
1164
|
+
return openDb().then(function (db) {
|
|
1165
|
+
return new Promise(function (resolve, reject) {
|
|
1166
|
+
var tx = db.transaction('sessions', 'readonly');
|
|
1167
|
+
var store = tx.objectStore('sessions');
|
|
1168
|
+
var request = store.getAll();
|
|
1169
|
+
request.onsuccess = function (event) { resolve(event.target.result || []); };
|
|
1170
|
+
request.onerror = function (event) { reject(event.target.error); };
|
|
1171
|
+
});
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
|
|
957
1175
|
function buildActiveSessionByProject(existing, projectId, sessionId) {
|
|
958
1176
|
var result = {};
|
|
959
1177
|
if (existing && typeof existing === 'object') {
|
|
@@ -1059,6 +1277,10 @@
|
|
|
1059
1277
|
buildAuditLogRecord: buildAuditLogRecord,
|
|
1060
1278
|
extractLightweightPrefs: extractLightweightPrefs,
|
|
1061
1279
|
buildActiveSessionByProject: buildActiveSessionByProject,
|
|
1062
|
-
createEventBuffer: createEventBuffer
|
|
1280
|
+
createEventBuffer: createEventBuffer,
|
|
1281
|
+
listRecentProjectsAcrossAccount: listRecentProjectsAcrossAccount,
|
|
1282
|
+
filterRecentProjectsAcrossAccount: filterRecentProjectsAcrossAccount,
|
|
1283
|
+
derivePrimaryStatusBadge: derivePrimaryStatusBadge,
|
|
1284
|
+
getAllSessions: getAllSessions
|
|
1063
1285
|
};
|
|
1064
1286
|
});
|
|
@@ -193,11 +193,15 @@ function formatWriteExpectation({ mode = 'auto', task = '', skillInvocation = nu
|
|
|
193
193
|
return '- This is read-only. Inspect and explain; do not edit files.';
|
|
194
194
|
}
|
|
195
195
|
if (requestImpliesFileChanges(task)) {
|
|
196
|
-
|
|
196
|
+
const lines = [
|
|
197
197
|
'- The request asks for file changes. You must edit the local workspace when you find concrete fixes.',
|
|
198
198
|
'- Do not stop at a suggestion list or say you will not modify files unless no safe concrete edit exists.',
|
|
199
199
|
'- If you intentionally leave files unchanged, explain the specific blocker in the final answer.'
|
|
200
|
-
]
|
|
200
|
+
];
|
|
201
|
+
if (requestTargetsAbstract(task)) {
|
|
202
|
+
lines.push('- The request targets the abstract/summary. Locate the LaTeX abstract environment or summary paragraph and edit that local project file directly.');
|
|
203
|
+
}
|
|
204
|
+
return lines.join('\n');
|
|
201
205
|
}
|
|
202
206
|
return [
|
|
203
207
|
'- This mode can write. If the request asks for corrections, revisions, fixes, polishing, updates, or implementation, edit the local workspace directly.',
|
|
@@ -209,6 +213,10 @@ function requestImpliesFileChanges(task = '') {
|
|
|
209
213
|
return /修正|修复|修改|改[一-龥]*|完善|补全|补充|润色|重写|改写|整理|调整|应用|写入|fix|correct|repair|revise|edit|update|rewrite|polish|improve|apply/i.test(String(task || ''));
|
|
210
214
|
}
|
|
211
215
|
|
|
216
|
+
function requestTargetsAbstract(task = '') {
|
|
217
|
+
return /摘要|abstract|summary/i.test(String(task || ''));
|
|
218
|
+
}
|
|
219
|
+
|
|
212
220
|
function normalizeFocusFiles(value) {
|
|
213
221
|
const seen = new Set();
|
|
214
222
|
const files = [];
|
|
@@ -821,13 +821,22 @@ function runCodexAppServerSession(input) {
|
|
|
821
821
|
|
|
822
822
|
if (message.method) {
|
|
823
823
|
recordAssistantMessage(message);
|
|
824
|
-
|
|
824
|
+
// For `error` events surface the actual error text as the visible
|
|
825
|
+
// title so the run timeline reads "Reconnecting... 2/5" instead of
|
|
826
|
+
// a generic "error". Other methods continue to use the method name.
|
|
827
|
+
const eventTitle = message.method === 'error'
|
|
828
|
+
? (extractCodexAppServerErrorMessage(message.params) || message.method)
|
|
829
|
+
: message.method;
|
|
830
|
+
emitCodexEvent(input.emit, 'codex.session.event', eventTitle, {
|
|
825
831
|
method: message.method,
|
|
826
832
|
params: message.params || {}
|
|
827
833
|
}, inferNotificationStatus(message));
|
|
828
834
|
if (message.method === 'turn/completed' && (!activeTurnId || message.params?.turn?.id === activeTurnId || message.params?.turnId === activeTurnId)) {
|
|
829
835
|
succeed();
|
|
830
836
|
}
|
|
837
|
+
if (message.method === 'error' && isTransientCodexAppServerError(message.params)) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
831
840
|
if (message.method === 'error') {
|
|
832
841
|
fail(new Error(message.params?.error?.message || 'Codex turn failed'));
|
|
833
842
|
}
|
|
@@ -1211,12 +1220,35 @@ function emitCodexEvent(emit, type, title, detail = {}, status = 'running') {
|
|
|
1211
1220
|
}
|
|
1212
1221
|
|
|
1213
1222
|
function inferNotificationStatus(message) {
|
|
1223
|
+
if (message.method === 'error' && isTransientCodexAppServerError(message.params)) {
|
|
1224
|
+
return 'warning';
|
|
1225
|
+
}
|
|
1214
1226
|
if (/completed|updated|delta|started/.test(message.method || '')) {
|
|
1215
1227
|
return /completed/.test(message.method || '') ? 'completed' : 'running';
|
|
1216
1228
|
}
|
|
1217
1229
|
return 'running';
|
|
1218
1230
|
}
|
|
1219
1231
|
|
|
1232
|
+
function isTransientCodexAppServerError(params = {}) {
|
|
1233
|
+
const message = extractCodexAppServerErrorMessage(params);
|
|
1234
|
+
return /^Reconnecting(?:\.\.\.|…)?\s+\d+\s*\/\s*\d+\.?$/i.test(message);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
function extractCodexAppServerErrorMessage(params = {}) {
|
|
1238
|
+
const candidates = [
|
|
1239
|
+
params?.error?.message,
|
|
1240
|
+
params?.message,
|
|
1241
|
+
params?.title,
|
|
1242
|
+
params?.error
|
|
1243
|
+
];
|
|
1244
|
+
for (const candidate of candidates) {
|
|
1245
|
+
if (typeof candidate === 'string' && candidate.trim()) {
|
|
1246
|
+
return candidate.trim();
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
return '';
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1220
1252
|
function normalizeReasoningEffort(value) {
|
|
1221
1253
|
return ['none', 'minimal', 'low', 'medium', 'high', 'xhigh'].includes(value) ? value : null;
|
|
1222
1254
|
}
|