2025-07-14 11:12:51 -07:00
|
|
|
// In: @soulcraft/brainy/src/utils/textEncoding.ts
|
|
|
|
|
|
2025-07-04 14:42:33 -07:00
|
|
|
/**
|
2025-07-14 11:12:51 -07:00
|
|
|
* Checks if the code is running in a Node.js environment.
|
2025-07-04 14:42:33 -07:00
|
|
|
*/
|
2025-07-14 11:12:51 -07:00
|
|
|
function isNode(): boolean {
|
|
|
|
|
return (
|
|
|
|
|
typeof process !== 'undefined' &&
|
|
|
|
|
process.versions != null &&
|
|
|
|
|
process.versions.node != null
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-07-04 14:42:33 -07:00
|
|
|
|
|
|
|
|
/**
|
2025-07-14 11:12:51 -07:00
|
|
|
* Global flag to track if TensorFlow.js has been initialized
|
|
|
|
|
* This helps prevent multiple registrations of the same kernels
|
2025-07-04 14:42:33 -07:00
|
|
|
*/
|
2025-07-14 11:12:51 -07:00
|
|
|
const TENSORFLOW_INITIALIZED = Symbol('TENSORFLOW_INITIALIZED')
|
2025-07-04 14:42:33 -07:00
|
|
|
|
|
|
|
|
/**
|
2025-07-14 11:12:51 -07:00
|
|
|
* Flag to track if the patch has been applied
|
|
|
|
|
* This prevents multiple applications of the patch
|
2025-07-04 14:42:33 -07:00
|
|
|
*/
|
2025-07-14 11:12:51 -07:00
|
|
|
let patchApplied = false
|
2025-07-04 14:42:33 -07:00
|
|
|
|
|
|
|
|
/**
|
2025-07-14 11:12:51 -07:00
|
|
|
* CRITICAL: Applies a compatibility patch for TensorFlow.js when running in a modern
|
|
|
|
|
* Node.js ES Module environment. This must be called before any TensorFlow.js
|
|
|
|
|
* modules are imported.
|
|
|
|
|
*
|
|
|
|
|
* This function prevents the "TextEncoder is not a constructor" error by preemptively
|
|
|
|
|
* creating a compliant PlatformNode class with proper TextEncoder/TextDecoder support
|
|
|
|
|
* and placing it on the global object where TensorFlow.js expects to find it.
|
|
|
|
|
*
|
|
|
|
|
* The race condition occurs because TensorFlow.js's platform detection might run
|
|
|
|
|
* before the necessary global objects are properly initialized in certain Node.js
|
|
|
|
|
* environments, particularly when the package is being used by other applications.
|
|
|
|
|
*
|
|
|
|
|
* This function is called from setup.ts, which must be the first import in unified.ts
|
|
|
|
|
* to ensure the patch is applied before any TensorFlow.js code is executed.
|
|
|
|
|
*
|
|
|
|
|
* It also applies a patch to prevent duplicate kernel registrations when TensorFlow.js
|
|
|
|
|
* is imported multiple times.
|
2025-07-04 14:42:33 -07:00
|
|
|
*/
|
|
|
|
|
export function applyTensorFlowPatch(): void {
|
2025-07-14 11:12:51 -07:00
|
|
|
// Prevent multiple applications of the patch
|
|
|
|
|
if (patchApplied) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isNode()) {
|
|
|
|
|
return // Patch is only for Node.js
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// In modern Node.js with ES Modules, TensorFlow.js can fail during its
|
|
|
|
|
// initial platform detection. This patch preempts that logic by creating
|
|
|
|
|
// a compliant "Platform" class that uses the standard global TextEncoder
|
|
|
|
|
// and placing it on the global object where TensorFlow.js expects to find it.
|
2025-07-07 10:19:04 -07:00
|
|
|
try {
|
2025-07-14 11:12:51 -07:00
|
|
|
// Ensure TextEncoder and TextDecoder are available
|
|
|
|
|
const nodeUtil = require('util')
|
|
|
|
|
const TextEncoderPolyfill = nodeUtil.TextEncoder || global.TextEncoder
|
|
|
|
|
const TextDecoderPolyfill = nodeUtil.TextDecoder || global.TextDecoder
|
|
|
|
|
|
|
|
|
|
if (!TextEncoderPolyfill || !TextDecoderPolyfill) {
|
|
|
|
|
console.warn(
|
|
|
|
|
'Brainy: TextEncoder or TextDecoder not available, attempting to polyfill'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// If still not available, try to use a simple polyfill
|
|
|
|
|
if (!TextEncoderPolyfill) {
|
|
|
|
|
class SimpleTextEncoder {
|
|
|
|
|
encode(input: string): Uint8Array {
|
|
|
|
|
const buf = Buffer.from(input, 'utf8')
|
|
|
|
|
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
global.TextEncoder = SimpleTextEncoder
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!TextDecoderPolyfill) {
|
|
|
|
|
class SimpleTextDecoder {
|
|
|
|
|
decode(input?: Uint8Array): string {
|
|
|
|
|
if (!input) return ''
|
|
|
|
|
return Buffer.from(
|
|
|
|
|
input.buffer,
|
|
|
|
|
input.byteOffset,
|
|
|
|
|
input.byteLength
|
|
|
|
|
).toString('utf8')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
global.TextDecoder = SimpleTextDecoder
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Ensure they're available globally
|
|
|
|
|
global.TextEncoder = TextEncoderPolyfill
|
|
|
|
|
global.TextDecoder = TextDecoderPolyfill
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create a PlatformNode implementation that uses the polyfilled TextEncoder/TextDecoder
|
|
|
|
|
class BrainyPlatformNode {
|
|
|
|
|
// Use the polyfilled TextEncoder/TextDecoder
|
|
|
|
|
readonly util = {
|
|
|
|
|
TextEncoder: global.TextEncoder,
|
|
|
|
|
TextDecoder: global.TextDecoder,
|
|
|
|
|
|
|
|
|
|
// Add utility functions that TensorFlow.js might need
|
|
|
|
|
isTypedArray: (arr: any): boolean => {
|
|
|
|
|
return ArrayBuffer.isView(arr) && !(arr instanceof DataView)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
isFloat32Array: (arr: any): boolean => {
|
|
|
|
|
return (
|
|
|
|
|
arr instanceof Float32Array ||
|
|
|
|
|
(arr &&
|
|
|
|
|
Object.prototype.toString.call(arr) === '[object Float32Array]')
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create instances of the encoder/decoder
|
|
|
|
|
readonly textEncoder: any
|
|
|
|
|
readonly textDecoder: any
|
2025-07-07 10:19:04 -07:00
|
|
|
|
|
|
|
|
constructor() {
|
2025-07-14 11:12:51 -07:00
|
|
|
try {
|
|
|
|
|
// Initialize encoders using constructors
|
|
|
|
|
this.textEncoder = new global.TextEncoder()
|
|
|
|
|
this.textDecoder = new global.TextDecoder()
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn(
|
|
|
|
|
'Brainy: Error creating TextEncoder/TextDecoder instances:',
|
|
|
|
|
e
|
|
|
|
|
)
|
|
|
|
|
// Provide fallback implementations if instantiation fails
|
|
|
|
|
this.textEncoder = {
|
|
|
|
|
encode: (input: string): Uint8Array => {
|
|
|
|
|
const buf = Buffer.from(input, 'utf8')
|
|
|
|
|
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.textDecoder = {
|
|
|
|
|
decode: (input?: Uint8Array): string => {
|
|
|
|
|
if (!input) return ''
|
|
|
|
|
return Buffer.from(
|
|
|
|
|
input.buffer,
|
|
|
|
|
input.byteOffset,
|
|
|
|
|
input.byteLength
|
|
|
|
|
).toString('utf8')
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-04 14:42:33 -07:00
|
|
|
}
|
2025-07-14 11:12:51 -07:00
|
|
|
}
|
2025-07-07 10:19:04 -07:00
|
|
|
|
2025-07-14 11:12:51 -07:00
|
|
|
isTypedArray(arr: any): arr is Float32Array | Int32Array | Uint8Array {
|
|
|
|
|
return ArrayBuffer.isView(arr) && !(arr instanceof DataView)
|
2025-07-07 10:19:04 -07:00
|
|
|
}
|
|
|
|
|
|
2025-07-14 11:12:51 -07:00
|
|
|
isFloat32Array(arr: any): arr is Float32Array {
|
|
|
|
|
return (
|
2025-07-07 10:19:04 -07:00
|
|
|
arr instanceof Float32Array ||
|
|
|
|
|
(arr &&
|
2025-07-11 11:11:56 -07:00
|
|
|
Object.prototype.toString.call(arr) === '[object Float32Array]')
|
2025-07-07 10:19:04 -07:00
|
|
|
)
|
2025-07-04 14:42:33 -07:00
|
|
|
}
|
2025-07-07 10:19:04 -07:00
|
|
|
}
|
2025-07-04 14:42:33 -07:00
|
|
|
|
2025-07-14 11:12:51 -07:00
|
|
|
// Assign the custom platform class to the global scope.
|
|
|
|
|
// TensorFlow.js specifically looks for `PlatformNode`.
|
|
|
|
|
global.PlatformNode = BrainyPlatformNode
|
|
|
|
|
|
|
|
|
|
// Also create an instance and assign it to global.platformNode (lowercase p)
|
|
|
|
|
// This is needed for some TensorFlow.js versions
|
|
|
|
|
global.platformNode = new BrainyPlatformNode()
|
|
|
|
|
|
|
|
|
|
// Set up a global flag to track TensorFlow.js initialization
|
|
|
|
|
global[TENSORFLOW_INITIALIZED] = false
|
|
|
|
|
|
|
|
|
|
// Monkey patch the registerKernel function to prevent duplicate registrations
|
|
|
|
|
// This will be applied when TensorFlow.js is imported
|
|
|
|
|
const originalRegisterKernel = global.registerKernel
|
|
|
|
|
if (!originalRegisterKernel) {
|
|
|
|
|
// Set up a handler to intercept the registerKernel function when it's defined
|
|
|
|
|
Object.defineProperty(global, 'registerKernel', {
|
|
|
|
|
set: function (newRegisterKernel) {
|
|
|
|
|
// Replace the setter with our patched version
|
|
|
|
|
Object.defineProperty(global, 'registerKernel', {
|
|
|
|
|
value: function (kernel: any) {
|
|
|
|
|
// Check if this kernel is already registered
|
|
|
|
|
const kernelName = kernel.kernelName
|
|
|
|
|
const backendName = kernel.backendName
|
|
|
|
|
const key = `${kernelName}_${backendName}`
|
|
|
|
|
|
|
|
|
|
// Use a global registry to track registered kernels
|
|
|
|
|
if (!global.__REGISTERED_KERNELS__) {
|
|
|
|
|
global.__REGISTERED_KERNELS__ = new Set()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If this kernel is already registered, skip it
|
|
|
|
|
if (global.__REGISTERED_KERNELS__.has(key)) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Otherwise, register it and add it to our registry
|
|
|
|
|
global.__REGISTERED_KERNELS__.add(key)
|
|
|
|
|
return newRegisterKernel(kernel)
|
|
|
|
|
},
|
|
|
|
|
configurable: true,
|
|
|
|
|
writable: true
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
configurable: true
|
|
|
|
|
})
|
2025-07-04 14:42:33 -07:00
|
|
|
}
|
2025-07-14 11:12:51 -07:00
|
|
|
|
|
|
|
|
// Mark the patch as applied
|
|
|
|
|
patchApplied = true
|
|
|
|
|
console.log('Brainy: Successfully applied TensorFlow.js platform patch')
|
2025-07-07 10:19:04 -07:00
|
|
|
} catch (error) {
|
2025-07-14 11:12:51 -07:00
|
|
|
console.warn('Brainy: Failed to apply TensorFlow.js platform patch:', error)
|
2025-07-04 14:42:33 -07:00
|
|
|
}
|
|
|
|
|
}
|