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:) ⇒ Object

Builds JavaScript usage instructions and TypeScript declarations for catalog.



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/little_ghost/code_mode/javascript_engine.rb', line 53

def instructions(catalog:)
  javascript_catalog = Javascript::Catalog.new(catalog)
  <<~INSTRUCTIONS.strip
    Use JavaScript to call the available tools and compose their results.

    Program lifecycle:
    - Every exec call starts a fresh program in a new V8 context.
    - Exec and wait observe it for up to one minute. They return sooner when it finishes.
    - If exec or wait returns `still_working`, call wait to observe the same running program again.
    - Wait does not pause, resume, or restart the program.
    - Use stop when the result is no longer needed.
    - Do not call wait or stop after `completed`, `error`, or `terminated`.

    Tool calls:
    - Tool functions return Promises. Use await or Promise.all.
    - JSON results become objects or arrays. Other results remain strings.
    - Unawaited tool calls finish before the program exits.
    - `ALL_TOOLS` contains the complete runtime catalog.

    Output and completion:
    - Use `text(value)` for user-visible output.
    - Use `exit()` to complete early.

    The V8 context has no Node.js APIs, filesystem, network, console, WebAssembly, or process API.

    Available tool methods:

    #{javascript_catalog.declarations}
  INSTRUCTIONS
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: {}) ⇒ Object

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



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/little_ghost/code_mode/javascript_engine.rb', line 86

def open_session(broker:, sandbox_factory:, limits: {})
  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)
    )
  })
  Javascript::Session.new(
    broker:, client:, sandbox:, workspace:,
    max_concurrency: configured_limits.fetch(:max_concurrency),
    wall_seconds: configured_limits.fetch(:wall_seconds)
  )
rescue
  sandbox&.close
  workspace&.close
  FileUtils.remove_entry_secure(root) if root && File.exist?(root)
  raise
end