lua

package
v0.0.0-...-072aef6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 25, 2026 License: AGPL-3.0 Imports: 37 Imported by: 0

Documentation

Overview

Lua bindings for the ai.* module (ai.chat, ai.complete, ai.embed).

DELIBERATE CONVENTION DEVIATION:

All other rela Lua bindings raise errors via ls.RaiseError. The ai.* bindings instead return (nil, err_table) for *expected runtime failures* — network errors, HTTP errors, missing config, rate limits, upstream 5xx, etc. — because AI calls are inherently network-bound and scripts should be able to handle failure inline rather than wrap every call in pcall.

PROGRAMMING ERRORS still raise via RaiseError (wrong argument type, empty messages/input, malformed entries). The taxonomy is:

expected runtime failure  -> (nil, err_table)
programming error         -> RaiseError

The error table has stable fields: kind (string), status (number), message (string), retry_after (number, seconds). Scripts branch on err.kind (e.g. "rate_limited", "auth", "server_error", ...) without parsing prose error messages.

CONCURRENCY: ai.chat assumes single-threaded LState use. gopher-lua *lua.LState is NOT safe for concurrent goroutine use. ai.Provider implementations must be safe to share across runtimes (the default OpenAICompatProvider is, because http.Client is).

Do not "fix" this convention without reading the planning document for TKT-YBKB. The string-error alternative was deliberately rejected.

This file implements interactive flows using Lua coroutines.

Lua bindings for the http.* module.

Provides HTTP client capabilities for Lua scripts to call external APIs. Follows the same error convention as the ai.* module:

expected runtime failure  -> (nil, err_table)
programming error         -> RaiseError

The error table mirrors ai.Error so scripts switching between ai.chat and http.request see the same shape: kind (string), message (string), retry_after (number, always 0 for http), details (string, unwrapped cause when present). Scripts branch on err.kind.

Error kinds:

  • timeout: request exceeded deadline
  • canceled: request was canceled (e.g., runtime shutting down)
  • network: DNS, connection refused, TLS, read error, etc.
  • bad_response: response body exceeded the 10 MiB cap

JSON encode/decode helpers live separately under rela.json (see json.go).

Lua bindings for the rela.json.* submodule.

JSON encode/decode helpers exposed on the rela namespace alongside rela.md.* (markdown). They are placed under rela rather than http because nothing about JSON serialization is HTTP-specific — scripts reach for them in flow scripts, document renderers, and ad-hoc transformations as often as in API calls.

Convention split (matches http.* and ai.*):

rela.json.encode(value)     -> string  (raises on encoding failure)
rela.json.decode(string)    -> (value, nil) | (nil, err_table)

json.encode raises because the only ways it can fail are programming errors (cycles past the cycle-marker fallback, unsupported value types). json.decode returns (nil, err_table) because invalid JSON typically comes from external data — scripts should branch on the error rather than wrap each call in pcall.

The error table for decode failures uses kind="bad_response" — the same kind http.json_decode previously used — so existing scripts migrate without changing their error handling.

Package lua provides markdown AST manipulation functions for Lua scripts. The rela.md module enables parsing, transforming, and rendering markdown content.

Package lua provides a Lua scripting runtime for rela with bindings to query entities, relations, and output results.

The runtime is sandboxed: only safe Lua libraries are loaded (base, table, string, math, utf8, coroutine). The io, os, and debug libraries are NOT available to prevent filesystem access and code execution. File operations are only possible through the provided rela.write_file() function which validates paths are within the project root.

Index

Constants

View Source
const DefaultTimeout = 30 * time.Second

DefaultTimeout is the default execution timeout for scripts. This prevents infinite loops and resource exhaustion.

Variables

View Source
var ErrNoReturnValue = errors.New("script did not return a value")

ErrNoReturnValue is returned by RunActionString when the script did not return a value. Action handlers can use errors.Is to check for this.

Functions

func EntityToTable

