Class: LittleGhost::CodeMode::JavascriptEngine

Inherits:
Engine
  • Object
show all
Defined in:
lib/little_ghost/code_mode/javascript_engine.rb

Overview

Runs model-written JavaScript in an isolated V8 context.

This optional engine uses MiniRacer. Requiring LittleGhost does not load MiniRacer; applications opt in by requiring little_ghost/code_mode/javascript_engine.

Constant Summary collapse

DEFAULT_LIMITS =

Resource and concurrency limits applied when the application does not override them.

{
  output_bytes: 64 * 1024 * 1024,
  memory_bytes: 128 * 1024 * 1024,
  cpu_seconds: 30,
  file_bytes: 1024 * 1024,
  wall_seconds: 3_600,
  max_concurrency: 8
}.freeze

Instance Method Summary collapse

Instance Method Details

#instructions(catalog:, prompts: nil) ⇒ Object

Builds JavaScript usage instructions and TypeScript declarations for catalog.



53
54
55
56
57
# File 'lib/little_ghost/code_mode/javascript_engine.rb', line 53

def instructions(catalog:, prompts: nil)
  javascript_catalog = Javascript::Catalog.new(catalog)
  renderer = prompts || ->(key, **locals) { FrameworkPrompts.new.render(key, locals:) }
  renderer.call("code_mode/javascript/instructions", declarations: javascript_catalog.declarations)
end

#languageObject

Returns the :javascript language identifier.



49
# File 'lib/little_ghost/code_mode/javascript_engine.rb', line 49

def language = :javascript

#open_session(broker:, sandbox_factory:, limits: {}, framework_prompt_scope: {}) ⇒ Object

Opens a JavaScript Session with engine defaults merged with limits. Unsupported limit keys raise ArgumentError.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/little_ghost/code_mode/javascript_engine.rb', line 61

def open_session(broker:, sandbox_factory:, limits: {}, framework_prompt_scope: {})
  configured_limits = normalize_limits(limits, defaults: DEFAULT_LIMITS)
  root = Dir.mktmpdir("little-ghost-javascript-")
  runtime_paths = javascript_runtime_paths
  workspace = Workspace.new(
    root:,
    paths: runtime_paths,
    teardown: lambda do |workspace:, **|
      FileUtils.remove_entry_secure(workspace.root) if File.exist?(workspace.root)
    end
  ).open
  sandbox = sandbox_factory.call(
    workspace:,
    required_runtime_paths: runtime_paths.keys.to_h { |name| [name, :read_only] }
  )
  sandbox.open
  client = Javascript::Client.new(
    session_factory: lambda {
      sandbox.start_program(
        host_command,
        cwd: ".",
        environment: child_environment,
        output_bytes: configured_limits.fetch(:output_bytes),
        memory_bytes: configured_limits[:memory_bytes],
        cpu_seconds: configured_limits[:cpu_seconds],
        file_bytes: configured_limits[:file_bytes],
        allow_subprocesses: allow_subprocesses_for(sandbox)
      )
    },
    framework_prompt_scope:
  )
  Javascript::Session.new(
    broker:, client:, sandbox:, workspace:,
    max_concurrency: configured_limits.fetch(:max_concurrency),
    wall_seconds: configured_limits.fetch(:wall_seconds),
    framework_prompt_scope:
  )
rescue
  sandbox&.close
  workspace&.close
  FileUtils.remove_entry_secure(root) if root && File.exist?(root)
  raise
end