codex-overleaf-link 1.1.1
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/LICENSE +21 -0
- package/README.md +457 -0
- package/bin/codex-overleaf-link.mjs +223 -0
- package/extension/src/shared/agentTranscript.js +1175 -0
- package/extension/src/shared/auditRecords.js +568 -0
- package/extension/src/shared/compatibility.js +372 -0
- package/extension/src/shared/compileAdapter.js +176 -0
- package/extension/src/shared/governanceRules.js +252 -0
- package/extension/src/shared/i18n.js +565 -0
- package/extension/src/shared/models.js +106 -0
- package/extension/src/shared/otText.js +505 -0
- package/extension/src/shared/projectFiles.js +180 -0
- package/extension/src/shared/reviewing.js +99 -0
- package/extension/src/shared/sensitiveScan.js +116 -0
- package/extension/src/shared/sessionState.js +1084 -0
- package/extension/src/shared/staleGuard.js +150 -0
- package/extension/src/shared/storageDb.js +986 -0
- package/extension/src/shared/storageKeys.js +29 -0
- package/extension/src/shared/storageMigration.js +168 -0
- package/extension/src/shared/summary.js +248 -0
- package/extension/src/shared/undoOperations.js +369 -0
- package/native-host/src/codexArgs.js +43 -0
- package/native-host/src/codexHome.js +538 -0
- package/native-host/src/codexModels.js +247 -0
- package/native-host/src/codexPrompt.js +192 -0
- package/native-host/src/codexPromptAssembly.js +411 -0
- package/native-host/src/codexSessionRunner.js +1247 -0
- package/native-host/src/commandApproval.js +914 -0
- package/native-host/src/debugLog.js +78 -0
- package/native-host/src/diffEngine.js +247 -0
- package/native-host/src/index.js +132 -0
- package/native-host/src/launcher.js +81 -0
- package/native-host/src/localSkills.js +476 -0
- package/native-host/src/manifest.js +226 -0
- package/native-host/src/mirrorSensitiveScan.js +119 -0
- package/native-host/src/mirrorWorkspace.js +1019 -0
- package/native-host/src/nativeDoctor.js +826 -0
- package/native-host/src/nativeEnvironment.js +315 -0
- package/native-host/src/nativeHostPlatform.js +112 -0
- package/native-host/src/nativeMessaging.js +60 -0
- package/native-host/src/nativeQuotas.js +294 -0
- package/native-host/src/nativeResponseBudget.js +194 -0
- package/native-host/src/runtimeInstaller.js +357 -0
- package/native-host/src/taskRunner.js +3 -0
- package/native-host/src/taskRunnerRuntime.js +1083 -0
- package/native-host/src/textPatch.js +287 -0
- package/package.json +40 -0
- package/scripts/codex-json-agent.mjs +269 -0
- package/scripts/install-native-host.mjs +255 -0
- package/scripts/npm-package-files-v1.1.1.txt +52 -0
- package/scripts/uninstall-native-host.mjs +298 -0
- package/scripts/verify-npm-package.mjs +296 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const NATIVE_REQUEST_QUOTAS = Object.freeze({
|
|
4
|
+
maxProjectFiles: 1000,
|
|
5
|
+
maxProjectTextBytes: 32 * 1024 * 1024,
|
|
6
|
+
maxProjectBinaryBytes: 32 * 1024 * 1024,
|
|
7
|
+
maxOperations: 1000,
|
|
8
|
+
maxCompileLogBytes: 512 * 1024,
|
|
9
|
+
maxAttachmentCount: 8,
|
|
10
|
+
maxAttachmentBytes: 12 * 1024 * 1024,
|
|
11
|
+
maxAttachmentTotalBytes: 8 * 12 * 1024 * 1024,
|
|
12
|
+
maxSkillContentBytes: 64 * 1024
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
function validateNativeRequestQuotas(request = {}) {
|
|
16
|
+
const params = request.params || {};
|
|
17
|
+
switch (request.method) {
|
|
18
|
+
case 'codex.run':
|
|
19
|
+
return firstQuotaViolation([
|
|
20
|
+
validateProjectSnapshotQuota(params.project),
|
|
21
|
+
validateOperationListQuota(params.fileOverlays, 'fileOverlays'),
|
|
22
|
+
validateFilePayloadQuota(params.fileOverlays, 'fileOverlays'),
|
|
23
|
+
validateCompileLogQuota(params),
|
|
24
|
+
validateAttachmentQuota(params.attachments),
|
|
25
|
+
validateSkillContentQuota(params.skillContent)
|
|
26
|
+
]);
|
|
27
|
+
case 'mirror.sync':
|
|
28
|
+
return validateProjectSnapshotQuota(params.project);
|
|
29
|
+
case 'mirror.patchFiles':
|
|
30
|
+
return firstQuotaViolation([
|
|
31
|
+
validateOperationListQuota(params.files, 'files'),
|
|
32
|
+
validatePatchFileTextQuota(params.files)
|
|
33
|
+
]);
|
|
34
|
+
case 'task.run':
|
|
35
|
+
return firstQuotaViolation([
|
|
36
|
+
validateProjectSnapshotQuota(params.project),
|
|
37
|
+
validateOperationListQuota(params.fileOverlays, 'fileOverlays'),
|
|
38
|
+
validateFilePayloadQuota(params.fileOverlays, 'fileOverlays'),
|
|
39
|
+
validateOperationListQuota(params.proposedOperations, 'proposedOperations'),
|
|
40
|
+
validateOperationPayloadQuota(params.proposedOperations, 'proposedOperations'),
|
|
41
|
+
validateOperationListQuota(params.operations, 'operations'),
|
|
42
|
+
validateOperationPayloadQuota(params.operations, 'operations'),
|
|
43
|
+
validateCompileLogQuota(params)
|
|
44
|
+
]);
|
|
45
|
+
case 'skills.install':
|
|
46
|
+
return validateSkillContentQuota(params.content);
|
|
47
|
+
default:
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function firstQuotaViolation(violations) {
|
|
53
|
+
return violations.find(Boolean) || null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function validateProjectSnapshotQuota(project = {}) {
|
|
57
|
+
const files = Array.isArray(project?.files) ? project.files : [];
|
|
58
|
+
if (files.length > NATIVE_REQUEST_QUOTAS.maxProjectFiles) {
|
|
59
|
+
return quotaViolation('project.files', NATIVE_REQUEST_QUOTAS.maxProjectFiles, files.length, 'too many project files');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return validateFilePayloadQuota(files, 'project.files');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function validateFilePayloadQuota(files, fieldPrefix) {
|
|
66
|
+
let textBytes = 0;
|
|
67
|
+
let binaryBytes = 0;
|
|
68
|
+
for (const file of Array.isArray(files) ? files : []) {
|
|
69
|
+
for (const key of ['content', 'nextContent']) {
|
|
70
|
+
if (typeof file?.[key] === 'string') {
|
|
71
|
+
textBytes += Buffer.byteLength(file[key], 'utf8');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (typeof file?.contentBase64 === 'string') {
|
|
75
|
+
binaryBytes += getDeclaredOrEstimatedBinaryBytes(file);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (textBytes > NATIVE_REQUEST_QUOTAS.maxProjectTextBytes) {
|
|
79
|
+
return quotaViolation(
|
|
80
|
+
`${fieldPrefix}.content`,
|
|
81
|
+
NATIVE_REQUEST_QUOTAS.maxProjectTextBytes,
|
|
82
|
+
textBytes,
|
|
83
|
+
`${fieldPrefix} text is too large`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (binaryBytes > NATIVE_REQUEST_QUOTAS.maxProjectBinaryBytes) {
|
|
87
|
+
return quotaViolation(
|
|
88
|
+
`${fieldPrefix}.contentBase64`,
|
|
89
|
+
NATIVE_REQUEST_QUOTAS.maxProjectBinaryBytes,
|
|
90
|
+
binaryBytes,
|
|
91
|
+
`${fieldPrefix} binary payload is too large`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function validatePatchFileTextQuota(files) {
|
|
98
|
+
let textBytes = 0;
|
|
99
|
+
for (const file of Array.isArray(files) ? files : []) {
|
|
100
|
+
for (const key of ['nextContent', 'content']) {
|
|
101
|
+
if (typeof file?.[key] === 'string') {
|
|
102
|
+
textBytes += Buffer.byteLength(file[key], 'utf8');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return textBytes > NATIVE_REQUEST_QUOTAS.maxProjectTextBytes
|
|
107
|
+
? quotaViolation('files.content', NATIVE_REQUEST_QUOTAS.maxProjectTextBytes, textBytes, 'patch text is too large')
|
|
108
|
+
: null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function validateOperationListQuota(value, field) {
|
|
112
|
+
const operations = Array.isArray(value) ? value : [];
|
|
113
|
+
return operations.length > NATIVE_REQUEST_QUOTAS.maxOperations
|
|
114
|
+
? quotaViolation(field, NATIVE_REQUEST_QUOTAS.maxOperations, operations.length, 'too many operations')
|
|
115
|
+
: null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function validateOperationPayloadQuota(value, fieldPrefix) {
|
|
119
|
+
const operations = Array.isArray(value) ? value : [];
|
|
120
|
+
const { textBytes, binaryBytes } = measureOperationPayloads(operations);
|
|
121
|
+
if (textBytes > NATIVE_REQUEST_QUOTAS.maxProjectTextBytes) {
|
|
122
|
+
return quotaViolation(
|
|
123
|
+
`${fieldPrefix}.content`,
|
|
124
|
+
NATIVE_REQUEST_QUOTAS.maxProjectTextBytes,
|
|
125
|
+
textBytes,
|
|
126
|
+
`${fieldPrefix} text payload is too large`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (binaryBytes > NATIVE_REQUEST_QUOTAS.maxProjectBinaryBytes) {
|
|
130
|
+
return quotaViolation(
|
|
131
|
+
`${fieldPrefix}.contentBase64`,
|
|
132
|
+
NATIVE_REQUEST_QUOTAS.maxProjectBinaryBytes,
|
|
133
|
+
binaryBytes,
|
|
134
|
+
`${fieldPrefix} binary payload is too large`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function measureOperationPayloads(operations = []) {
|
|
141
|
+
const totals = { textBytes: 0, binaryBytes: 0 };
|
|
142
|
+
const seen = new Set();
|
|
143
|
+
for (const operation of operations) {
|
|
144
|
+
measureOperationPayloadValue(operation, '', totals, seen);
|
|
145
|
+
}
|
|
146
|
+
return totals;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function measureOperationPayloadValue(value, key, totals, seen) {
|
|
150
|
+
if (value === null || value === undefined) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (typeof value === 'string') {
|
|
154
|
+
if (isBase64PayloadKey(key)) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
totals.textBytes += Buffer.byteLength(value, 'utf8');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (typeof value !== 'object') {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (seen.has(value)) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
seen.add(value);
|
|
167
|
+
|
|
168
|
+
if (typeof value.contentBase64 === 'string') {
|
|
169
|
+
totals.binaryBytes += getDeclaredOrEstimatedBinaryBytes(value);
|
|
170
|
+
} else if (isBinarySizedOperationPayload(value)) {
|
|
171
|
+
totals.binaryBytes += Number(value.size);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (Array.isArray(value)) {
|
|
175
|
+
for (const item of value) {
|
|
176
|
+
measureOperationPayloadValue(item, '', totals, seen);
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (const [childKey, childValue] of Object.entries(value)) {
|
|
182
|
+
if (isBase64PayloadKey(childKey)) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
measureOperationPayloadValue(childValue, childKey, totals, seen);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function isBase64PayloadKey(key) {
|
|
190
|
+
return key === 'contentBase64';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function isBinarySizedOperationPayload(value = {}) {
|
|
194
|
+
const size = Number(value.size);
|
|
195
|
+
if (!Number.isFinite(size) || size <= 0) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
return String(value.type || '').includes('binary')
|
|
199
|
+
|| String(value.kind || '') === 'binary'
|
|
200
|
+
|| Object.prototype.hasOwnProperty.call(value, 'contentBase64');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function validateCompileLogQuota(params = {}) {
|
|
204
|
+
const bytes = Buffer.byteLength(String(params.compileLog || ''), 'utf8')
|
|
205
|
+
+ byteLengthOfStringArray(params.compileErrors)
|
|
206
|
+
+ byteLengthOfStringArray(params.compileWarnings);
|
|
207
|
+
return bytes > NATIVE_REQUEST_QUOTAS.maxCompileLogBytes
|
|
208
|
+
? quotaViolation('compileLog', NATIVE_REQUEST_QUOTAS.maxCompileLogBytes, bytes, 'compile log context is too large')
|
|
209
|
+
: null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function validateAttachmentQuota(attachments) {
|
|
213
|
+
const items = Array.isArray(attachments) ? attachments : [];
|
|
214
|
+
if (items.length > NATIVE_REQUEST_QUOTAS.maxAttachmentCount) {
|
|
215
|
+
return quotaViolation('attachments', NATIVE_REQUEST_QUOTAS.maxAttachmentCount, items.length, 'too many attachments');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
let totalBytes = 0;
|
|
219
|
+
for (const item of items) {
|
|
220
|
+
const bytes = getDeclaredOrEstimatedBinaryBytes(item);
|
|
221
|
+
if (bytes > NATIVE_REQUEST_QUOTAS.maxAttachmentBytes) {
|
|
222
|
+
return quotaViolation(
|
|
223
|
+
'attachments.contentBase64',
|
|
224
|
+
NATIVE_REQUEST_QUOTAS.maxAttachmentBytes,
|
|
225
|
+
bytes,
|
|
226
|
+
'attachment payload is too large'
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
totalBytes += bytes;
|
|
230
|
+
}
|
|
231
|
+
return totalBytes > NATIVE_REQUEST_QUOTAS.maxAttachmentTotalBytes
|
|
232
|
+
? quotaViolation('attachments', NATIVE_REQUEST_QUOTAS.maxAttachmentTotalBytes, totalBytes, 'attachment payload is too large')
|
|
233
|
+
: null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function validateSkillContentQuota(content) {
|
|
237
|
+
if (content === undefined || content === null) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
const bytes = typeof content === 'string'
|
|
241
|
+
? Buffer.byteLength(content, 'utf8')
|
|
242
|
+
: Buffer.byteLength(String(content), 'utf8');
|
|
243
|
+
return bytes > NATIVE_REQUEST_QUOTAS.maxSkillContentBytes
|
|
244
|
+
? quotaViolation('content', NATIVE_REQUEST_QUOTAS.maxSkillContentBytes, bytes, 'skill content is too large')
|
|
245
|
+
: null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function getDeclaredOrEstimatedBinaryBytes(value = {}) {
|
|
249
|
+
const declared = Number(value.size);
|
|
250
|
+
const estimated = typeof value.contentBase64 === 'string'
|
|
251
|
+
? estimateBase64DecodedBytes(value.contentBase64)
|
|
252
|
+
: 0;
|
|
253
|
+
return Math.max(Number.isFinite(declared) && declared > 0 ? declared : 0, estimated);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function estimateBase64DecodedBytes(value) {
|
|
257
|
+
const clean = String(value || '').replace(/\s+/g, '');
|
|
258
|
+
if (!clean) {
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
const padding = clean.endsWith('==') ? 2 : clean.endsWith('=') ? 1 : 0;
|
|
262
|
+
return Math.max(0, Math.floor(clean.length * 3 / 4) - padding);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function byteLengthOfStringArray(value) {
|
|
266
|
+
return (Array.isArray(value) ? value : [])
|
|
267
|
+
.reduce((sum, item) => sum + Buffer.byteLength(String(item || ''), 'utf8'), 0);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function quotaViolation(field, limit, actual, reason) {
|
|
271
|
+
return {
|
|
272
|
+
field,
|
|
273
|
+
limit,
|
|
274
|
+
actual,
|
|
275
|
+
reason
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
module.exports = {
|
|
280
|
+
NATIVE_REQUEST_QUOTAS,
|
|
281
|
+
estimateBase64DecodedBytes,
|
|
282
|
+
firstQuotaViolation,
|
|
283
|
+
measureOperationPayloads,
|
|
284
|
+
quotaViolation,
|
|
285
|
+
validateAttachmentQuota,
|
|
286
|
+
validateCompileLogQuota,
|
|
287
|
+
validateFilePayloadQuota,
|
|
288
|
+
validateNativeRequestQuotas,
|
|
289
|
+
validateOperationListQuota,
|
|
290
|
+
validateOperationPayloadQuota,
|
|
291
|
+
validatePatchFileTextQuota,
|
|
292
|
+
validateProjectSnapshotQuota,
|
|
293
|
+
validateSkillContentQuota
|
|
294
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { MAX_NATIVE_OUTPUT_MESSAGE_BYTES } = require('./nativeMessaging');
|
|
4
|
+
|
|
5
|
+
const NATIVE_OK_RESPONSE_BUDGET_BYTES = MAX_NATIVE_OUTPUT_MESSAGE_BYTES - (64 * 1024);
|
|
6
|
+
const NATIVE_RESPONSE_ESTIMATE_ID = 'response-budget-estimate';
|
|
7
|
+
|
|
8
|
+
function enforceNativeOkResponseBudget(result) {
|
|
9
|
+
const next = {
|
|
10
|
+
...result,
|
|
11
|
+
syncChanges: Array.isArray(result.syncChanges) ? [...result.syncChanges] : [],
|
|
12
|
+
unsupportedChanges: Array.isArray(result.unsupportedChanges) ? [...result.unsupportedChanges] : []
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
while (estimateNativeOkResponseBytes(next) > NATIVE_OK_RESPONSE_BUDGET_BYTES) {
|
|
16
|
+
const textIndex = findLargestInlineTextChangeIndex(next.syncChanges);
|
|
17
|
+
if (textIndex >= 0) {
|
|
18
|
+
const [change] = next.syncChanges.splice(textIndex, 1);
|
|
19
|
+
next.unsupportedChanges.push(buildOversizedTextPayloadChange(change));
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const binaryIndex = findLargestInlineBinaryChangeIndex(next.syncChanges);
|
|
24
|
+
if (binaryIndex >= 0) {
|
|
25
|
+
const [change] = next.syncChanges.splice(binaryIndex, 1);
|
|
26
|
+
next.unsupportedChanges.push(buildOversizedBinaryPayloadChange(change));
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (typeof next.assistantMessage === 'string' && next.assistantMessage) {
|
|
31
|
+
next.assistantMessage = shrinkAssistantMessageForNativeResponse(next.assistantMessage, next);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return compactNativeResponseForBudget(next);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
next.unsupportedChanges.sort((left, right) => String(left.path || '').localeCompare(String(right.path || '')));
|
|
39
|
+
return next;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function estimateNativeOkResponseBytes(result) {
|
|
43
|
+
return Buffer.byteLength(JSON.stringify({
|
|
44
|
+
id: NATIVE_RESPONSE_ESTIMATE_ID,
|
|
45
|
+
ok: true,
|
|
46
|
+
result
|
|
47
|
+
}), 'utf8');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function findLargestInlineTextChangeIndex(changes = []) {
|
|
51
|
+
let largestIndex = -1;
|
|
52
|
+
let largestBytes = -1;
|
|
53
|
+
for (let index = 0; index < changes.length; index += 1) {
|
|
54
|
+
const change = changes[index];
|
|
55
|
+
if (!hasInlineTextPayload(change)) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const bytes = Buffer.byteLength(JSON.stringify(change), 'utf8');
|
|
59
|
+
if (bytes > largestBytes) {
|
|
60
|
+
largestBytes = bytes;
|
|
61
|
+
largestIndex = index;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return largestIndex;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function hasInlineTextPayload(change) {
|
|
68
|
+
return change
|
|
69
|
+
&& change.type !== 'binary-create'
|
|
70
|
+
&& change.type !== 'overwrite-binary'
|
|
71
|
+
&& (
|
|
72
|
+
typeof change.content === 'string'
|
|
73
|
+
|| typeof change.previousContent === 'string'
|
|
74
|
+
|| Array.isArray(change.diff)
|
|
75
|
+
|| Array.isArray(change.patches)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function findLargestInlineBinaryChangeIndex(changes = []) {
|
|
80
|
+
let largestIndex = -1;
|
|
81
|
+
let largestBytes = -1;
|
|
82
|
+
for (let index = 0; index < changes.length; index += 1) {
|
|
83
|
+
const change = changes[index];
|
|
84
|
+
if (
|
|
85
|
+
(change?.type !== 'binary-create' && change?.type !== 'overwrite-binary')
|
|
86
|
+
|| typeof change.contentBase64 !== 'string'
|
|
87
|
+
) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const bytes = Buffer.byteLength(change.contentBase64, 'utf8');
|
|
91
|
+
if (bytes > largestBytes) {
|
|
92
|
+
largestBytes = bytes;
|
|
93
|
+
largestIndex = index;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return largestIndex;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function buildOversizedTextPayloadChange(change = {}) {
|
|
100
|
+
const contentBytes = typeof change.content === 'string' ? Buffer.byteLength(change.content, 'utf8') : 0;
|
|
101
|
+
const previousBytes = typeof change.previousContent === 'string' ? Buffer.byteLength(change.previousContent, 'utf8') : 0;
|
|
102
|
+
return {
|
|
103
|
+
type: 'unsupported-local-file',
|
|
104
|
+
path: change.path || '',
|
|
105
|
+
reason: 'text_payload_exceeds_native_message_limit',
|
|
106
|
+
size: contentBytes,
|
|
107
|
+
previousSize: previousBytes,
|
|
108
|
+
attemptedChangeType: change.type || 'write',
|
|
109
|
+
previousExists: change.previousExists === true,
|
|
110
|
+
nativeOutputLimit: MAX_NATIVE_OUTPUT_MESSAGE_BYTES,
|
|
111
|
+
responseBudget: NATIVE_OK_RESPONSE_BUDGET_BYTES,
|
|
112
|
+
guidance: 'The text writeback payload is too large for native messaging after adding review diff and patch data. Apply the change in Overleaf directly, split the edit into smaller files, or rerun with a narrower focus.'
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function buildOversizedBinaryPayloadChange(change = {}) {
|
|
117
|
+
return {
|
|
118
|
+
type: 'unsupported-local-file',
|
|
119
|
+
path: change.path || '',
|
|
120
|
+
reason: 'binary_payload_exceeds_native_message_limit',
|
|
121
|
+
size: Number.isFinite(Number(change.size)) ? Number(change.size) : estimateBase64Size(change.contentBase64),
|
|
122
|
+
attemptedChangeType: change.type || 'binary-create',
|
|
123
|
+
previousExists: change.previousExists === true,
|
|
124
|
+
previousKind: change.previousKind || '',
|
|
125
|
+
previousSize: change.previousSize,
|
|
126
|
+
nativeOutputLimit: MAX_NATIVE_OUTPUT_MESSAGE_BYTES,
|
|
127
|
+
responseBudget: NATIVE_OK_RESPONSE_BUDGET_BYTES,
|
|
128
|
+
guidance: 'The binary writeback payload would exceed the native messaging response budget. Update the file in Overleaf directly or reduce the asset size before retrying.'
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function estimateBase64Size(value) {
|
|
133
|
+
const clean = String(value || '').replace(/\s+/g, '');
|
|
134
|
+
if (!clean) {
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
const padding = clean.endsWith('==') ? 2 : clean.endsWith('=') ? 1 : 0;
|
|
138
|
+
return Math.max(0, Math.floor(clean.length * 3 / 4) - padding);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function shrinkAssistantMessageForNativeResponse(message, result) {
|
|
142
|
+
const currentBytes = Buffer.byteLength(message, 'utf8');
|
|
143
|
+
const overflowBytes = estimateNativeOkResponseBytes(result) - NATIVE_OK_RESPONSE_BUDGET_BYTES;
|
|
144
|
+
const targetBytes = Math.max(0, currentBytes - overflowBytes - 1024);
|
|
145
|
+
return truncateUtf8Text(message, targetBytes, '\n...[truncated to fit native response budget]');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function truncateUtf8Text(value, maxBytes, suffix = '') {
|
|
149
|
+
const text = String(value || '');
|
|
150
|
+
if (maxBytes <= 0) {
|
|
151
|
+
return '';
|
|
152
|
+
}
|
|
153
|
+
if (Buffer.byteLength(text, 'utf8') <= maxBytes) {
|
|
154
|
+
return text;
|
|
155
|
+
}
|
|
156
|
+
const suffixBytes = Buffer.byteLength(suffix, 'utf8');
|
|
157
|
+
const suffixToUse = suffixBytes < maxBytes ? suffix : '';
|
|
158
|
+
const bodyLimit = Math.max(0, maxBytes - Buffer.byteLength(suffixToUse, 'utf8'));
|
|
159
|
+
let output = '';
|
|
160
|
+
let outputBytes = 0;
|
|
161
|
+
for (const char of text) {
|
|
162
|
+
const charBytes = Buffer.byteLength(char, 'utf8');
|
|
163
|
+
if (outputBytes + charBytes > bodyLimit) {
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
output += char;
|
|
167
|
+
outputBytes += charBytes;
|
|
168
|
+
}
|
|
169
|
+
return `${output}${suffixToUse}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function compactNativeResponseForBudget(result) {
|
|
173
|
+
return {
|
|
174
|
+
...result,
|
|
175
|
+
assistantMessage: truncateUtf8Text(result.assistantMessage || '', 4096),
|
|
176
|
+
syncChanges: [],
|
|
177
|
+
unsupportedChanges: [{
|
|
178
|
+
type: 'unsupported-local-file',
|
|
179
|
+
path: '',
|
|
180
|
+
reason: 'native_response_payload_exceeds_native_message_limit',
|
|
181
|
+
nativeOutputLimit: MAX_NATIVE_OUTPUT_MESSAGE_BYTES,
|
|
182
|
+
responseBudget: NATIVE_OK_RESPONSE_BUDGET_BYTES,
|
|
183
|
+
guidance: 'The native response payload remained too large after inline payload degradation. Rerun with a narrower focus or apply the local changes directly in Overleaf.'
|
|
184
|
+
}]
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
NATIVE_OK_RESPONSE_BUDGET_BYTES,
|
|
190
|
+
compactNativeResponseForBudget,
|
|
191
|
+
enforceNativeOkResponseBudget,
|
|
192
|
+
estimateNativeOkResponseBytes,
|
|
193
|
+
truncateUtf8Text
|
|
194
|
+
};
|