Module: LittleGhost::CodeMode::Javascript::Host

Defined in:
lib/little_ghost/code_mode/javascript/host.rb

Overview

:nodoc: all

Defined Under Namespace

Classes: Program, Runner

Constant Summary collapse

MAX_ACTIVE_PROGRAMS =
8
MAX_SOURCE_BYTES =
1024 * 1024
CONTEXT_MEMORY_BYTES =
64 * 1024 * 1024
JAVASCRIPT_TIMEOUT_MS =
10_000
MAX_PENDING_TOOL_CALLS =
1_024
Terminated =
Class.new(StandardError)
BOOTSTRAP =
<<~'JAVASCRIPT'
  const __LITTLE_GHOST_CONTROL_IDENTIFIER__ = (() => {
    "use strict";
    const definitions = __LITTLE_GHOST_TOOL_DEFINITIONS__;
    const definitionIndex = Object.fromEntries(definitions.map((definition) => [definition.name, definition]));
    const calls = [];
    const outputs = [];
    const pending = new Map();
    const exitSignal = Object.freeze({exit: true});
    let nextCallId = 0;
    let done = false;
    let failure = null;

    const stringify = (value) => {
      if (typeof value === "string") return value;
      if (value === undefined) return "undefined";
      if (typeof value === "bigint") return value.toString();
      const encoded = JSON.stringify(value);
      return encoded === undefined ? String(value) : encoded;
    };

    const enqueue = (name, args) => new Promise((resolve, reject) => {
      if (!Object.prototype.hasOwnProperty.call(definitionIndex, name)) {
        reject(new Error(`Unknown tool: ${name}`));
        return;
      }
      const id = String(++nextCallId);
      pending.set(id, {resolve, reject});
      calls.push({call_id: id, name, arguments: args === undefined ? {} : args});
    });

    const tools = new Proxy(Object.freeze(Object.create(null)), {
      get(_target, property) {
        if (property === "then") return undefined;
        if (typeof property !== "string") return undefined;
        return (args = {}) => enqueue(property, args);
      }
    });
    const catalog = definitions.map(({name, description}) => Object.freeze({name, description}));

    const text = (value) => { outputs.push(stringify(value)); };
    const exit = () => { throw exitSignal; };
    const errorText = (error) => {
      if (error && typeof error.stack === "string") return error.stack;
      if (error && typeof error.message === "string") return error.message;
      return stringify(error);
    };

    Object.defineProperties(globalThis, {
      tools: {value: tools, writable: false, configurable: false},
      ALL_TOOLS: {value: Object.freeze(catalog), writable: false, configurable: false},
      text: {value: text, writable: false, configurable: false},
      exit: {value: exit, writable: false, configurable: false},
      console: {value: undefined, writable: false, configurable: false},
      process: {value: undefined, writable: false, configurable: false},
      require: {value: undefined, writable: false, configurable: false},
      fetch: {value: undefined, writable: false, configurable: false},
      WebAssembly: {value: undefined, writable: false, configurable: false},
      ArrayBuffer: {value: undefined, writable: false, configurable: false},
      SharedArrayBuffer: {value: undefined, writable: false, configurable: false},
      DataView: {value: undefined, writable: false, configurable: false},
      Atomics: {value: undefined, writable: false, configurable: false},
      Int8Array: {value: undefined, writable: false, configurable: false},
      Uint8Array: {value: undefined, writable: false, configurable: false},
      Uint8ClampedArray: {value: undefined, writable: false, configurable: false},
      Int16Array: {value: undefined, writable: false, configurable: false},
      Uint16Array: {value: undefined, writable: false, configurable: false},
      Int32Array: {value: undefined, writable: false, configurable: false},
      Uint32Array: {value: undefined, writable: false, configurable: false},
      BigInt64Array: {value: undefined, writable: false, configurable: false},
      BigUint64Array: {value: undefined, writable: false, configurable: false},
      Float32Array: {value: undefined, writable: false, configurable: false},
      Float64Array: {value: undefined, writable: false, configurable: false}
    });

    return Object.freeze({
      drain: () => ({
        calls: calls.splice(0),
        outputs: outputs.splice(0),
        done,
        failure
      }),
      resolve: (id, ok, value) => {
        const continuation = pending.get(String(id));
        if (!continuation) return false;
        pending.delete(String(id));
        if (ok) continuation.resolve(value);
        else continuation.reject(new Error(String(value)));
        return true;
      },
      run: (source) => {
        let execution;
        try {
          execution = (0, eval)(`(async () => {\n${source}\n})()`);
        } catch (error) {
          done = true;
          failure = errorText(error);
          return;
        }
        Promise.resolve(execution).then(
          () => { done = true; },
          (error) => {
            done = true;
            failure = error === exitSignal ? null : errorText(error);
          }
        );
      },
    });
  })();
JAVASCRIPT

Class Method Summary collapse

Class Method Details

.runObject



341
342
343
344
# File 'lib/little_ghost/code_mode/javascript/host.rb', line 341

def run
  MiniRacer::Platform.set_flags!(:jitless)
  Runner.new.run
end