AIX: CGR: use shared objectUtils

This commit is contained in:
Enrico Ros
2025-11-29 12:40:04 -08:00
parent e36dde0d25
commit 7d274a31fe
2 changed files with 15 additions and 80 deletions
+11 -14
View File
@@ -72,7 +72,7 @@ export function objectEstimateJsonSize(value: unknown, debugCaller: string): num
case 'boolean':
return val ? 4 : 5; // "true" or "false"
case 'object': {
// Cycle detection
// cycle detection
if (seen.has(val as object)) {
console.warn(`[estimateJsonSize (${debugCaller})] Circular reference detected, returning 0 for this branch`);
return 0;
@@ -88,7 +88,7 @@ export function objectEstimateJsonSize(value: unknown, debugCaller: string): num
return size;
}
// Plain object
// plain object
let size = 2; // {}
const keys = Object.keys(val);
for (let i = 0; i < keys.length; i++) {
@@ -122,10 +122,10 @@ export function objectDeepCloneWithStringLimit(value: unknown, debugCaller: stri
const seen = new WeakSet<object>();
function clone(val: unknown): unknown {
// Handle primitives
// handle primitives first
if (val === null || val === undefined) return val;
// Handle strings - truncate if too long
// handle strings - truncate if too long
if (typeof val === 'string') {
if (val.length <= maxBytes) return val;
const ellipsis = `...[${(val.length - maxBytes).toLocaleString()} bytes]...`;
@@ -133,25 +133,22 @@ export function objectDeepCloneWithStringLimit(value: unknown, debugCaller: stri
return val.slice(0, half) + ellipsis + val.slice(-half);
}
// Handle other primitives
// handle other primitives
if (typeof val !== 'object') return val;
// Cycle detection
// cycle detection
if (seen.has(val)) return '[Circular]';
seen.add(val);
// Handle arrays
if (Array.isArray(val)) {
// handle arrays - recurse
if (Array.isArray(val))
return val.map(item => clone(item));
}
// Handle objects
// handle objects - recurse
const result: Record<string, unknown> = {};
for (const key in val) {
if (Object.prototype.hasOwnProperty.call(val, key)) {
for (const key in val)
if (Object.prototype.hasOwnProperty.call(val, key))
result[key] = clone((val as Record<string, unknown>)[key]);
}
}
return result;
}
@@ -1,6 +1,8 @@
import { SERVER_DEBUG_WIRE } from '~/server/wire';
import { serverSideId } from '~/server/trpc/trpc.nanoid';
import { objectDeepCloneWithStringLimit, objectEstimateJsonSize } from '~/common/util/objectUtils';
import type { AixWire_Particles } from '../../api/aix.wiretypes';
import type { IParticleTransmitter, ParticleServerLogLevel } from './parsers/IParticleTransmitter';
@@ -25,70 +27,6 @@ export const IssueSymbols = {
};
/** Estimates JSON size without stringifying (avoids memory spike on large objects). */
function _fastEstimateJsonSize(value: any): number {
if (value === null) return 4; // "null"
if (value === undefined) return 0; // omitted in JSON
if (typeof value === 'string')
return value.length + 2; // quotes
if (typeof value === 'number')
return String(value).length;
if (typeof value === 'boolean')
return value ? 4 : 5; // "true" or "false"
if (Array.isArray(value)) {
let size = 2; // []
for (let i = 0; i < value.length; i++) {
size += _fastEstimateJsonSize(value[i]);
if (i < value.length - 1) size += 1; // comma
}
return size;
}
if (typeof value === 'object') {
let size = 2; // {}
const keys = Object.keys(value);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
size += key.length + 3; // "key":
size += _fastEstimateJsonSize(value[key]);
if (i < keys.length - 1) size += 1; // comma
}
return size;
}
return 0;
}
/** Deep-clones an object while ellipsizing any string exceeding maxBytes in the middle. */
function _fastEllipsizeStringsInObject(value: any, maxBytes: number = DEBUG_REQUEST_MAX_STRING_BYTES): any {
// handle primitives first
if (value === null || value === undefined)
return value;
// handle strings - ellipsize if too long
if (typeof value === 'string') {
if (value.length <= maxBytes)
return value;
const ellipsis = `...[${(value.length - maxBytes).toLocaleString()} bytes]...`;
const half = Math.floor((maxBytes - ellipsis.length) / 2);
return value.slice(0, half) + ellipsis + value.slice(-half);
}
// handle other primitives (number, boolean)
if (typeof value !== 'object')
return value;
// handle arrays - recurse
if (Array.isArray(value))
return value.map(item => _fastEllipsizeStringsInObject(item, maxBytes));
// handle objects - recurse
const result: any = {};
for (const key in value)
if (value.hasOwnProperty(key))
result[key] = _fastEllipsizeStringsInObject(value[key], maxBytes);
return result;
}
/**
* Queues up and emits small messages (particles) to the client, for the purpose of a stateful
* full reconstruction of the AixWire_Parts[] objects.
@@ -194,7 +132,7 @@ export class ChatGenerateTransmitter implements IParticleTransmitter {
addDebugRequest(hideSensitiveData: boolean, url: string, headers: HeadersInit, body?: object) {
// Ellipsize individual strings in the body object (e.g., base64 images) to reduce debug packet size
const ellipsizedBody = body ? _fastEllipsizeStringsInObject(body) : undefined;
const ellipsizedBody = body ? objectDeepCloneWithStringLimit(body, 'aix.addDebugRequest', DEBUG_REQUEST_MAX_STRING_BYTES) : undefined;
const processedBody = ellipsizedBody ? JSON.stringify(ellipsizedBody, null, 2) : '';
this.transmissionQueue.push({
@@ -204,7 +142,7 @@ export class ChatGenerateTransmitter implements IParticleTransmitter {
url: url,
headers: hideSensitiveData ? '(hidden sensitive data)' : JSON.stringify(headers, null, 2),
body: processedBody,
bodySize: body ? _fastEstimateJsonSize(body) : 0,
bodySize: body ? objectEstimateJsonSize(body, 'aix.addDebugRequest') : 0,
},
});
}