@opengeni/runtime 0.14.1 → 0.14.15
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/dist/{chunk-4YJ5Z52Z.js → chunk-7E2ZSCZK.js} +6 -6
- package/dist/{chunk-4YJ5Z52Z.js.map → chunk-7E2ZSCZK.js.map} +1 -1
- package/dist/{chunk-HHWIZHQM.js → chunk-GNEGANUF.js} +942 -300
- package/dist/chunk-GNEGANUF.js.map +1 -0
- package/dist/codex-tool-search.d.ts +100 -0
- package/dist/context-compaction.d.ts +236 -0
- package/dist/history-sanitizer.d.ts +320 -0
- package/dist/index.d.ts +186 -718
- package/dist/index.js +612 -126
- package/dist/index.js.map +1 -1
- package/dist/{localSources-T3HZ6KY4.js → localSources-BXHBKGF4.js} +2 -2
- package/dist/mcp-network.d.ts +32 -35
- package/dist/metrics.d.ts +33 -0
- package/dist/sandbox/capabilities.d.ts +11 -0
- package/dist/sandbox/channel-a.d.ts +226 -0
- package/dist/sandbox/display-stack.d.ts +99 -0
- package/dist/sandbox/errors.d.ts +16 -0
- package/dist/sandbox/exec-banner.d.ts +19 -0
- package/dist/sandbox/index.d.ts +190 -6
- package/dist/sandbox/index.js +20 -4
- package/dist/sandbox/op-correlation.d.ts +24 -0
- package/dist/sandbox/provider-errors.d.ts +32 -0
- package/dist/sandbox/provider-operation-gate.d.ts +12 -0
- package/dist/sandbox/providers/blaxel.d.ts +2 -0
- package/dist/sandbox/providers/cloudflare.d.ts +2 -0
- package/dist/sandbox/providers/daytona.d.ts +2 -0
- package/dist/sandbox/providers/docker.d.ts +2 -0
- package/dist/sandbox/providers/e2b.d.ts +2 -0
- package/dist/sandbox/providers/index.d.ts +11 -0
- package/dist/sandbox/providers/local.d.ts +2 -0
- package/dist/sandbox/providers/modal.d.ts +132 -0
- package/dist/sandbox/providers/none.d.ts +2 -0
- package/dist/sandbox/providers/runloop.d.ts +2 -0
- package/dist/sandbox/providers/selfhosted.d.ts +2 -0
- package/dist/sandbox/providers/types.d.ts +30 -0
- package/dist/sandbox/providers/vercel.d.ts +2 -0
- package/dist/sandbox/recording.d.ts +80 -0
- package/dist/sandbox/routing/backend-resolver.d.ts +173 -0
- package/dist/sandbox/routing/routing-session.d.ts +401 -0
- package/dist/sandbox/run-credentials.d.ts +65 -0
- package/dist/sandbox/select.d.ts +120 -0
- package/dist/sandbox/selfhosted/capabilities.d.ts +91 -0
- package/dist/sandbox/selfhosted/control-rpc.d.ts +175 -0
- package/dist/sandbox/selfhosted/fault-rendering.d.ts +40 -0
- package/dist/sandbox/selfhosted/op-observer.d.ts +40 -0
- package/dist/sandbox/selfhosted/op-stream.d.ts +104 -0
- package/dist/sandbox/selfhosted/op-testing.d.ts +108 -0
- package/dist/sandbox/selfhosted/op-transport.d.ts +60 -0
- package/dist/sandbox/selfhosted/retry-policy.d.ts +106 -0
- package/dist/sandbox/selfhosted/session.d.ts +552 -0
- package/dist/sandbox/selfhosted/testing.d.ts +48 -0
- package/dist/sandbox/stream-port.d.ts +80 -0
- package/dist/sandbox/stream-token.d.ts +44 -0
- package/dist/sandbox/terminal-server.d.ts +67 -0
- package/dist/sandbox/toolspace-token.d.ts +23 -0
- package/dist/sandbox/turn-tool-cancellation.d.ts +72 -0
- package/dist/sandbox/workspace-archive.d.ts +39 -0
- package/dist/sandbox-computer.d.ts +227 -0
- package/dist/screenshot-error-card.d.ts +1 -0
- package/dist/skill-library.d.ts +10 -12
- package/dist/usage-telemetry.d.ts +45 -0
- package/dist/workspace-skills.d.ts +31 -0
- package/package.json +7 -7
- package/src/context-compaction.ts +600 -6
- package/src/history-sanitizer.ts +10 -92
- package/src/index.ts +301 -62
- package/src/sandbox/channel-a.ts +267 -81
- package/src/sandbox/index.ts +56 -94
- package/src/sandbox/provider-operation-gate.ts +110 -0
- package/src/sandbox/providers/docker.ts +13 -1
- package/src/sandbox/providers/modal.ts +433 -3
- package/src/sandbox/routing/routing-session.ts +84 -8
- package/src/sandbox/selfhosted/session.ts +7 -6
- package/src/sandbox/turn-tool-cancellation.ts +47 -7
- package/src/sandbox/workspace-archive.ts +176 -73
- package/src/sandbox-computer.ts +25 -75
- package/dist/chunk-HHWIZHQM.js.map +0 -1
- package/dist/index-BraazrAO.d.ts +0 -2931
- /package/dist/{localSources-T3HZ6KY4.js.map → localSources-BXHBKGF4.js.map} +0 -0
|
@@ -27,6 +27,10 @@ export const SUMMARY_BUFFER_TOKENS = 20_000;
|
|
|
27
27
|
// A single cumulative budget for all retained real user messages, matching
|
|
28
28
|
// Codex core's build_compacted_history_with_limit (not a per-message allowance).
|
|
29
29
|
export const COMPACT_USER_MESSAGE_MAX_TOKENS = 20_000;
|
|
30
|
+
/** Codex CLI remote compaction v2 retained-message budget (codex-rs). */
|
|
31
|
+
export const REMOTE_V2_RETAINED_MESSAGE_TOKEN_BUDGET = 64_000;
|
|
32
|
+
export const REMOTE_COMPACTION_V2_IMPLEMENTATION = "responses_compaction_v2" as const;
|
|
33
|
+
export const REMOTE_COMPACTION_V2_BETA_FEATURE = "remote_compaction_v2" as const;
|
|
30
34
|
// 0.9: compact as LATE as possible — retained context is worth more than early
|
|
31
35
|
// headroom now that declared per-model windows are honest. Model-catalog
|
|
32
36
|
// explicit limits take precedence; the ratio is used for models without one.
|
|
@@ -118,15 +122,480 @@ export function estimateTextTokens(text: string): number {
|
|
|
118
122
|
return nonAsciiCodeUnits + Math.ceil(asciiCodeUnits / 4);
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
const IMAGE_LOW_DETAIL_TOKENS = 85;
|
|
126
|
+
const IMAGE_HIGH_DETAIL_BASE_TOKENS = 85;
|
|
127
|
+
const IMAGE_HIGH_DETAIL_TILE_TOKENS = 170;
|
|
128
|
+
const IMAGE_HIGH_DETAIL_TILE_SIZE = 512;
|
|
129
|
+
const IMAGE_HIGH_DETAIL_MAX_SIDE = 2_048;
|
|
130
|
+
const IMAGE_HIGH_DETAIL_TARGET_SHORT_SIDE = 768;
|
|
131
|
+
export const UNKNOWN_IMAGE_TOKENS = 4_096;
|
|
132
|
+
export const MAX_NATIVE_IMAGE_TOKENS = 8_192;
|
|
133
|
+
|
|
134
|
+
export type NativeImageEstimateReason = "dimensions" | "bounded_fallback";
|
|
135
|
+
export type NativeImageTokenEstimate = {
|
|
136
|
+
tokens: number;
|
|
137
|
+
width: number | null;
|
|
138
|
+
height: number | null;
|
|
139
|
+
detail: string;
|
|
140
|
+
reason: NativeImageEstimateReason;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export type ModelInputTokenBreakdown = {
|
|
144
|
+
totalTokens: number;
|
|
145
|
+
textTokens: number;
|
|
146
|
+
imageTokens: number;
|
|
147
|
+
imageCount: number;
|
|
148
|
+
imageFallbackCount: number;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
type ImageDescriptor = {
|
|
152
|
+
source: unknown;
|
|
153
|
+
detail: string;
|
|
154
|
+
width: number | null;
|
|
155
|
+
height: number | null;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
type NativeImageProjection = {
|
|
159
|
+
value: unknown;
|
|
160
|
+
imageTokens: number;
|
|
161
|
+
imageCount: number;
|
|
162
|
+
imageFallbackCount: number;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
function finitePositiveInteger(value: unknown): number | null {
|
|
166
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
167
|
+
? Math.floor(value)
|
|
168
|
+
: null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function dimensionsFromRecord(record: Record<string, unknown>): [number, number] | null {
|
|
172
|
+
const width = finitePositiveInteger(record.width);
|
|
173
|
+
const height = finitePositiveInteger(record.height);
|
|
174
|
+
if (width && height) return [width, height];
|
|
175
|
+
if (Array.isArray(record.dimensions) && record.dimensions.length >= 2) {
|
|
176
|
+
const dimensionsWidth = finitePositiveInteger(record.dimensions[0]);
|
|
177
|
+
const dimensionsHeight = finitePositiveInteger(record.dimensions[1]);
|
|
178
|
+
if (dimensionsWidth && dimensionsHeight) return [dimensionsWidth, dimensionsHeight];
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function dataUrlBytesPrefix(value: string, maxBytes = 262_144): Uint8Array | null {
|
|
184
|
+
const match = /^data:image\/[a-z0-9.+-]+;base64,([a-z0-9+/=\r\n]+)$/i.exec(value);
|
|
185
|
+
if (!match) return null;
|
|
186
|
+
try {
|
|
187
|
+
const maxBase64CodeUnits = Math.ceil(maxBytes / 3) * 4;
|
|
188
|
+
return new Uint8Array(Buffer.from(match[1]!.slice(0, maxBase64CodeUnits), "base64"));
|
|
189
|
+
} catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function base64BytesPrefix(value: string, maxBytes = 262_144): Uint8Array | null {
|
|
195
|
+
if (!/^[a-z0-9+/=\r\n]+$/i.test(value)) return null;
|
|
196
|
+
try {
|
|
197
|
+
const maxBase64CodeUnits = Math.ceil(maxBytes / 3) * 4;
|
|
198
|
+
return new Uint8Array(Buffer.from(value.slice(0, maxBase64CodeUnits), "base64"));
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function uint16BigEndian(bytes: Uint8Array, offset: number): number {
|
|
205
|
+
return ((bytes[offset] ?? 0) << 8) | (bytes[offset + 1] ?? 0);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function uint32BigEndian(bytes: Uint8Array, offset: number): number {
|
|
209
|
+
return (
|
|
210
|
+
((bytes[offset] ?? 0) * 0x1000000 +
|
|
211
|
+
((bytes[offset + 1] ?? 0) << 16) +
|
|
212
|
+
((bytes[offset + 2] ?? 0) << 8) +
|
|
213
|
+
(bytes[offset + 3] ?? 0)) >>>
|
|
214
|
+
0
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function crc32(bytes: Uint8Array, start: number, end: number): number {
|
|
219
|
+
let crc = 0xffff_ffff;
|
|
220
|
+
for (let offset = start; offset < end; offset += 1) {
|
|
221
|
+
crc ^= bytes[offset] ?? 0;
|
|
222
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
223
|
+
crc = (crc >>> 1) ^ (crc & 1 ? 0xedb8_8320 : 0);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return (crc ^ 0xffff_ffff) >>> 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function dimensionsFromImageBytes(bytes: Uint8Array): [number, number] | null {
|
|
230
|
+
// PNG IHDR: trust geometry only from a complete first IHDR chunk whose CRC32
|
|
231
|
+
// authenticates the 4-byte chunk type plus 13-byte data. This inspects exactly
|
|
232
|
+
// the bounded 33-byte prefix and never decodes the rest of the image.
|
|
233
|
+
if (
|
|
234
|
+
bytes.length >= 33 &&
|
|
235
|
+
bytes[0] === 0x89 &&
|
|
236
|
+
bytes[1] === 0x50 &&
|
|
237
|
+
bytes[2] === 0x4e &&
|
|
238
|
+
bytes[3] === 0x47 &&
|
|
239
|
+
bytes[4] === 0x0d &&
|
|
240
|
+
bytes[5] === 0x0a &&
|
|
241
|
+
bytes[6] === 0x1a &&
|
|
242
|
+
bytes[7] === 0x0a &&
|
|
243
|
+
uint32BigEndian(bytes, 8) === 13 &&
|
|
244
|
+
bytes[12] === 0x49 &&
|
|
245
|
+
bytes[13] === 0x48 &&
|
|
246
|
+
bytes[14] === 0x44 &&
|
|
247
|
+
bytes[15] === 0x52 &&
|
|
248
|
+
crc32(bytes, 12, 29) === uint32BigEndian(bytes, 29)
|
|
249
|
+
) {
|
|
250
|
+
const width = uint32BigEndian(bytes, 16);
|
|
251
|
+
const height = uint32BigEndian(bytes, 20);
|
|
252
|
+
return width > 0 && height > 0 ? [width, height] : null;
|
|
253
|
+
}
|
|
254
|
+
// GIF logical screen descriptor.
|
|
255
|
+
if (
|
|
256
|
+
bytes.length >= 10 &&
|
|
257
|
+
bytes[0] === 0x47 &&
|
|
258
|
+
bytes[1] === 0x49 &&
|
|
259
|
+
bytes[2] === 0x46 &&
|
|
260
|
+
bytes[3] === 0x38
|
|
261
|
+
) {
|
|
262
|
+
const width = (bytes[6] ?? 0) | ((bytes[7] ?? 0) << 8);
|
|
263
|
+
const height = (bytes[8] ?? 0) | ((bytes[9] ?? 0) << 8);
|
|
264
|
+
return width > 0 && height > 0 ? [width, height] : null;
|
|
265
|
+
}
|
|
266
|
+
// WebP VP8X canvas size (24-bit little-endian, stored minus one).
|
|
267
|
+
if (
|
|
268
|
+
bytes.length >= 30 &&
|
|
269
|
+
bytes[0] === 0x52 &&
|
|
270
|
+
bytes[1] === 0x49 &&
|
|
271
|
+
bytes[2] === 0x46 &&
|
|
272
|
+
bytes[3] === 0x46 &&
|
|
273
|
+
bytes[8] === 0x57 &&
|
|
274
|
+
bytes[9] === 0x45 &&
|
|
275
|
+
bytes[10] === 0x42 &&
|
|
276
|
+
bytes[11] === 0x50 &&
|
|
277
|
+
bytes[12] === 0x56 &&
|
|
278
|
+
bytes[13] === 0x50 &&
|
|
279
|
+
bytes[14] === 0x38 &&
|
|
280
|
+
bytes[15] === 0x58
|
|
281
|
+
) {
|
|
282
|
+
const width = 1 + (bytes[24] ?? 0) + ((bytes[25] ?? 0) << 8) + ((bytes[26] ?? 0) << 16);
|
|
283
|
+
const height = 1 + (bytes[27] ?? 0) + ((bytes[28] ?? 0) << 8) + ((bytes[29] ?? 0) << 16);
|
|
284
|
+
return width > 0 && height > 0 ? [width, height] : null;
|
|
285
|
+
}
|
|
286
|
+
// JPEG SOF markers can appear after variable-length metadata segments.
|
|
287
|
+
if (bytes.length >= 4 && bytes[0] === 0xff && bytes[1] === 0xd8) {
|
|
288
|
+
let offset = 2;
|
|
289
|
+
while (offset + 8 < bytes.length) {
|
|
290
|
+
if (bytes[offset] !== 0xff) {
|
|
291
|
+
offset += 1;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const marker = bytes[offset + 1] ?? 0;
|
|
295
|
+
offset += 2;
|
|
296
|
+
if (marker === 0xd8 || marker === 0xd9 || marker === 0x01) continue;
|
|
297
|
+
if (marker >= 0xd0 && marker <= 0xd7) continue;
|
|
298
|
+
if (offset + 2 > bytes.length) break;
|
|
299
|
+
const segmentLength = uint16BigEndian(bytes, offset);
|
|
300
|
+
if (segmentLength < 2 || offset + segmentLength > bytes.length) break;
|
|
301
|
+
const isStartOfFrame =
|
|
302
|
+
(marker >= 0xc0 && marker <= 0xc3) ||
|
|
303
|
+
(marker >= 0xc5 && marker <= 0xc7) ||
|
|
304
|
+
(marker >= 0xc9 && marker <= 0xcb) ||
|
|
305
|
+
(marker >= 0xcd && marker <= 0xcf);
|
|
306
|
+
if (isStartOfFrame && segmentLength >= 7) {
|
|
307
|
+
const height = uint16BigEndian(bytes, offset + 3);
|
|
308
|
+
const width = uint16BigEndian(bytes, offset + 5);
|
|
309
|
+
return width > 0 && height > 0 ? [width, height] : null;
|
|
310
|
+
}
|
|
311
|
+
offset += segmentLength;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function dimensionsFromImageSource(source: unknown): [number, number] | null {
|
|
318
|
+
if (source instanceof Uint8Array) {
|
|
319
|
+
return dimensionsFromImageBytes(source);
|
|
320
|
+
}
|
|
321
|
+
if (typeof source === "string") {
|
|
322
|
+
const bytes = dataUrlBytesPrefix(source);
|
|
323
|
+
return bytes ? dimensionsFromImageBytes(bytes) : null;
|
|
324
|
+
}
|
|
325
|
+
if (!source || typeof source !== "object") return null;
|
|
326
|
+
const record = source as Record<string, unknown>;
|
|
327
|
+
const rawData = record.data;
|
|
328
|
+
const mediaType = record.mediaType ?? record.media_type;
|
|
329
|
+
const rawDataDimensions =
|
|
330
|
+
rawData instanceof Uint8Array
|
|
331
|
+
? dimensionsFromImageBytes(rawData)
|
|
332
|
+
: typeof rawData === "string" &&
|
|
333
|
+
typeof mediaType === "string" &&
|
|
334
|
+
mediaType.toLowerCase().startsWith("image/")
|
|
335
|
+
? dimensionsFromImageBytes(base64BytesPrefix(rawData) ?? new Uint8Array())
|
|
336
|
+
: null;
|
|
337
|
+
return (
|
|
338
|
+
dimensionsFromRecord(record) ??
|
|
339
|
+
rawDataDimensions ??
|
|
340
|
+
dimensionsFromImageSource(record.url) ??
|
|
341
|
+
dimensionsFromImageSource(rawData)
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function highDetailImageTokens(width: number, height: number): number {
|
|
346
|
+
let scaledWidth = width;
|
|
347
|
+
let scaledHeight = height;
|
|
348
|
+
const longest = Math.max(scaledWidth, scaledHeight);
|
|
349
|
+
if (longest > IMAGE_HIGH_DETAIL_MAX_SIDE) {
|
|
350
|
+
const scale = IMAGE_HIGH_DETAIL_MAX_SIDE / longest;
|
|
351
|
+
scaledWidth *= scale;
|
|
352
|
+
scaledHeight *= scale;
|
|
353
|
+
}
|
|
354
|
+
const shortest = Math.min(scaledWidth, scaledHeight);
|
|
355
|
+
if (shortest > IMAGE_HIGH_DETAIL_TARGET_SHORT_SIDE) {
|
|
356
|
+
const scale = IMAGE_HIGH_DETAIL_TARGET_SHORT_SIDE / shortest;
|
|
357
|
+
scaledWidth *= scale;
|
|
358
|
+
scaledHeight *= scale;
|
|
359
|
+
}
|
|
360
|
+
const tiles =
|
|
361
|
+
Math.ceil(scaledWidth / IMAGE_HIGH_DETAIL_TILE_SIZE) *
|
|
362
|
+
Math.ceil(scaledHeight / IMAGE_HIGH_DETAIL_TILE_SIZE);
|
|
363
|
+
return Math.min(
|
|
364
|
+
MAX_NATIVE_IMAGE_TOKENS,
|
|
365
|
+
IMAGE_HIGH_DETAIL_BASE_TOKENS + IMAGE_HIGH_DETAIL_TILE_TOKENS * Math.max(1, tiles),
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Provider-neutral local estimate for one native image. Inline bytes are used
|
|
371
|
+
* only to recover dimensions; their base64 length never becomes text tokens.
|
|
372
|
+
*/
|
|
373
|
+
export function estimateNativeImageTokens(input: {
|
|
374
|
+
source?: unknown;
|
|
375
|
+
width?: number | null;
|
|
376
|
+
height?: number | null;
|
|
377
|
+
detail?: unknown;
|
|
378
|
+
}): NativeImageTokenEstimate {
|
|
379
|
+
const explicitWidth = finitePositiveInteger(input.width);
|
|
380
|
+
const explicitHeight = finitePositiveInteger(input.height);
|
|
381
|
+
const sourceDimensions = dimensionsFromImageSource(input.source);
|
|
382
|
+
const width = explicitWidth ?? sourceDimensions?.[0] ?? null;
|
|
383
|
+
const height = explicitHeight ?? sourceDimensions?.[1] ?? null;
|
|
384
|
+
const detail =
|
|
385
|
+
typeof input.detail === "string" && input.detail.length > 0 ? input.detail : "auto";
|
|
386
|
+
if (detail === "low") {
|
|
387
|
+
return { tokens: IMAGE_LOW_DETAIL_TOKENS, width, height, detail, reason: "dimensions" };
|
|
388
|
+
}
|
|
389
|
+
if (!width || !height) {
|
|
390
|
+
return {
|
|
391
|
+
tokens: UNKNOWN_IMAGE_TOKENS,
|
|
392
|
+
width: null,
|
|
393
|
+
height: null,
|
|
394
|
+
detail,
|
|
395
|
+
reason: "bounded_fallback",
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
tokens: highDetailImageTokens(width, height),
|
|
400
|
+
width,
|
|
401
|
+
height,
|
|
402
|
+
detail,
|
|
403
|
+
reason: "dimensions",
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function imageDescriptor(record: Record<string, unknown>): ImageDescriptor | null {
|
|
408
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
409
|
+
if (!new Set(["input_image", "image_url", "image", "computer_screenshot"]).has(type)) {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
const imageUrl = record.image_url;
|
|
413
|
+
const nestedImageUrl =
|
|
414
|
+
imageUrl && typeof imageUrl === "object" ? (imageUrl as Record<string, unknown>).url : imageUrl;
|
|
415
|
+
const source =
|
|
416
|
+
record.image ??
|
|
417
|
+
record.imageUrl ??
|
|
418
|
+
nestedImageUrl ??
|
|
419
|
+
record.data ??
|
|
420
|
+
record.url ??
|
|
421
|
+
record.fileId ??
|
|
422
|
+
record.file_id ??
|
|
423
|
+
record.artifact ??
|
|
424
|
+
record.artifactReference;
|
|
425
|
+
const nestedDetail =
|
|
426
|
+
imageUrl && typeof imageUrl === "object"
|
|
427
|
+
? (imageUrl as Record<string, unknown>).detail
|
|
428
|
+
: undefined;
|
|
429
|
+
const dimensions =
|
|
430
|
+
dimensionsFromRecord(record) ??
|
|
431
|
+
(source && typeof source === "object"
|
|
432
|
+
? dimensionsFromRecord(source as Record<string, unknown>)
|
|
433
|
+
: null);
|
|
434
|
+
return {
|
|
435
|
+
source,
|
|
436
|
+
detail:
|
|
437
|
+
typeof record.detail === "string"
|
|
438
|
+
? record.detail
|
|
439
|
+
: typeof nestedDetail === "string"
|
|
440
|
+
? nestedDetail
|
|
441
|
+
: "auto",
|
|
442
|
+
width: dimensions?.[0] ?? null,
|
|
443
|
+
height: dimensions?.[1] ?? null,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function projectNativeImages(value: unknown, seen: WeakSet<object>): NativeImageProjection {
|
|
448
|
+
if (!value || typeof value !== "object") {
|
|
449
|
+
return { value, imageTokens: 0, imageCount: 0, imageFallbackCount: 0 };
|
|
450
|
+
}
|
|
451
|
+
if (seen.has(value)) {
|
|
452
|
+
return { value: "[circular]", imageTokens: 0, imageCount: 0, imageFallbackCount: 0 };
|
|
453
|
+
}
|
|
454
|
+
seen.add(value);
|
|
455
|
+
if (Array.isArray(value)) {
|
|
456
|
+
let imageTokens = 0;
|
|
457
|
+
let imageCount = 0;
|
|
458
|
+
let imageFallbackCount = 0;
|
|
459
|
+
const projected = value.map((entry) => {
|
|
460
|
+
const result = projectNativeImages(entry, seen);
|
|
461
|
+
imageTokens += result.imageTokens;
|
|
462
|
+
imageCount += result.imageCount;
|
|
463
|
+
imageFallbackCount += result.imageFallbackCount;
|
|
464
|
+
return result.value;
|
|
465
|
+
});
|
|
466
|
+
seen.delete(value);
|
|
467
|
+
return { value: projected, imageTokens, imageCount, imageFallbackCount };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const record = value as Record<string, unknown>;
|
|
471
|
+
const descriptor = imageDescriptor(record);
|
|
472
|
+
if (descriptor) {
|
|
473
|
+
const estimate = estimateNativeImageTokens(descriptor);
|
|
474
|
+
const projected: Record<string, unknown> = {};
|
|
475
|
+
const payloadKeys = new Set([
|
|
476
|
+
"image",
|
|
477
|
+
"image_url",
|
|
478
|
+
"imageUrl",
|
|
479
|
+
"data",
|
|
480
|
+
"url",
|
|
481
|
+
"fileId",
|
|
482
|
+
"file_id",
|
|
483
|
+
"artifact",
|
|
484
|
+
"artifactReference",
|
|
485
|
+
]);
|
|
486
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
487
|
+
projected[key] = payloadKeys.has(key) ? "[native image]" : entry;
|
|
488
|
+
}
|
|
489
|
+
projected.image_estimate = {
|
|
490
|
+
width: estimate.width,
|
|
491
|
+
height: estimate.height,
|
|
492
|
+
detail: estimate.detail,
|
|
493
|
+
reason: estimate.reason,
|
|
494
|
+
};
|
|
495
|
+
seen.delete(value);
|
|
496
|
+
return {
|
|
497
|
+
value: projected,
|
|
498
|
+
imageTokens: estimate.tokens,
|
|
499
|
+
imageCount: 1,
|
|
500
|
+
imageFallbackCount: estimate.reason === "bounded_fallback" ? 1 : 0,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
let imageTokens = 0;
|
|
505
|
+
let imageCount = 0;
|
|
506
|
+
let imageFallbackCount = 0;
|
|
507
|
+
const projected: Record<string, unknown> = {};
|
|
508
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
509
|
+
const result = projectNativeImages(entry, seen);
|
|
510
|
+
projected[key] = result.value;
|
|
511
|
+
imageTokens += result.imageTokens;
|
|
512
|
+
imageCount += result.imageCount;
|
|
513
|
+
imageFallbackCount += result.imageFallbackCount;
|
|
514
|
+
}
|
|
515
|
+
seen.delete(value);
|
|
516
|
+
return { value: projected, imageTokens, imageCount, imageFallbackCount };
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Codex CLI `estimate_reasoning_length`: opaque encrypted payloads are not
|
|
521
|
+
* model-visible as raw JSON/base64. Visible bytes ≈ `len * 3/4 - 650`.
|
|
522
|
+
*/
|
|
523
|
+
export function estimateOpaqueEncryptedModelVisibleBytes(encodedLen: number): number {
|
|
524
|
+
const length = Math.max(0, Math.floor(encodedLen));
|
|
525
|
+
return Math.max(0, Math.floor((length * 3) / 4) - 650);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Codex CLI bytes→tokens for opaque encrypted content (`ceil(bytes / 4)`). */
|
|
529
|
+
export function estimateOpaqueEncryptedTokens(encodedLen: number): number {
|
|
530
|
+
const bytes = estimateOpaqueEncryptedModelVisibleBytes(encodedLen);
|
|
531
|
+
return bytes === 0 ? 0 : Math.ceil(bytes / 4);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Length of opaque encrypted content that must use the Codex encrypted
|
|
536
|
+
* estimator instead of JSON.stringify (compaction blobs and reasoning).
|
|
537
|
+
*/
|
|
538
|
+
export function opaqueEncryptedContentLength(item: unknown): number | null {
|
|
539
|
+
if (!item || typeof item !== "object") return null;
|
|
540
|
+
const record = item as Record<string, unknown>;
|
|
541
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
542
|
+
if (
|
|
543
|
+
(type === "compaction" || type === "context_compaction" || type === "reasoning") &&
|
|
544
|
+
typeof record.encrypted_content === "string" &&
|
|
545
|
+
record.encrypted_content.length > 0
|
|
546
|
+
) {
|
|
547
|
+
return record.encrypted_content.length;
|
|
548
|
+
}
|
|
549
|
+
if (type === "reasoning") {
|
|
550
|
+
const providerData =
|
|
551
|
+
record.providerData && typeof record.providerData === "object"
|
|
552
|
+
? (record.providerData as Record<string, unknown>)
|
|
553
|
+
: null;
|
|
554
|
+
const nested =
|
|
555
|
+
providerData && typeof providerData.encrypted_content === "string"
|
|
556
|
+
? providerData.encrypted_content
|
|
557
|
+
: providerData && typeof providerData.encryptedContent === "string"
|
|
558
|
+
? providerData.encryptedContent
|
|
559
|
+
: null;
|
|
560
|
+
if (nested && nested.length > 0) return nested.length;
|
|
561
|
+
}
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** Native-image-aware item estimate for pre-call accounting and retained budgets. */
|
|
566
|
+
export function estimateItemTokenBreakdown(item: CompactionItem): ModelInputTokenBreakdown {
|
|
567
|
+
const opaqueLen = opaqueEncryptedContentLength(item);
|
|
568
|
+
if (opaqueLen !== null) {
|
|
569
|
+
// Match Codex: Compaction / encrypted reasoning use the opaque byte
|
|
570
|
+
// heuristic only — never JSON-stringify the ciphertext into the budget.
|
|
571
|
+
const textTokens = estimateOpaqueEncryptedTokens(opaqueLen);
|
|
572
|
+
return {
|
|
573
|
+
totalTokens: textTokens,
|
|
574
|
+
textTokens,
|
|
575
|
+
imageTokens: 0,
|
|
576
|
+
imageCount: 0,
|
|
577
|
+
imageFallbackCount: 0,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
const projected = projectNativeImages(item, new WeakSet<object>());
|
|
123
581
|
let text: string;
|
|
124
582
|
try {
|
|
125
|
-
text = JSON.stringify(
|
|
583
|
+
text = JSON.stringify(projected.value);
|
|
126
584
|
} catch {
|
|
127
|
-
text = String(
|
|
585
|
+
text = String(projected.value);
|
|
128
586
|
}
|
|
129
|
-
|
|
587
|
+
const textTokens = estimateTextTokens(text);
|
|
588
|
+
return {
|
|
589
|
+
totalTokens: textTokens + projected.imageTokens,
|
|
590
|
+
textTokens,
|
|
591
|
+
imageTokens: projected.imageTokens,
|
|
592
|
+
imageCount: projected.imageCount,
|
|
593
|
+
imageFallbackCount: projected.imageFallbackCount,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export function estimateItemTokens(item: CompactionItem): number {
|
|
598
|
+
return estimateItemTokenBreakdown(item).totalTokens;
|
|
130
599
|
}
|
|
131
600
|
|
|
132
601
|
export function estimateTokens(items: readonly CompactionItem[]): number {
|
|
@@ -137,6 +606,27 @@ export function estimateTokens(items: readonly CompactionItem[]): number {
|
|
|
137
606
|
return total;
|
|
138
607
|
}
|
|
139
608
|
|
|
609
|
+
export function estimateTokensBreakdown(
|
|
610
|
+
items: readonly CompactionItem[],
|
|
611
|
+
): ModelInputTokenBreakdown {
|
|
612
|
+
const total: ModelInputTokenBreakdown = {
|
|
613
|
+
totalTokens: 0,
|
|
614
|
+
textTokens: 0,
|
|
615
|
+
imageTokens: 0,
|
|
616
|
+
imageCount: 0,
|
|
617
|
+
imageFallbackCount: 0,
|
|
618
|
+
};
|
|
619
|
+
for (const item of items) {
|
|
620
|
+
const estimate = estimateItemTokenBreakdown(item);
|
|
621
|
+
total.totalTokens += estimate.totalTokens;
|
|
622
|
+
total.textTokens += estimate.textTokens;
|
|
623
|
+
total.imageTokens += estimate.imageTokens;
|
|
624
|
+
total.imageCount += estimate.imageCount;
|
|
625
|
+
total.imageFallbackCount += estimate.imageFallbackCount;
|
|
626
|
+
}
|
|
627
|
+
return total;
|
|
628
|
+
}
|
|
629
|
+
|
|
140
630
|
export function estimateSerializedValueTokens(value: unknown): number {
|
|
141
631
|
let serialized: string;
|
|
142
632
|
try {
|
|
@@ -164,6 +654,10 @@ export type CompleteModelInputEstimate = {
|
|
|
164
654
|
tokens: number;
|
|
165
655
|
source: "complete_estimate" | "provider_plus_local";
|
|
166
656
|
inputTokens: number;
|
|
657
|
+
inputTextTokens: number;
|
|
658
|
+
inputImageTokens: number;
|
|
659
|
+
inputImageCount: number;
|
|
660
|
+
inputImageFallbackCount: number;
|
|
167
661
|
instructionsTokens: number;
|
|
168
662
|
toolSchemaTokens: number;
|
|
169
663
|
appendedAfterModelTokens: number;
|
|
@@ -182,7 +676,8 @@ export function estimateCompleteModelInput(input: {
|
|
|
182
676
|
provider?: ProviderContextTokenSignal | null;
|
|
183
677
|
providerRequestFootprint?: CompleteModelInputFootprint | null;
|
|
184
678
|
}): CompleteModelInputEstimate {
|
|
185
|
-
const
|
|
679
|
+
const inputEstimate = estimateTokensBreakdown(input.current.input);
|
|
680
|
+
const inputTokens = inputEstimate.totalTokens;
|
|
186
681
|
const instructionsTokens = input.current.instructionsTokens;
|
|
187
682
|
const toolSchemaTokens = input.current.toolSchemaTokens;
|
|
188
683
|
if (!input.provider || !input.providerRequestFootprint || input.provider.totalTokens <= 0) {
|
|
@@ -190,6 +685,10 @@ export function estimateCompleteModelInput(input: {
|
|
|
190
685
|
tokens: inputTokens + instructionsTokens + toolSchemaTokens,
|
|
191
686
|
source: "complete_estimate",
|
|
192
687
|
inputTokens,
|
|
688
|
+
inputTextTokens: inputEstimate.textTokens,
|
|
689
|
+
inputImageTokens: inputEstimate.imageTokens,
|
|
690
|
+
inputImageCount: inputEstimate.imageCount,
|
|
691
|
+
inputImageFallbackCount: inputEstimate.imageFallbackCount,
|
|
193
692
|
instructionsTokens,
|
|
194
693
|
toolSchemaTokens,
|
|
195
694
|
appendedAfterModelTokens: 0,
|
|
@@ -211,6 +710,10 @@ export function estimateCompleteModelInput(input: {
|
|
|
211
710
|
input.provider.totalTokens + appendedAfterModelTokens + instructionGrowth + toolSchemaGrowth,
|
|
212
711
|
source: "provider_plus_local",
|
|
213
712
|
inputTokens,
|
|
713
|
+
inputTextTokens: inputEstimate.textTokens,
|
|
714
|
+
inputImageTokens: inputEstimate.imageTokens,
|
|
715
|
+
inputImageCount: inputEstimate.imageCount,
|
|
716
|
+
inputImageFallbackCount: inputEstimate.imageFallbackCount,
|
|
214
717
|
instructionsTokens,
|
|
215
718
|
toolSchemaTokens,
|
|
216
719
|
appendedAfterModelTokens,
|
|
@@ -551,6 +1054,97 @@ export function buildCompactionReplacementHistory(
|
|
|
551
1054
|
return history;
|
|
552
1055
|
}
|
|
553
1056
|
|
|
1057
|
+
/** True for a Codex remote-compaction output item with opaque encrypted content. */
|
|
1058
|
+
export function isRemoteCompactionItem(item: unknown): item is CompactionItem {
|
|
1059
|
+
if (!item || typeof item !== "object") return false;
|
|
1060
|
+
const record = item as Record<string, unknown>;
|
|
1061
|
+
return (
|
|
1062
|
+
record.type === "compaction" &&
|
|
1063
|
+
typeof record.encrypted_content === "string" &&
|
|
1064
|
+
record.encrypted_content.length > 0
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/** Messages retained beside a remote v2 compaction blob (user + developer). */
|
|
1069
|
+
export function isRetainedRemoteV2Message(item: unknown): boolean {
|
|
1070
|
+
if (isCompactionSummary(item) || itemType(item) === "compaction") return false;
|
|
1071
|
+
const role = itemRole(item);
|
|
1072
|
+
return itemType(item) === "message" && (role === "user" || role === "developer");
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Build the active history after Codex remote compaction v2:
|
|
1077
|
+
* newest retained user/developer messages within the CLI 64k budget plus the
|
|
1078
|
+
* opaque `{ type: "compaction", encrypted_content }` item.
|
|
1079
|
+
*/
|
|
1080
|
+
export function buildRemoteV2ReplacementHistory(
|
|
1081
|
+
items: readonly CompactionItem[],
|
|
1082
|
+
compactionItem: CompactionItem,
|
|
1083
|
+
): CompactionItem[] {
|
|
1084
|
+
if (!isRemoteCompactionItem(compactionItem)) {
|
|
1085
|
+
throw new EmptyCompactionSummaryError({ stage: "remote_v2_compaction_item" });
|
|
1086
|
+
}
|
|
1087
|
+
const retainedReversed: CompactionItem[] = [];
|
|
1088
|
+
let remaining = REMOTE_V2_RETAINED_MESSAGE_TOKEN_BUDGET;
|
|
1089
|
+
for (let index = items.length - 1; index >= 0 && remaining > 0; index -= 1) {
|
|
1090
|
+
const item = items[index]!;
|
|
1091
|
+
if (!isRetainedRemoteV2Message(item)) continue;
|
|
1092
|
+
const textTokens = estimateTextTokens(messageText(item));
|
|
1093
|
+
retainedReversed.push(compactMessageToTokenBudget(item, remaining));
|
|
1094
|
+
if (textTokens > remaining) {
|
|
1095
|
+
remaining = 0;
|
|
1096
|
+
break;
|
|
1097
|
+
}
|
|
1098
|
+
remaining -= textTokens;
|
|
1099
|
+
}
|
|
1100
|
+
const history = retainedReversed.reverse();
|
|
1101
|
+
history.push({
|
|
1102
|
+
type: "compaction",
|
|
1103
|
+
encrypted_content: compactionItem.encrypted_content,
|
|
1104
|
+
...(typeof compactionItem.summary === "string" ? { summary: compactionItem.summary } : {}),
|
|
1105
|
+
});
|
|
1106
|
+
return history;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* Append the transient compaction_trigger used only for the remote v2 request.
|
|
1111
|
+
*
|
|
1112
|
+
* The OpenAI Agents SDK's Responses converter rejects a bare
|
|
1113
|
+
* `{ type: "compaction_trigger" }` (`UserError: Unsupported item`). It does
|
|
1114
|
+
* accept `type: "unknown"` and forwards `providerData` onto the wire, which is
|
|
1115
|
+
* how we deliver the Codex-only trigger through CompactionResponsesModel into
|
|
1116
|
+
* the Codex fetch normalizer (which allowlists top-level `compaction_trigger`).
|
|
1117
|
+
*/
|
|
1118
|
+
export function buildRemoteCompactionV2PromptInput(
|
|
1119
|
+
items: readonly CompactionItem[],
|
|
1120
|
+
): CompactionItem[] {
|
|
1121
|
+
return [
|
|
1122
|
+
...items,
|
|
1123
|
+
{
|
|
1124
|
+
type: "unknown",
|
|
1125
|
+
providerData: { type: "compaction_trigger" },
|
|
1126
|
+
},
|
|
1127
|
+
];
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
/** Extract exactly one compaction output item from a Responses payload. */
|
|
1131
|
+
export function extractRemoteCompactionV2OutputItem(response: unknown): CompactionItem {
|
|
1132
|
+
if (!response || typeof response !== "object") {
|
|
1133
|
+
throw new EmptyCompactionSummaryError({ stage: "remote_v2_extract", reason: "no_response" });
|
|
1134
|
+
}
|
|
1135
|
+
const record = response as Record<string, unknown>;
|
|
1136
|
+
const output = Array.isArray(record.output) ? record.output : [];
|
|
1137
|
+
const compactionItems = output.filter(isRemoteCompactionItem);
|
|
1138
|
+
if (compactionItems.length !== 1) {
|
|
1139
|
+
throw new EmptyCompactionSummaryError({
|
|
1140
|
+
stage: "remote_v2_extract",
|
|
1141
|
+
reason: "expected_exactly_one_compaction",
|
|
1142
|
+
found: compactionItems.length,
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
return compactionItems[0]!;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
554
1148
|
export function compactionReplacementFingerprint(items: readonly CompactionItem[]): string {
|
|
555
1149
|
// PostgreSQL JSONB does not preserve JavaScript object-key insertion order.
|
|
556
1150
|
// Canonicalize recursively so a replacement has the same identity before
|