func EntityToTable(ls *lua.LState, e *entity.Entity) *lua.LTable

EntityToTable converts an entity.Entity to a Lua table. The returned table has a prop(name, default) method. Exported for use by workspace automation execution.

func GoToLuaValue

func GoToLuaValue(ls *lua.LState, v interface{}) lua.LValue

GoToLuaValue converts a Go value to a Lua value. Exported for use by workspace automation execution.

Types

type Action

type Action struct {
	ID    string `json:"id"`
	Label string `json:"label"`
	Style string `json:"style,omitempty"`
}

Action represents a form action button.

type Cache

type Cache struct {
	// contains filtered or unexported fields
}

Cache is an in-memory key/value store with TTL and global LRU eviction, shared across all Lua runtimes in a single process. It is safe for concurrent use. Callers inject a shared Cache into each lua.Runtime via WithCache; runtimes namespace their operations by the script path (see Runtime.scriptPath) so two scripts that happen to pick the same user key do not collide.

Not persisted: each process starts with an empty cache. For cross- process durability see ticket TKT-135Q (the AI response disk cache).

Concurrency: uses a plain sync.Mutex (not RWMutex). Every hit path mutates lastAccess for LRU accounting, so a reader-writer split would require two-phase locking (RLock lookup, Lock upgrade for lastAccess) without a clear win. Misses, nil-deletes, and TTL-expiry deletes do not touch lastAccess — under heavy read-miss contention a switch to RWMutex (with atomic lastAccess) is a defensible optimization, but needs a benchmark before the complexity is worth it.

The zero value is not usable; construct with NewCache.

func NewCache

func NewCache() *Cache

NewCache builds a ready-to-use Cache. The returned value is a singleton in the logical sense — callers typically construct one per process and pass it to every lua.Runtime they build.

func (*Cache) SetNow

func (c *Cache) SetNow(f func() time.Time)

SetNow overrides the Cache's time source. Intended for tests that need deterministic TTL/LRU behavior without time.Sleep. Production code should never call this.

INVARIANT: the replacement function MUST be safe to call under c.mu and is exclusively invoked from locked methods. Adding a lock-free caller of c.now (e.g. a metrics accessor) would create a data race with any closure-based fake clock that mutates a shared counter. If you need such an accessor, snapshot c.now under the lock first.

type Event

type Event struct {
	Action string         `json:"action"`
	Data   map[string]any `json:"data,omitempty"`
}

Event represents user input from a screen.

type Field

type Field struct {
	Name        string         `json:"name"`
	Type        string         `json:"type"`
	Label       string         `json:"label,omitempty"`
	Content     string         `json:"content,omitempty"` // Markdown content (for type="markdown")
	Placeholder string         `json:"placeholder,omitempty"`
	Required    bool           `json:"required,omitempty"`
	Default     any            `json:"default,omitempty"`
	Options     []SelectOption `json:"options,omitempty"`
	Lines       int            `json:"lines,omitempty"`
	Min         *float64       `json:"min,omitempty"`
	Max         *float64       `json:"max,omitempty"`
	Step        *float64       `json:"step,omitempty"`
	MinDate     string         `json:"min_date,omitempty"`
	MaxDate     string         `json:"max_date,omitempty"`
}

Field represents a form field.

type FlowRuntime

type FlowRuntime struct {
	// contains filtered or unexported fields
}

FlowRuntime manages the execution of an interactive Lua flow.

func NewFlowRuntime

func NewFlowRuntime(r *Runtime, transport Transport) *FlowRuntime

NewFlowRuntime creates a new flow runtime with the given transport.

func (*FlowRuntime) RunFile

func (f *FlowRuntime) RunFile(path string, args []string) error

RunFile loads and executes a flow script file.

func (*FlowRuntime) RunString

func (f *FlowRuntime) RunString(code string) error

RunString executes a flow script from a string.

type Option

type Option func(*Runtime)

Option configures a Runtime.

func LoadContextOptions

