Pure Go JavaScript/TypeScript execution engine using goja + esbuild. Provides sandboxed code execution with a call() bridge for agent actions, automatic TypeScript detection and transpilation, timeout enforcement, max-calls limits, and a concurrent execution pool. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
84 lines
2.0 KiB
Go
84 lines
2.0 KiB
Go
package jsruntime
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
)
|
|
|
|
// Pool manages a pool of reusable goja VMs for concurrent JavaScript execution.
|
|
// Each Execute call acquires a VM from the pool, runs the code, then releases
|
|
// the VM back. If all VMs are in use, callers block until one becomes available
|
|
// or the context is cancelled.
|
|
type Pool struct {
|
|
size int
|
|
available chan struct{} // semaphore — each token represents a "slot"
|
|
mu sync.Mutex
|
|
closed bool
|
|
}
|
|
|
|
// NewPool creates a new runtime pool with the given concurrency limit.
|
|
// The size determines how many concurrent Execute calls can run simultaneously.
|
|
func NewPool(size int) *Pool {
|
|
if size < 1 {
|
|
size = 1
|
|
}
|
|
|
|
p := &Pool{
|
|
size: size,
|
|
available: make(chan struct{}, size),
|
|
}
|
|
|
|
// Fill the semaphore
|
|
for i := 0; i < size; i++ {
|
|
p.available <- struct{}{}
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
// Execute acquires a slot from the pool, runs the code, and releases the slot.
|
|
// A fresh goja VM is created for each execution to ensure clean state isolation.
|
|
// Blocks if all slots are in use; respects context cancellation.
|
|
func (p *Pool) Execute(ctx context.Context, code string, caller ToolCaller, opts ExecuteOptions) (*ExecuteResult, error) {
|
|
p.mu.Lock()
|
|
if p.closed {
|
|
p.mu.Unlock()
|
|
return nil, fmt.Errorf("pool is closed")
|
|
}
|
|
p.mu.Unlock()
|
|
|
|
// Acquire a slot (blocks if pool is exhausted)
|
|
select {
|
|
case <-p.available:
|
|
// Got a slot
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
}
|
|
|
|
// Always release the slot when done
|
|
defer func() {
|
|
p.available <- struct{}{}
|
|
}()
|
|
|
|
// Execute with a fresh VM (created inside Execute)
|
|
return Execute(ctx, code, caller, opts)
|
|
}
|
|
|
|
// Size returns the configured pool size.
|
|
func (p *Pool) Size() int {
|
|
return p.size
|
|
}
|
|
|
|
// Available returns the number of available slots.
|
|
func (p *Pool) Available() int {
|
|
return len(p.available)
|
|
}
|
|
|
|
// Close marks the pool as closed. Subsequent Execute calls will return an error.
|
|
func (p *Pool) Close() {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
p.closed = true
|
|
}
|