@opengeni/codex 0.2.3 → 0.2.5

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.
@@ -0,0 +1,590 @@
1
+ /**
2
+ * Canonical model-facing tool-output truncation.
3
+ *
4
+ * Ported from openai/codex `rust-v0.144.6` (commit
5
+ * 5d1fbf26c43abc65a203928b2e31561cb039e06d):
6
+ *
7
+ * - `codex-rs/utils/string/src/truncate.rs`
8
+ * - `codex-rs/utils/output-truncation/src/lib.rs`
9
+ * - `codex-rs/core/src/context_manager/history.rs`
10
+ *
11
+ * The live gpt-5.6 model catalog declares a 10,000-token truncation policy.
12
+ * Codex applies a 1.2x allowance before serializing a function-call output, so
13
+ * the effective textual payload budget is 12,000 approximate tokens. Images,
14
+ * files, and encrypted content are preserved; textual content shares one
15
+ * sequential budget and carries an explicit head/tail truncation marker.
16
+ *
17
+ * This module deliberately has no database or Agents SDK dependency. Both the
18
+ * runtime request seam and the database history boundary call the same pure
19
+ * function, so replayed conversation truth is identical to live model input.
20
+ */
21
+
22
+ import { MODEL_TOOL_OUTPUT_OVERSIZED_IMAGE_CARD_DATA_URL } from "./oversized-image-card";
23
+
24
+ export { MODEL_TOOL_OUTPUT_OVERSIZED_IMAGE_CARD_DATA_URL } from "./oversized-image-card";
25
+
26
+ export type ModelHistoryItem = Record<string, unknown>;
27
+
28
+ export const CODEX_MODEL_TOOL_OUTPUT_TRUNCATION_TOKENS = 10_000;
29
+ export const CODEX_TOOL_OUTPUT_SERIALIZATION_ALLOWANCE = 1.2;
30
+ export const DEFAULT_MODEL_TOOL_OUTPUT_TRUNCATION_TOKENS =
31
+ CODEX_MODEL_TOOL_OUTPUT_TRUNCATION_TOKENS;
32
+
33
+ const APPROX_BYTES_PER_TOKEN = 4;
34
+ // Twelve decimal digits already describe ~4 TB at four bytes/token, far beyond
35
+ // any JavaScript string the runtime can materialize. Bounding the digit run is
36
+ // security-significant: otherwise a forged multi-megabyte run of digits could
37
+ // make `markerBytes` as large as the entire untrusted tool result and bypass the
38
+ // cap below.
39
+ const TOKEN_TRUNCATION_MARKER = /…\d{1,12} tokens truncated…/u;
40
+ const TOOL_RESULT_TYPES = new Set([
41
+ "function_call_result",
42
+ "function_call_output",
43
+ "computer_call_result",
44
+ "custom_tool_call_output",
45
+ "shell_call_output",
46
+ "apply_patch_call_output",
47
+ ]);
48
+ const STRUCTURAL_STRING_KEYS = new Set([
49
+ "type",
50
+ "role",
51
+ "status",
52
+ "name",
53
+ "id",
54
+ "callId",
55
+ "call_id",
56
+ "namespace",
57
+ "detail",
58
+ "mimeType",
59
+ "media_type",
60
+ ]);
61
+ const MODEL_TOOL_OUTPUT_MAX_DEPTH = 12;
62
+ const MODEL_TOOL_OUTPUT_MAX_CONTAINER_ENTRIES = 255;
63
+ const MODEL_TOOL_OUTPUT_MAX_TOTAL_ENTRIES = 2_048;
64
+ const MODEL_TOOL_OUTPUT_MAX_PROPERTY_KEY_BYTES = 256;
65
+ const MODEL_TOOL_OUTPUT_MAX_STRUCTURAL_STRING_TOKENS = 64;
66
+ const MODEL_TOOL_OUTPUT_STRUCTURAL_STRING_BUDGET_TOKENS = 1_024;
67
+ export const MODEL_TOOL_OUTPUT_OPAQUE_PAYLOAD_MAX_BYTES = 8 * 1024 * 1024;
68
+
69
+ const DEPTH_OMISSION_MARKER =
70
+ "[OpenGeni omitted subtree: maximum structured tool-output depth exceeded]";
71
+ const CYCLE_OMISSION_MARKER = "[OpenGeni omitted subtree: cyclic tool output]";
72
+ const STRUCTURAL_STRING_OMISSION_MARKER =
73
+ "[OpenGeni omitted structural string: structural budget exhausted]";
74
+ const TEXT_FIELD_OMISSION_MARKER = /^\[omitted text field \d+ \.\.\.\]$/u;
75
+ const TEXT_ITEMS_OMISSION_MARKER = /^\[omitted \d+ text items \.\.\.\]$/u;
76
+ const STRUCTURAL_ENTRIES_OMISSION_MARKER =
77
+ /^\[OpenGeni omitted \d+ structured (?:array items|object properties)\]$/u;
78
+ const OPAQUE_PAYLOAD_OMISSION_MARKER =
79
+ /^\[OpenGeni omitted (?:image|file|encrypted) payload: \d+ bytes exceeded the bounded model-input allowance\]$/u;
80
+ const STRUCTURAL_PROPERTIES_MARKER_KEY = "__opengeni_omitted_properties__";
81
+
82
+ type OpaqueProtocolKind = "image" | "file" | "encrypted";
83
+
84
+ type ModelOutputBoundState = {
85
+ remaining: number;
86
+ remainingStructural: number;
87
+ remainingEntries: number;
88
+ remainingOpaqueBytes: number;
89
+ opaqueOmissions: number;
90
+ lastOpaqueOmissionMarker: string | null;
91
+ omitted: number;
92
+ seen: WeakSet<object>;
93
+ };
94
+
95
+ export function modelToolOutputSerializationBudgetTokens(
96
+ policyTokens = DEFAULT_MODEL_TOOL_OUTPUT_TRUNCATION_TOKENS,
97
+ ): number {
98
+ return Math.ceil(Math.max(0, policyTokens) * CODEX_TOOL_OUTPUT_SERIALIZATION_ALLOWANCE);
99
+ }
100
+
101
+ export function approximateTokenCount(value: string): number {
102
+ return Math.ceil(Buffer.byteLength(value, "utf8") / APPROX_BYTES_PER_TOKEN);
103
+ }
104
+
105
+ /** Exact Codex-style middle truncation for a token policy. */
106
+ export function truncateMiddleWithTokenBudget(value: string, maxTokens: number): string {
107
+ if (value.length === 0) return value;
108
+ const maxBytes = Math.max(0, maxTokens) * APPROX_BYTES_PER_TOKEN;
109
+ const valueBytes = Buffer.byteLength(value, "utf8");
110
+ if (maxTokens > 0 && valueBytes <= maxBytes) return value;
111
+ // Codex applies this transform once while recording history, so its marker
112
+ // sits just outside the content budget. OpenGeni deliberately enforces the
113
+ // same policy both at canonical persistence and at the final provider seam.
114
+ // Recognize only an output whose excess is no larger than its own canonical
115
+ // marker; this makes that repeated enforcement byte-idempotent without letting
116
+ // an arbitrary oversized string bypass the cap merely by containing marker-like
117
+ // text. The first application remains byte-for-byte Codex 0.144.6 behavior.
118
+ const existingMarker = value.match(TOKEN_TRUNCATION_MARKER)?.[0];
119
+ if (existingMarker && valueBytes <= maxBytes + Buffer.byteLength(existingMarker, "utf8")) {
120
+ return value;
121
+ }
122
+ if (maxBytes === 0) {
123
+ return `…${approximateTokenCount(value)} tokens truncated…`;
124
+ }
125
+
126
+ const leftBudget = Math.floor(maxBytes / 2);
127
+ const rightBudget = maxBytes - leftBudget;
128
+ // Do not materialize `Array.from(value)`: production tool results can be
129
+ // multi-megabyte strings and one JS element per code point multiplies peak
130
+ // memory. A single UTF-8 buffer gives bounded scans at the two cut points.
131
+ const bytes = Buffer.from(value, "utf8");
132
+ let leftEnd = Math.min(leftBudget, bytes.length);
133
+ while (leftEnd > 0 && leftEnd < bytes.length && isUtf8ContinuationByte(bytes[leftEnd]!)) {
134
+ leftEnd -= 1;
135
+ }
136
+ let rightStart = Math.max(0, bytes.length - rightBudget);
137
+ while (rightStart < bytes.length && isUtf8ContinuationByte(bytes[rightStart]!)) {
138
+ rightStart += 1;
139
+ }
140
+ const left = bytes.subarray(0, leftEnd).toString("utf8");
141
+ const right = bytes.subarray(rightStart).toString("utf8");
142
+ const removedBytes = Math.max(0, valueBytes - maxBytes);
143
+ const removedTokens = Math.ceil(removedBytes / APPROX_BYTES_PER_TOKEN);
144
+ return `${left}…${removedTokens} tokens truncated…${right}`;
145
+ }
146
+
147
+ function isUtf8ContinuationByte(value: number): boolean {
148
+ return (value & 0xc0) === 0x80;
149
+ }
150
+
151
+ /**
152
+ * Bound every model-visible tool-result item. Non-result items are returned by
153
+ * reference. Result items are cloned only when their textual output changes.
154
+ */
155
+ export function boundModelToolOutputItem<T extends ModelHistoryItem>(
156
+ item: T,
157
+ policyTokens = DEFAULT_MODEL_TOOL_OUTPUT_TRUNCATION_TOKENS,
158
+ ): T {
159
+ const type = typeof item.type === "string" ? item.type : "";
160
+ if (!TOOL_RESULT_TYPES.has(type)) return item;
161
+ const budget = modelToolOutputSerializationBudgetTokens(policyTokens);
162
+ const boundedOutput = boundToolOutputValue(item.output, budget);
163
+ return boundedOutput === item.output ? item : ({ ...item, output: boundedOutput } as T);
164
+ }
165
+
166
+ export function boundModelToolOutputItems<T extends ModelHistoryItem>(
167
+ items: readonly T[],
168
+ policyTokens = DEFAULT_MODEL_TOOL_OUTPUT_TRUNCATION_TOKENS,
169
+ ): T[] {
170
+ return items.map((item) => boundModelToolOutputItem(item, policyTokens));
171
+ }
172
+
173
+ function boundToolOutputValue(output: unknown, budgetTokens: number): unknown {
174
+ const state = modelOutputBoundState(budgetTokens);
175
+ if (typeof output === "string") {
176
+ if (isGeneratedModelOutputMarker(output)) {
177
+ observeGeneratedMarkerBudget(output, state);
178
+ return output;
179
+ }
180
+ // Text-transport computer/view_image tools use a data URL because Chat
181
+ // Completions has no structured image result. It is still image protocol,
182
+ // not textual tool output; truncating its base64 permanently corrupts it.
183
+ if (isImageDataUrl(output)) return boundOpaqueProtocolString(output, state, "image");
184
+ return truncateMiddleWithTokenBudget(output, budgetTokens);
185
+ }
186
+ if (Array.isArray(output)) {
187
+ // Responses content arrays have an explicit text/image/file protocol and
188
+ // follow Codex's sequential item policy exactly. Shell/apply adapters can
189
+ // instead return arrays of objects containing stdout/stderr; those share
190
+ // the same total text budget through the generic leaf walker.
191
+ // Inspect only the prefix the boundary can retain. Cardinality itself does
192
+ // not make an otherwise-valid Responses content list invalid, and scanning
193
+ // an untrusted 100k-item tail merely to classify it defeats the bound.
194
+ const isProtocolContent = isResponsesProtocolContentPrefix(output);
195
+ return isProtocolContent
196
+ ? boundStructuredOutputItems(output, state)
197
+ : boundTextLeaves(output, state);
198
+ }
199
+ if (!output || typeof output !== "object") return output;
200
+
201
+ const record = output as Record<string, unknown>;
202
+ // Shell/apply-patch result objects are not structured Responses content, but
203
+ // can contain arbitrarily large stdout/stderr leaves. Preserve useful shape
204
+ // while sharing bounded text, structural, entry, depth, and opaque-protocol
205
+ // budgets across the whole value.
206
+ return boundTextLeaves(record, state);
207
+ }
208
+
209
+ function modelOutputBoundState(budgetTokens: number): ModelOutputBoundState {
210
+ return {
211
+ remaining: Math.max(0, budgetTokens),
212
+ remainingStructural: MODEL_TOOL_OUTPUT_STRUCTURAL_STRING_BUDGET_TOKENS,
213
+ remainingEntries: MODEL_TOOL_OUTPUT_MAX_TOTAL_ENTRIES,
214
+ remainingOpaqueBytes: MODEL_TOOL_OUTPUT_OPAQUE_PAYLOAD_MAX_BYTES,
215
+ opaqueOmissions: 0,
216
+ lastOpaqueOmissionMarker: null,
217
+ omitted: 0,
218
+ seen: new WeakSet(),
219
+ };
220
+ }
221
+
222
+ function boundStructuredOutputItems(items: unknown[], state: ModelOutputBoundState): unknown[] {
223
+ let omitted = 0;
224
+ let changed = false;
225
+ const out: unknown[] = [];
226
+ let processed = 0;
227
+ // A canonical first pass can contain one typed structural trailer beyond the
228
+ // ordinary 255 retained parts. Preserve that exact terminal trailer when a
229
+ // durable/provider/recovery boundary applies the function again. Limiting
230
+ // this exception to an already-bounded array prevents an arbitrary huge tail
231
+ // with a marker-shaped last element from bypassing the first-pass count.
232
+ const terminalStructuralMarker =
233
+ items.length <= MODEL_TOOL_OUTPUT_MAX_CONTAINER_ENTRIES + 1 &&
234
+ isTypedStructuralArrayOmissionMarker(items.at(-1))
235
+ ? items.at(-1)
236
+ : null;
237
+ let preservedTerminalStructuralMarker = false;
238
+ for (let index = 0; index < items.length; index += 1) {
239
+ const item = items[index];
240
+ if (processed >= MODEL_TOOL_OUTPUT_MAX_CONTAINER_ENTRIES || state.remainingEntries <= 0) {
241
+ if (terminalStructuralMarker && index <= items.length - 1) {
242
+ out.push(terminalStructuralMarker);
243
+ preservedTerminalStructuralMarker = true;
244
+ }
245
+ break;
246
+ }
247
+ processed += 1;
248
+ state.remainingEntries -= 1;
249
+ const record = item as Record<string, unknown>;
250
+ if (record.type === "input_text" && isGeneratedModelOutputMarker(record.text)) {
251
+ const bounded = boundTextLeaves(item, state, 1);
252
+ out.push(bounded);
253
+ if (item === terminalStructuralMarker) preservedTerminalStructuralMarker = true;
254
+ if (bounded !== item) changed = true;
255
+ continue;
256
+ }
257
+ if (record.type === "input_text" && state.remaining === 0) {
258
+ omitted += 1;
259
+ changed = true;
260
+ continue;
261
+ }
262
+ const bounded = boundResponsesProtocolContentItem(record, state);
263
+ out.push(bounded);
264
+ if (item === terminalStructuralMarker) preservedTerminalStructuralMarker = true;
265
+ if (bounded !== item) changed = true;
266
+ }
267
+ if (omitted > 0) {
268
+ out.push({
269
+ type: "input_text",
270
+ text: `[omitted ${omitted} text items ...]`,
271
+ });
272
+ }
273
+ const structurallyOmitted = preservedTerminalStructuralMarker ? 0 : items.length - processed;
274
+ if (structurallyOmitted > 0) {
275
+ out.push(typedStructuredArrayOmissionMarker(structurallyOmitted));
276
+ changed = true;
277
+ }
278
+ return changed ? out : items;
279
+ }
280
+
281
+ function boundResponsesProtocolContentItem(
282
+ item: Record<string, unknown>,
283
+ state: ModelOutputBoundState,
284
+ ): Record<string, unknown> {
285
+ const opaqueOmissionsBefore = state.opaqueOmissions;
286
+ const bounded = boundTextLeaves(item, state, 1) as Record<string, unknown>;
287
+ // Agents interprets fileId/file_id (and nested image.id) as a provider file
288
+ // reference. Replacing only that string with our data URL would manufacture
289
+ // a fictitious file_id. Normalize the whole overflowing image part instead,
290
+ // removing every ID field while staying inside the Responses content union.
291
+ if (item.type === "input_image" && state.opaqueOmissions > opaqueOmissionsBefore) {
292
+ return {
293
+ type: "input_image",
294
+ imageUrl: MODEL_TOOL_OUTPUT_OVERSIZED_IMAGE_CARD_DATA_URL,
295
+ };
296
+ }
297
+ // A marker string in `input_file.file` is interpreted by pinned Agents as a
298
+ // file_url. Replace the whole content part instead, so every generated
299
+ // omission remains inside the Responses text/image/file union without
300
+ // inventing a URL or file ID. This also covers cumulative opaque exhaustion.
301
+ if (item.type === "input_file" && state.opaqueOmissions > opaqueOmissionsBefore) {
302
+ return typedProtocolTextMarker(
303
+ state.lastOpaqueOmissionMarker ??
304
+ "[OpenGeni omitted file payload: 0 bytes exceeded the bounded model-input allowance]",
305
+ );
306
+ }
307
+ return bounded;
308
+ }
309
+
310
+ function boundTextLeaves(
311
+ value: unknown,
312
+ state: ModelOutputBoundState,
313
+ depth = 0,
314
+ opaqueKind: OpaqueProtocolKind | null = null,
315
+ ): unknown {
316
+ if (typeof value === "string") {
317
+ if (isGeneratedModelOutputMarker(value)) {
318
+ observeGeneratedMarkerBudget(value, state);
319
+ return value;
320
+ }
321
+ if (opaqueKind || isImageDataUrl(value)) {
322
+ return boundOpaqueProtocolString(value, state, opaqueKind ?? "image");
323
+ }
324
+ if (state.remaining === 0) {
325
+ state.omitted += 1;
326
+ return `[omitted text field ${state.omitted} ...]`;
327
+ }
328
+ const cost = approximateTokenCount(value);
329
+ if (cost <= state.remaining) {
330
+ state.remaining -= cost;
331
+ return value;
332
+ }
333
+ const bounded = truncateMiddleWithTokenBudget(value, state.remaining);
334
+ state.remaining = 0;
335
+ return bounded;
336
+ }
337
+ if (!value || typeof value !== "object") return value;
338
+ if (depth >= MODEL_TOOL_OUTPUT_MAX_DEPTH) return DEPTH_OMISSION_MARKER;
339
+ if (state.seen.has(value)) return CYCLE_OMISSION_MARKER;
340
+ state.seen.add(value);
341
+ if (Array.isArray(value)) {
342
+ const out: unknown[] = [];
343
+ let processed = 0;
344
+ let changed = false;
345
+ for (let index = 0; index < value.length; index += 1) {
346
+ const entry = value[index];
347
+ if (processed >= MODEL_TOOL_OUTPUT_MAX_CONTAINER_ENTRIES || state.remainingEntries <= 0) {
348
+ // A prior pass can add exactly one structural trailer beyond the normal
349
+ // item allowance. Retain only that final trailer for replay idempotence;
350
+ // marker-shaped untrusted entries otherwise consume the same caps as
351
+ // every other entry and cannot form an unbounded bypass.
352
+ if (
353
+ index === value.length - 1 &&
354
+ typeof entry === "string" &&
355
+ STRUCTURAL_ENTRIES_OMISSION_MARKER.test(entry)
356
+ ) {
357
+ out.push(entry);
358
+ }
359
+ break;
360
+ }
361
+ processed += 1;
362
+ state.remainingEntries -= 1;
363
+ const bounded = boundTextLeaves(entry, state, depth + 1, opaqueKind);
364
+ out.push(bounded);
365
+ if (bounded !== entry) changed = true;
366
+ }
367
+ const omitted = value.length - out.length;
368
+ if (omitted > 0) {
369
+ out.push(structuredEntriesOmissionMarker(omitted, "array"));
370
+ changed = true;
371
+ }
372
+ state.seen.delete(value);
373
+ return changed ? out : value;
374
+ }
375
+ const record = value as Record<string, unknown>;
376
+ const recordOpaqueKind = nonTextProtocolKind(record.type) ?? opaqueKind;
377
+ const entries = Object.entries(record);
378
+ const out: Record<string, unknown> = {};
379
+ let processed = 0;
380
+ let omitted = 0;
381
+ let changed = false;
382
+ for (let index = 0; index < entries.length; index += 1) {
383
+ const [key, entry] = entries[index]!;
384
+ if (processed >= MODEL_TOOL_OUTPUT_MAX_CONTAINER_ENTRIES || state.remainingEntries <= 0) {
385
+ // As with arrays, a bounded prior pass may have appended one final marker
386
+ // property after filling the normal property allowance. Preserve only
387
+ // that terminal marker; forged/interspersed marker properties remain
388
+ // ordinary bounded input.
389
+ if (index === entries.length - 1 && isGeneratedStructuralMarkerProperty(key, entry)) {
390
+ out[key] = entry;
391
+ break;
392
+ }
393
+ omitted += entries.length - index;
394
+ break;
395
+ }
396
+ processed += 1;
397
+ state.remainingEntries -= 1;
398
+ if (Buffer.byteLength(key, "utf8") > MODEL_TOOL_OUTPUT_MAX_PROPERTY_KEY_BYTES) {
399
+ omitted += 1;
400
+ changed = true;
401
+ continue;
402
+ }
403
+ const childOpaqueKind = opaqueKindForChild(recordOpaqueKind, key);
404
+ if (typeof entry === "string" && childOpaqueKind) {
405
+ const bounded = boundTextLeaves(entry, state, depth + 1, childOpaqueKind);
406
+ out[key] = bounded;
407
+ if (bounded !== entry) changed = true;
408
+ continue;
409
+ }
410
+ if (typeof entry === "string" && STRUCTURAL_STRING_KEYS.has(key)) {
411
+ const bounded = boundStructuralString(entry, state);
412
+ out[key] = bounded;
413
+ if (bounded !== entry) changed = true;
414
+ continue;
415
+ }
416
+ const bounded = boundTextLeaves(entry, state, depth + 1, childOpaqueKind);
417
+ out[key] = bounded;
418
+ if (bounded !== entry) changed = true;
419
+ }
420
+ if (omitted > 0) {
421
+ out[uniqueStructuralMarkerKey(out)] = structuredEntriesOmissionMarker(omitted, "object");
422
+ changed = true;
423
+ }
424
+ state.seen.delete(value);
425
+ return changed ? out : value;
426
+ }
427
+
428
+ function boundStructuralString(value: string, state: ModelOutputBoundState): string {
429
+ if (isGeneratedModelOutputMarker(value)) {
430
+ observeGeneratedMarkerBudget(value, state);
431
+ return value;
432
+ }
433
+ if (state.remainingStructural === 0) return STRUCTURAL_STRING_OMISSION_MARKER;
434
+ const cost = approximateTokenCount(value);
435
+ const allowance = Math.min(
436
+ MODEL_TOOL_OUTPUT_MAX_STRUCTURAL_STRING_TOKENS,
437
+ state.remainingStructural,
438
+ );
439
+ if (cost <= allowance) {
440
+ state.remainingStructural -= cost;
441
+ return value;
442
+ }
443
+ state.remainingStructural -= allowance;
444
+ return truncateMiddleWithTokenBudget(value, allowance);
445
+ }
446
+
447
+ function boundOpaqueProtocolString(
448
+ value: string,
449
+ state: ModelOutputBoundState,
450
+ kind: OpaqueProtocolKind,
451
+ ): string {
452
+ // A prior pass can only have produced this exact static value. Treat it as a
453
+ // consumed image allowance so applying the boundary again is byte-idempotent
454
+ // even when more image fields follow it in the same structured result.
455
+ if (kind === "image" && value === MODEL_TOOL_OUTPUT_OVERSIZED_IMAGE_CARD_DATA_URL) {
456
+ state.remainingOpaqueBytes = 0;
457
+ return value;
458
+ }
459
+ const bytes = Buffer.byteLength(value, "utf8");
460
+ if (bytes <= state.remainingOpaqueBytes) {
461
+ state.remainingOpaqueBytes -= bytes;
462
+ return value;
463
+ }
464
+ state.remainingOpaqueBytes = 0;
465
+ if (kind === "image") {
466
+ state.opaqueOmissions += 1;
467
+ return MODEL_TOOL_OUTPUT_OVERSIZED_IMAGE_CARD_DATA_URL;
468
+ }
469
+ const marker = `[OpenGeni omitted ${kind} payload: ${bytes} bytes exceeded the bounded model-input allowance]`;
470
+ state.opaqueOmissions += 1;
471
+ state.lastOpaqueOmissionMarker = marker;
472
+ return marker;
473
+ }
474
+
475
+ function nonTextProtocolKind(value: unknown): OpaqueProtocolKind | null {
476
+ if (value === "image" || value === "input_image" || value === "computer_screenshot") {
477
+ return "image";
478
+ }
479
+ if (value === "file" || value === "input_file") return "file";
480
+ if (value === "encrypted_content") return "encrypted";
481
+ return null;
482
+ }
483
+
484
+ function opaqueKindForChild(
485
+ kind: OpaqueProtocolKind | null,
486
+ key: string,
487
+ ): OpaqueProtocolKind | null {
488
+ if (!kind) return null;
489
+ const opaqueKeys =
490
+ kind === "image"
491
+ ? ["image", "image_url", "imageUrl", "file_id", "fileId", "id", "data", "url", "source"]
492
+ : kind === "file"
493
+ ? [
494
+ "file",
495
+ "file_data",
496
+ "fileData",
497
+ "file_url",
498
+ "fileUrl",
499
+ "file_id",
500
+ "fileId",
501
+ "id",
502
+ "data",
503
+ "url",
504
+ "content",
505
+ "source",
506
+ ]
507
+ : ["encrypted_content", "content", "data"];
508
+ return opaqueKeys.includes(key) ? kind : null;
509
+ }
510
+
511
+ function structuredEntriesOmissionMarker(count: number, container: "array" | "object"): string {
512
+ return `[OpenGeni omitted ${count} structured ${container === "array" ? "array items" : "object properties"}]`;
513
+ }
514
+
515
+ function typedProtocolTextMarker(text: string): Record<string, unknown> {
516
+ return { type: "input_text", text };
517
+ }
518
+
519
+ function typedStructuredArrayOmissionMarker(count: number): Record<string, unknown> {
520
+ return typedProtocolTextMarker(structuredEntriesOmissionMarker(count, "array"));
521
+ }
522
+
523
+ function isTypedStructuralArrayOmissionMarker(value: unknown): boolean {
524
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
525
+ const record = value as Record<string, unknown>;
526
+ return (
527
+ record.type === "input_text" &&
528
+ typeof record.text === "string" &&
529
+ STRUCTURAL_ENTRIES_OMISSION_MARKER.test(record.text) &&
530
+ record.text.includes("structured array items")
531
+ );
532
+ }
533
+
534
+ function isGeneratedModelOutputMarker(value: unknown): value is string {
535
+ return (
536
+ typeof value === "string" &&
537
+ (value === DEPTH_OMISSION_MARKER ||
538
+ value === CYCLE_OMISSION_MARKER ||
539
+ value === STRUCTURAL_STRING_OMISSION_MARKER ||
540
+ TEXT_FIELD_OMISSION_MARKER.test(value) ||
541
+ TEXT_ITEMS_OMISSION_MARKER.test(value) ||
542
+ STRUCTURAL_ENTRIES_OMISSION_MARKER.test(value) ||
543
+ OPAQUE_PAYLOAD_OMISSION_MARKER.test(value))
544
+ );
545
+ }
546
+
547
+ function observeGeneratedMarkerBudget(value: string, state: ModelOutputBoundState): void {
548
+ if (TEXT_FIELD_OMISSION_MARKER.test(value) || TEXT_ITEMS_OMISSION_MARKER.test(value)) {
549
+ state.remaining = 0;
550
+ }
551
+ if (value === STRUCTURAL_STRING_OMISSION_MARKER) state.remainingStructural = 0;
552
+ if (OPAQUE_PAYLOAD_OMISSION_MARKER.test(value)) state.remainingOpaqueBytes = 0;
553
+ }
554
+
555
+ function isGeneratedStructuralMarkerProperty(key: string, value: unknown): boolean {
556
+ return (
557
+ key.startsWith(STRUCTURAL_PROPERTIES_MARKER_KEY) &&
558
+ typeof value === "string" &&
559
+ STRUCTURAL_ENTRIES_OMISSION_MARKER.test(value)
560
+ );
561
+ }
562
+
563
+ function uniqueStructuralMarkerKey(record: Record<string, unknown>): string {
564
+ let key = STRUCTURAL_PROPERTIES_MARKER_KEY;
565
+ let suffix = 1;
566
+ while (Object.hasOwn(record, key)) {
567
+ key = `${STRUCTURAL_PROPERTIES_MARKER_KEY}_${suffix}`;
568
+ suffix += 1;
569
+ }
570
+ return key;
571
+ }
572
+
573
+ function isResponsesProtocolContentPrefix(output: unknown[]): boolean {
574
+ if (output.length === 0) return false;
575
+ const retainedPrefixLength = Math.min(output.length, MODEL_TOOL_OUTPUT_MAX_CONTAINER_ENTRIES);
576
+ for (let index = 0; index < retainedPrefixLength; index += 1) {
577
+ const item = output[index];
578
+ if (!item || typeof item !== "object" || Array.isArray(item)) return false;
579
+ const record = item as Record<string, unknown>;
580
+ if (record.type === "input_text" && typeof record.text === "string") continue;
581
+ if (record.type === "input_image") continue;
582
+ if (record.type === "input_file") continue;
583
+ return false;
584
+ }
585
+ return true;
586
+ }
587
+
588
+ function isImageDataUrl(value: string): boolean {
589
+ return /^data:image\/[a-z0-9.+-]+;base64,/i.test(value);
590
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Protocol-valid image shown to the model when an inline tool image exceeds the
3
+ * hard model-input allowance. The image channel cannot carry a text marker, so
4
+ * the omission itself is rendered as a legible PNG instead of corrupting the
5
+ * original base64 or pretending the placeholder is the real screenshot.
6
+ *
7
+ * Generated with the dependency-free bitmap/PNG encoder documented in
8
+ * `scripts/gen-screenshot-error-card.mjs`; the source image is 1,076x284 RGBA
9
+ * and 5,255 bytes. Rendered text:
10
+ *
11
+ * SCREEN CAPTURE OMITTED
12
+ * THE SCREEN CAPTURE IS TOO LARGE.
13
+ * THIS IS A PLACEHOLDER, NOT THE REAL SCREEN.
14
+ * DO NOT SAY THIS IS THE REAL SCREEN.
15
+ * TELL THE USER TO TAKE A SMALLER CAPTURE.
16
+ */
17
+ export const MODEL_TOOL_OUTPUT_OVERSIZED_IMAGE_CARD_DATA_URL =
18
+ "data:image/png;base64,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";