func LoadContextOptions(cacheDir, scriptPath string) ([]Option, error)

LoadContextOptions loads AI provider and secrets from the .rela directory and returns them as runtime options. This is the single entry point for all Lua callers (CLI, MCP, automation, actions) to load project-level context into a runtime.

scriptPath is the script being executed (used to resolve per-script secrets). Pass "" for inline code (skips secrets loading).

Returns ai.ErrConfigNotFound (via errors.Is) when AI is not configured — callers that want to silently ignore missing AI can check for it.

func WithAIProvider

func WithAIProvider(p ai.Provider) Option

WithAIProvider wires an AI provider into the runtime so the ai.* Lua bindings are functional. When omitted, ai.chat and ai.complete return a typed not_configured error.

func WithActionMode

func WithActionMode() Option

WithActionMode marks the runtime as running in action mode, which changes rela.output behavior (logs a warning instead of writing to stdout).

func WithCache

func WithCache(c *Cache) Option

WithCache wires a process-wide cache into the runtime so the rela.cache.* Lua bindings are registered. When omitted (or passed nil), rela.cache.* is absent from the rela table — calling it from Lua raises "attempt to call a nil value" from the VM. The cache is namespaced by the runtime's script path (set by RunFile); inline or eval contexts that call rela.cache.* receive a fixed Lua error rather than sharing a nameless namespace.

func WithContext

func WithContext(ctx context.Context) Option

WithContext sets a parent context for the runtime. Cancellation of this context propagates into in-flight Lua operations (e.g. long-running loops or blocking calls from bindings). When combined with WithTimeout, the timeout is derived from this parent so canceling the parent also cancels the timeout-bound context.

Typical usage: pass cmd.Context() from a cobra RunE so that Ctrl+C interrupts script execution.

func WithDocumentMode

func WithDocumentMode(documentID, entryID string) Option

WithDocumentMode marks the runtime as running a data-entry document renderer. Populates the rela.document.{id, entry_id} table and sets rela.mode = "document" so scripts can branch on context; also changes rela.output behavior to emit a warning line (the captured stdout is the rendered document, so JSON noise in-band is almost certainly a mistake). documentID is the key under documents: in data-entry.yaml; entryID is the ID of the entity being rendered.

func WithOutputDir

func WithOutputDir(dir string) Option

WithOutputDir sets the output directory for write_file. If the path is absolute, files will be written there directly. If relative, it's relative to the project root.

func WithParams

func WithParams(params map[string]string) Option

WithParams sets the rela.params table contents for action scripts. Params are static key-value strings from the data-entry config.

func WithSecrets

func WithSecrets(secrets map[string]string) Option

WithSecrets sets the rela.secrets table contents. Secrets are loaded from .rela/secrets.yaml by the caller.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the execution timeout for scripts. Default is 30 seconds. Set to 0 to disable timeout (not recommended).

type ReadDeps

type ReadDeps struct {
	Store       store.Store
	Tracer      tracer.Tracer
	Searcher    search.Searcher
	Meta        *metamodel.Metamodel
	ProjectRoot string
}

ReadDeps is the capability bundle required to run a read-only Lua runtime. A runtime built from ReadDeps (see NewReader) exposes only query, trace, search, schema introspection, and output-to-stdout bindings. It cannot mutate the graph and cannot write files — both rela.create_entity et al. and rela.write_file are absent from the rela.* table on a reader.

ProjectRoot is the absolute project path; used by writer runtimes to resolve the output directory for rela.write_file.

type Runtime

type Runtime struct {
	L *lua.LState
	// contains filtered or unexported fields
}

Runtime wraps gopher-lua VM with rela bindings.

The runtime is constructed via NewReader (read-only) or NewWriter (read- write). A read-only runtime has no mutation bindings (create/update/delete of entities and relations) registered at all; calling those from Lua raises a "attempt to call a nil value" error from the VM itself.

func NewReader

func NewReader(d ReadDeps, stdout io.Writer, opts ...Option) *Runtime

NewReader creates a read-only Runtime with query/trace/search/output bindings. Mutation bindings (create/update/delete for entities and relations) are not registered; calling them from Lua raises "attempt to call a nil value".

The Lua VM is sandboxed with only safe libraries loaded (no io, os, or debug).

func NewWriter

func NewWriter(d WriteDeps, stdout io.Writer, opts ...Option) *Runtime

NewWriter creates a read-write Runtime. All read bindings plus mutation bindings (create_entity, update_entity, delete_entity, create_relation, delete_relation) are registered.

The Lua VM is sandboxed with only safe libraries loaded (no io, os, or debug).

func (*Runtime) Close

func (r *Runtime) Close()

Close releases Lua VM resources.

func (*Runtime) LState

func (r *Runtime) LState() *lua.LState

LState returns the underlying Lua state for setting globals.

func (*Runtime) RunActionString

func (r *Runtime) RunActionString(code, name string) (interface{}, error)

RunActionString executes Lua code as an action, returning the script's top-of-stack return value as a Go interface{}. Returns ErrNoReturnValue if the script did not return any values.

func (*Runtime) RunFile

func (r *Runtime) RunFile(path string, args []string) error

RunFile executes a Lua script file with arguments. Shebang lines (starting with #!) are automatically stripped.

RunFile sets the runtime's scriptPath (via filepath.Clean(path)) so the rela.cache.* bindings can namespace entries by script. Callers using RunString/inline code do not get this identity and any rela.cache.* call raises a Lua error.

func (*Runtime) RunFileContent

func (r *Runtime) RunFileContent(path string, content []byte, args []string) error

RunFileContent executes Lua code loaded from `path`, using the already-read `content`. This exists for callers that need traversal- resistant file reads (e.g. MCP's lua_run, which uses os.OpenRoot). Effects match RunFile: chunk name for errors, scriptPath for rela.cache.* namespacing, rela.args for script arguments. Shebangs are stripped.

func (*Runtime) RunString

func (r *Runtime) RunString(code string) error

RunString executes Lua code from a string. Shebang lines (starting with #!) are automatically stripped.

func (*Runtime) SetArgs

func (r *Runtime) SetArgs(args []string)

SetArgs sets the script arguments (rela.args) before execution.

func (*Runtime) SetScriptPath

func (r *Runtime) SetScriptPath(path string)

SetScriptPath records a cache namespace identity for callers that run via RunString or RunActionString (not RunFile/RunFileContent, which set it automatically). The "path" here is used purely as the rela.cache.* namespace — for inline-code callers that need a stable cache scope it's the caller's name for the script (e.g. "validations/<rule-name>"); for file-backed callers RunFileContent is preferred because it sets chunk name, args, and namespace in one step. The path is cleaned (filepath.Clean).

SetScriptPath persists across subsequent RunString calls on the same Runtime. Call with "" to revert to inline/eval mode (cache calls raise). RunFile / RunFileContent override it.

type Screen

type Screen struct {
	Type        string   `json:"type"`
	Title       string   `json:"title,omitempty"`
	Description string   `json:"description,omitempty"`
	Fields      []Field  `json:"fields,omitempty"`
	Actions     []Action `json:"actions,omitempty"`
}

Screen represents a UI screen to present to the user.

type SelectOption

type SelectOption struct {
	Value string `json:"value"`
	Label string `json:"label"`
}

SelectOption represents a select option.

type Transport

type Transport interface {
	// Present displays a screen to the user and blocks until an event is received.
	Present(screen Screen) (Event, error)
}

Transport presents screens to users and receives events.

type WriteDeps

type WriteDeps struct {
	ReadDeps
	EntityManager entitymanager.EntityManager
}

WriteDeps is the capability bundle required to run a read-write Lua runtime. A runtime built from WriteDeps (see NewWriter) additionally exposes create/update/delete bindings for entities and relations.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL