plugins

package
v1.5.0 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2026 License: Apache-2.0 Imports: 44 Imported by: 0

Documentation

Overview

Package plugins runs third-party TypeScript plugins (spec 5.3–5.5): manifest validation, the sandbox and the ctx API. It does not import core; the host side is injected.

Index

Constants

View Source
const (
	MaxBodyBytes    = 256 << 10 // each of a message's text and html, as a plugin gets it
	MaxStorageValue = 64 << 10
	MaxStorageTotal = 1 << 20  // keys and values of one plugin
	MaxLLMBytes     = 64 << 10 // llm.complete prompt and answer; llm.decide input and question text
	MaxQuestions    = 128      // per llm.decide call: rejected beyond, never split
	MaxFetchBody    = 1 << 20

	EventReceived   = "message.received"
	EventBeforeSend = "message.beforeSend"
)

Host-side data limits (spec 5.5).

View Source
const (
	MaxFetchResponse = 5 << 20
	FetchTimeout     = 30 * time.Second
)

Network limits of ctx.net.fetch (spec 5.5).

View Source
const AnyPlatform = "any"
View Source
const BuildDir = ".build"

BuildDir is where a snapshot keeps its compiled code; it is not part of the snapshot.

View Source
const MaxResultBytes = 1 << 20

MaxResultBytes caps the JSON a plugin call hands back to the host.

View Source
const NativeABI uint32 = 1

NativeABI is the C ABI version this build of Godwit loads.

Variables

View Source
var (
	ScriptLimits = Limits{File: 5 << 20, Total: 20 << 20}
	NativeLimits = Limits{File: 64 << 20, Total: 80 << 20}
)

Snapshot limits by kind: a native plugin carries a shared library.

View Source
var (
	// ErrNativeExited: the plugin's process ended (a crash, or it never loaded).
	ErrNativeExited = errors.New("plugin: native plugin exited")
	// ErrNativeABI: the call failed below the envelope.
	ErrNativeABI = errors.New("plugin: native call failed")
)
View Source
var (
	ErrPermission  = errors.New("plugin: permission denied")
	ErrTimeout     = errors.New("plugin: call timed out")
	ErrMemoryLimit = errors.New("plugin: memory limit exceeded")
	ErrClosed      = errors.New("plugin: closed")
	ErrTooLarge    = errors.New("plugin: data too large")
)
View Source
var Platforms = []string{"darwin-arm64", "darwin-amd64", "windows-amd64", "windows-arm64", "linux-amd64", "linux-arm64"}

Platforms are the artifact keys of native releases; a script release has only AnyPlatform.

Functions

func Build

func Build(dir, main string) ([]byte, error)

Build compiles the snapshot in dir with ESBuild, reusing a cached build.

func BuildWith

func BuildWith(c Compiler, dir, main string) ([]byte, error)

BuildWith compiles the snapshot in dir with c. The result is cached in dir/.build under the snapshot's hash, main and c.Key(); other builds there are removed.

func CompareVersions added in v1.3.0

func CompareVersions(a, b string) int

CompareVersions orders versions like 1.2.10 and v1.2.0-beta: numeric dot parts (missing ones are 0), then a release after its prereleases, which compare as strings.

func EgressFor

func EgressFor(kind InvocationKind, m Manifest, granted, holdsMail, scopeReadMail bool) (hosts []string, needsApproval bool)

EgressFor is the egress rule for one call of kind into plugin m (plan 4 规格解释): the hosts its Invocation may fetch from, and whether the call needs an egress approval listing them first. granted: the plugin's current permissions are granted; holdsMail: the plugin was ever handed mail content (its storage may keep it, across calls, updates and restarts); scopeReadMail: the call's input may carry mail content (a Wit run that has read mail calling a tool, an action on a selection). Init, card and beforeSend never fetch; a hook gets every declared host; an action or a tool needs approval when the plugin holds net and any of mail:read, holdsMail or scopeReadMail. Since ctx.storage outlives a call, "this call read no mail" is never trusted for a plugin that has or ever had mail.

func FailReason

func FailReason(err error) string

FailReason is the audit reason of a request the policy allowed that failed: dns_error, tls_error or network_error.

func FileSHA256 added in v1.3.0

func FileSHA256(path string) (string, error)

FileSHA256 is the hex SHA-256 of a file's contents.

func HashDir

func HashDir(dir string, lim Limits) (string, error)

HashDir hashes the files of a plugin directory as Snapshot does.

func HostPort

func HostPort(u *url.URL) string

HostPort is u's lowercased host with its port, the scheme's default (https 443, http 80) when it has none: the destination an audit row names.

func IsNativeFailure added in v1.3.0

func IsNativeFailure(err error) bool

IsNativeFailure reports whether err means the plugin misbehaved (exited, broke the ABI or timed out), as opposed to answering with an error.

func LibraryName added in v1.3.0

func LibraryName() string

LibraryName is the file a native plugin's shared library has in its directory on this platform.

func PlatformKey added in v1.3.0

func PlatformKey() string

PlatformKey is this build's artifact key.

func QuestionBytes

func QuestionBytes(qs []Question) int

QuestionBytes is the size of the questions' text: instructions, values and descriptions.

func ScrubPaths

func ScrubPaths(s string) string

ScrubPaths replaces absolute paths in s with "<path>".

func ServeNative added in v1.3.0

func ServeNative(args []string, stdin io.Reader, stdout, stderr io.Writer) int

ServeNative is `godwit plugin-host`. Exit codes: 3 the file is not the one granted (nothing was loaded), 4 it did not load or init, 0 stdin closed.

func ServeNativeWith added in v1.3.0

func ServeNativeWith(args []string, stdin io.Reader, stdout, stderr io.Writer, open NativeOpener) int

ServeNativeWith is ServeNative with another loader (tests).

func Snapshot

func Snapshot(src, dst string, lim Limits) (sourceHash string, err error)

Snapshot copies the plugin directory src to dst, which must not exist yet, and returns the hash of the copy. It copies into a sibling temp directory and renames that to dst, so dst is complete or absent.

Types

type ActionInfo

type ActionInfo struct {
	ID    string
	Label Text
	On    []string
}

type Answer

type Answer = ai.Answer

Question and Answer are typed judge questions and answers, as llm.decide and Wit's classify_mail take and give them: the OpenAI Decisions API's shape (predicate, choice, score; a refusal answer), whatever the judge.

type Artifact added in v1.3.0

type Artifact struct {
	URL    string `json:"url"`
	SHA256 string `json:"sha256"`
	Size   int64  `json:"size"`
}

Artifact is one released file: exactly Size bytes with this SHA-256.

type AttachmentMeta

type AttachmentMeta struct {
	Name string `json:"name"`
	Type string `json:"type"`
	Size int64  `json:"size"`
}

type CallScope

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

CallScope is one top-level call. It is only valid on the owner goroutine, inside the Call that made it (including host functions JS invokes then).

func (*CallScope) Async

func (c *CallScope) Async(fn func(ctx context.Context) (any, error)) goja.Value

Async returns a promise settled by fn, which runs on its own goroutine with the call's context. The result goes through rt.ToValue (json tags name struct fields); an error rejects with a plain Error whose message is redacted and path-scrubbed. The settlement is posted back to the owner goroutine tagged with this call; if the call has ended by then it is dropped and never runs.

func (*CallScope) Context

func (c *CallScope) Context() context.Context

Context ends with the call; synchronous host work should honour it, since Interrupt cannot stop a running Go function.

func (*CallScope) Invocation

func (c *CallScope) Invocation() Invocation

func (*CallScope) Runtime

func (c *CallScope) Runtime() *goja.Runtime

Runtime is the goja runtime; use it only on the owner goroutine during the call.

func (*CallScope) VM

func (c *CallScope) VM() *VM

type Client

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

Client is ctx.net.fetch's HTTP client. It resolves names itself and dials only addresses it has checked (no DNS rebinding between check and connect), never uses a proxy, and re-checks every redirect.

func NewClient

func NewClient(opts NetOptions) *Client

func (*Client) Do

func (c *Client) Do(ctx context.Context, allowed []string, req FetchRequest) (FetchResponse, []Hop, error)

Do fetches req if its URL is allowed (host[:port], 443 by default) and returns the response, every hop for the audit (none only for an unparseable URL), and an error; a policy refusal or the timeout is a *NetError. No partial body is ever returned.

func (*Client) Transport

func (c *Client) Transport() *http.Transport

Transport is the client's transport, for a caller with its own request policy (an MCP server): no proxy, and only addresses that pass the IP rules.

type Clock

type Clock interface {
	AfterFunc(d time.Duration, f func()) (stop func() bool)
}

Clock schedules the call deadlines and the watchdog ticks; tests inject a fake one.

type Compiler

type Compiler interface {
	// Key identifies the compiler version and options: builds cached under
	// another key are not reused.
	Key() string
	Compile(dir, main string) ([]byte, error)
}

Compiler turns a snapshot into the script the plugin VM runs.

var ESBuild Compiler = esbuildCompiler{}

ESBuild bundles TypeScript or JavaScript with esbuild.

type ConfigField

type ConfigField struct {
	Key         string          `json:"key"`
	Type        string          `json:"type"`
	Label       Text            `json:"label"`
	Options     []Option        `json:"options,omitempty"`
	AllowCustom bool            `json:"allowCustom,omitempty"`
	Required    bool            `json:"required,omitempty"`
	Default     json.RawMessage `json:"default,omitempty"`
	When        *When           `json:"when,omitempty"`
}

ConfigField is one setting the user fills in. Type is text, secret, number, boolean or select. A secret has no default and is stored in the keychain.

func (ConfigField) Accepts

func (f ConfigField) Accepts(v json.RawMessage) bool

Accepts reports whether v is a value f can hold (not for a secret).

type ConfigSnapshot

type ConfigSnapshot struct {
	Values  map[string]any
	Version int64
	Err     error
}

ConfigSnapshot is a plugin's configuration at one config version; Err is why it could not be read (ctx.config then throws). Never changed once made.

type DraftInput

type DraftInput struct {
	AccountID int64    `json:"accountId"`
	To        []string `json:"to"`
	Cc        []string `json:"cc"`
	Subject   string   `json:"subject"`
	HTML      string   `json:"html"`
}

type DraftUpdate

type DraftUpdate struct {
	DraftID  int64  `json:"draftId"`
	Revision int64  `json:"revision"`
	HTML     string `json:"html"`
}

DraftUpdate replaces a draft's body if the draft is still at Revision.

type FetchRequest

type FetchRequest struct {
	URL     string            `json:"url"`
	Method  string            `json:"method"`
	Headers map[string]string `json:"headers"`
	Body    string            `json:"body"`
}

type FetchResponse

type FetchResponse struct {
	Status  int               `json:"status"`
	Headers map[string]string `json:"headers"`
	Body    string            `json:"body"`
}

type Hop

type Hop struct{ Dest, Decision, Reason string }

Hop is one request of a fetch, the first or a redirect: its host:port and its outcome. Decision is "allow" (answered, or redirected onward), "deny" (refused by the policy; Reason is the NetError code) or "error" (the policy allowed it and it failed; Reason is dns_error, network_error, tls_error, timeout or cancelled).

type Host

type Host interface {
	StorageGet(ctx context.Context, key string) (string, bool, error)
	StorageSet(ctx context.Context, key, value string) error
	StorageDelete(ctx context.Context, key string) error
	MailGet(ctx context.Context, id int64) (MailMessage, error)
	MailSearch(ctx context.Context, q string, limit int) ([]MailSummary, error)
	MailSetFlags(ctx context.Context, ids []int64, add, remove []string) error
	MailMove(ctx context.Context, ids []int64, folderID int64) error
	MailCreateDraft(ctx context.Context, d DraftInput) (int64, error)
	MailUpdateDraft(ctx context.Context, u DraftUpdate) (int64, error)
	LLMComplete(ctx context.Context, prompt string) (string, error)
	LLMDecide(ctx context.Context, input string, qs []Question) ([]Answer, error)
	Fetch(ctx context.Context, inv Invocation, req FetchRequest) (FetchResponse, error)
	AuditEgress(ctx context.Context, inv Invocation, dest, decision, reason string) error
}

Host is what the core gives a plugin's ctx. Permissions are checked before any of it is called. It has no way to send mail, and must never get one.

type Invocation

type Invocation struct {
	ID          string
	Kind        InvocationKind
	Write       bool
	EgressHosts []string
	// Unapproved: the call needed an egress approval it did not get, so it
	// has no hosts; a fetch to a declared host is denied as approval_required.
	Unapproved bool
	// Approved: the user approved this call's egress (its hosts were listed).
	// An action or tool call without it is checked again as it fetches.
	Approved bool
	// Config is the call's ctx.config, taken when the host admitted the call
	// (before it waits for the VM); nil makes ctx.config throw.
	Config *ConfigSnapshot
}

Invocation is what the host allows one top-level call into a plugin: whether it may write mail, and the hosts it may fetch from (none: no egress). What the plugin declared when registering does not widen it.

type InvocationKind

type InvocationKind string
const (
	KindInit       InvocationKind = "init"
	KindHook       InvocationKind = "hook"
	KindBeforeSend InvocationKind = "beforeSend"
	KindAction     InvocationKind = "action"
	KindCard       InvocationKind = "card"
	KindTool       InvocationKind = "tool"
	// KindTranslate translates mail the user chose to translate with this
	// plugin: like a hook it gets every declared host without asking each
	// time (docs/superpowers/specs/2026-10-14-native-plugins-design.md 2.7).
	KindTranslate InvocationKind = "translate"
)

type Limits added in v1.3.0

type Limits struct{ File, Total int64 }

Limits caps a snapshot: each file, and all of them together, in bytes.

type MailMessage

type MailMessage struct {
	ID          int64            `json:"id"`
	Subject     string           `json:"subject"`
	From        string           `json:"from"`
	To          []string         `json:"to"`
	Cc          []string         `json:"cc"`
	Date        time.Time        `json:"date"`
	Text        string           `json:"text"`
	HTML        string           `json:"html"`
	Truncated   bool             `json:"truncated"`
	Attachments []AttachmentMeta `json:"attachments"`
}

MailMessage is a message as a plugin sees it. Text and HTML are cut to the host's limit, with Truncated set.

type MailSummary

type MailSummary struct {
	ID      int64     `json:"id"`
	Subject string    `json:"subject"`
	From    string    `json:"from"`
	Date    time.Time `json:"date"`
	Snippet string    `json:"snippet"`
}

type Manifest

type Manifest struct {
	Name         string        `json:"name"`
	Version      string        `json:"version"`
	Description  Text          `json:"description"` // one line, shown under the name in settings
	Main         string        `json:"main,omitempty"`
	Native       *NativeSpec   `json:"native,omitempty"`
	Capabilities []string      `json:"capabilities,omitempty"` // what it provides to Godwit's own features
	Permissions  Permissions   `json:"permissions"`
	Config       []ConfigField `json:"config"`
}

Manifest is a plugin's plugin.json (spec 5.3–5.4). A script plugin has Main and Permissions; a native one (docs/superpowers/specs/2026-10-14-native-plugins-design.md) has Native instead, and its shared library is LibraryName() beside plugin.json.

func ParseManifest

func ParseManifest(data []byte, dirName string) (Manifest, error)

ParseManifest parses and strictly validates plugin.json of the plugin installed in the directory dirName. A manifest that fails is not loaded.

func (Manifest) IsNative added in v1.3.0

func (m Manifest) IsNative() bool

IsNative reports whether m is a native (shared library) plugin.

func (Manifest) PermissionHash

func (m Manifest) PermissionHash() string

PermissionHash identifies what the user granted: it changes when the mail, net or llm permissions change, and not with their order, case, a ":443" or the config.

type NativeCall added in v1.3.0

type NativeCall func(method string, req []byte) ([]byte, error)

NativeCall calls a loaded library: it returns the plugin's response envelope as is; an error is a failure below the envelope.

type NativeError added in v1.3.0

type NativeError struct {
	Code      string `json:"code"`
	Message   string `json:"message"`
	Retryable bool   `json:"retryable,omitempty"`
}

NativeError is a native plugin's own error envelope: its answer, not a failure.

func (*NativeError) Error added in v1.3.0

func (e *NativeError) Error() string

type NativeOpener added in v1.3.0

type NativeOpener func(path string, hostABI uint32) (NativeCall, error)

NativeOpener loads the library at path, speaking hostABI.

func FakeNativeOpener added in v1.3.0

func FakeNativeOpener(stderr io.Writer) NativeOpener

FakeNativeOpener is a NativeOpener for tests that loads nothing: the "library" file is JSON {"describe": {...}, "initFail": bool, "echoStderr": "...", "record": "<path>", "describeSleepMs": n, "recordEnv": bool, "source": "en", "translateWrongCount": bool, "translateError": "<code>"}. initFail writes echoStderr and fails init; describe answers after describeSleepMs. translate answers "[<target>] <text>" per text from source (default "en"), one text short with translateWrongCount, or the error envelope translateError; with record each request is appended to record + ".translate", and recordEnv adds the environment to configure's record. Methods: describe, configure (writes its request to record, if set), translate (echoes the texts, source "en"), fail (a rate_limited envelope), probe.sleep {"ms"} and probe.crash (writes echoStderr, exits 2).

type NativeOptions added in v1.3.0

type NativeOptions struct {
	Command         []string
	Env             []string
	Library, SHA256 string
	Timeout         time.Duration
	Redact          func(string) string
	OnStart         func(ctx context.Context, call func(ctx context.Context, method string, params any) (json.RawMessage, error)) error
	Idle            time.Duration
}

NativeOptions start a plugin's process: Command then --library Library --sha256 SHA256, in the environment Env alone. Timeout 0 is 60 s; Redact nil leaves error text as it is. OnStart, if set, runs once per new process before any call reaches it (calls made meanwhile wait); its error fails the call that started the process, which is then ended. Idle, if set, ends a process no call has used for that long; the next call starts a new one.

type NativeProcess added in v1.3.0

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

func NewNativeProcess added in v1.3.0

func NewNativeProcess(o NativeOptions) *NativeProcess

NewNativeProcess returns the plugin's process handle; nothing starts until the first Call.

func (*NativeProcess) Call added in v1.3.0

func (n *NativeProcess) Call(ctx context.Context, method string, params any) (json.RawMessage, error)

Call runs method with params (marshalled to JSON) and returns the result. A plugin's error envelope is a *NativeError; see IsNativeFailure for the rest.

func (*NativeProcess) Close added in v1.3.0

func (n *NativeProcess) Close()

Close ends the process: stdin first, then after nativeGrace the process group. Calls after it return ErrClosed.

func (*NativeProcess) Start added in v1.3.0

func (n *NativeProcess) Start(ctx context.Context) error

Start makes sure the process runs and finished OnStart.

type NativeSpec added in v1.3.0

type NativeSpec struct {
	ABI uint32 `json:"abi"`
}

NativeSpec marks a native plugin: the C ABI version its library exports.

type NetError

type NetError struct{ Reason, Host string }

NetError is a fetch the policy refused. Reason is a stable code: host_not_allowed, scheme, ip_literal, userinfo, private_address, too_many_redirects, too_large or timeout.

func (*NetError) Error

func (e *NetError) Error() string

type NetOptions

type NetOptions struct {
	Resolver interface {
		LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, error)
	}
	Dial                     func(ctx context.Context, network, addr string) (net.Conn, error)
	AllowHTTP, AllowLoopback bool
	MaxBody                  int64         // response body; 0 = MaxFetchResponse
	Timeout                  time.Duration // whole call, redirects and body included; 0 = FetchTimeout
	UserAgent                string        // "Godwit-Plugin/<version>"
	// contains filtered or unexported fields
}

NetOptions configures a Client. Zero values are the real network and the spec's limits; AllowHTTP and AllowLoopback exist for tests only.

type Option

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

type PermissionError

type PermissionError struct{ Perm string }

PermissionError is a ctx API call the manifest or the invocation does not allow. Its message names only the permission.

func (*PermissionError) Error

func (e *PermissionError) Error() string

func (*PermissionError) Is

func (e *PermissionError) Is(target error) bool

type Permissions

type Permissions struct {
	Mail []string `json:"mail"`
	Net  []string `json:"net"`
	LLM  bool     `json:"llm"`
}

Permissions: Mail holds "read" and/or "write" (there is no send); Net the hosts, each "host" or "host:port", lowercased and without ":443" after ParseManifest.

type Plugin

type Plugin struct {
	Name string
	// contains filtered or unexported fields
}

Plugin is a loaded plugin: its VM and what it registered during initialization. Registrations never change after Load.

func Load

func Load(ctx context.Context, vm *VM, m Manifest, h Host, cfg *ConfigSnapshot) (*Plugin, error)

Load builds the ctx object and runs the plugin's initialization in an init invocation (no writes, no egress, cfg as ctx.config), recording what it registers.

func (*Plugin) Actions

func (p *Plugin) Actions() []ActionInfo

func (*Plugin) Card

func (p *Plugin) Card(ctx context.Context, inv Invocation, m MailMessage) (res any, matched bool, err error)

Card runs the card's match(m) and, if its result is truthy, render(m), both under inv, and returns render's result; matched is false when match was not truthy. ctx bounds both calls together.

func (*Plugin) Emit

func (p *Plugin) Emit(ctx context.Context, inv Invocation, event string, args ...any) error

Emit runs the plugin's handler for event with args (plain JSON-like Go data) as one top-level call under inv, awaiting it if it is async.

func (*Plugin) EmitResult

func (p *Plugin) EmitResult(ctx context.Context, inv Invocation, event string, args ...any) (any, error)

EmitResult is Emit that returns the handler's (awaited) result as plain JSON data.

func (*Plugin) Events

func (p *Plugin) Events() []string

Events lists the events it handles, sorted.

func (*Plugin) Handles

func (p *Plugin) Handles(event string) bool

Handles reports whether the plugin registered a handler for event.

func (*Plugin) HasCard

func (p *Plugin) HasCard() bool

func (*Plugin) RunAction

func (p *Plugin) RunAction(ctx context.Context, inv Invocation, id string, target any) (any, error)

RunAction runs the registered action id on target under inv and returns its (awaited) result as plain JSON data.

func (*Plugin) RunTool

func (p *Plugin) RunTool(ctx context.Context, inv Invocation, name string, args any) (any, error)

RunTool runs the registered tool name with args under inv and returns its (awaited) result as plain JSON data.

func (*Plugin) Tools

func (p *Plugin) Tools() []ToolInfo

func (*Plugin) Translate added in v1.3.0

func (p *Plugin) Translate(ctx context.Context, inv Invocation, req any) (any, error)

Translate runs the registered translator on req under inv and returns its (awaited) result as plain JSON data.

func (*Plugin) VM

func (p *Plugin) VM() *VM

type Question

type Question = ai.Question

Question and Answer are typed judge questions and answers, as llm.decide and Wit's classify_mail take and give them: the OpenAI Decisions API's shape (predicate, choice, score; a refusal answer), whatever the judge.

type Registry added in v1.3.0

type Registry struct {
	Schema  int              `json:"schema"`
	Updated time.Time        `json:"updated"`
	Plugins []RegistryPlugin `json:"plugins"`
}

func CheckRegistry added in v1.3.0

func CheckRegistry(data []byte) (Registry, error)

CheckRegistry is ParseRegistry for publishing: an unknown field anywhere or a release this build cannot read is an error.

func ParseRegistry added in v1.3.0

func ParseRegistry(data []byte) (Registry, error)

ParseRegistry parses and validates a registry.json as this build reads it. The format grows without a new schema: fields this build does not know are ignored outside releases, and a release it cannot read whole (new fields, a new capability, or invalid) goes to Unsupported instead of failing the registry, so older clients keep the rest of it, revocations included.

type RegistryPlugin added in v1.3.0

type RegistryPlugin struct {
	Name     string            `json:"name"`
	Title    Text              `json:"title"`
	Summary  Text              `json:"summary"`
	Homepage string            `json:"homepage,omitempty"`
	Releases []RegistryRelease `json:"releases"`
	// Unsupported are the releases this build cannot read, with only their
	// Version and MinGodwit: Pick's reason when none of Releases fits.
	Unsupported []RegistryRelease `json:"-"`
}

func (RegistryPlugin) Pick added in v1.3.0

func (p RegistryPlugin) Pick(platform, appVersion string) (RegistryRelease, Artifact, string)

Pick returns the newest release this build can install on platform, and its file; reason, when there is none, is why the newest release does not fit: "abi", "platform", "godwit:<minGodwit>", or "unsupported" for one this build cannot read. appVersion "" or "dev" (a development build) takes any minGodwit.

type RegistryRelease added in v1.3.0

type RegistryRelease struct {
	Version   string              `json:"version"`
	MinGodwit string              `json:"minGodwit,omitempty"`
	Manifest  json.RawMessage     `json:"manifest"`
	Artifacts map[string]Artifact `json:"artifacts"`
	Parsed    Manifest            `json:"-"` // Manifest, validated
}

type Text

type Text struct {
	Default string
	ByLang  map[string]string
}

Text is a label: a plain string, or {"default": ..., "<lang>": ...}.

func (Text) For

func (t Text) For(lang string) string

For returns the text for lang, then for its base language ("de" for "de-AT"), then a matching script/region, then the default.

func (Text) MarshalJSON

func (t Text) MarshalJSON() ([]byte, error)

func (*Text) UnmarshalJSON

func (t *Text) UnmarshalJSON(b []byte) error

type ToolInfo

type ToolInfo struct {
	Name, Description string
	Schema            map[string]any
	Write             bool
}

ToolInfo is a registered tool; Write defaults to true.

type VM

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

VM is one plugin's goja runtime. Every touch of the runtime happens on its owner goroutine: top-level calls are queued and run one at a time there, and async host work posts its completion back to it. Nothing returned by Call is a goja.Value.

func NewVM

func NewVM(name string, program *goja.Program, opts VMOptions) *VM

NewVM creates the runtime and its owner goroutine; it runs no plugin code.

func (*VM) Call

func (v *VM) Call(ctx context.Context, inv Invocation, fn func(c *CallScope) (any, error)) (any, error)

Call runs fn on the owner goroutine as one top-level call, after any call already running or queued. If fn returns a goja.Value it is awaited when it is a promise (pumping only this call's async completions) and exported as plain JSON data (map[string]any, []any, float64, string, bool, nil; at most MaxResultBytes, else ErrTooLarge). Go results are normalized the same way through encoding/json; one holding a goja.Value, func or channel is an error. The call ends at settlement, ctx's end, Close (ErrClosed), the kind's time limit (ErrTimeout) or the watchdog (ErrMemoryLimit); JS still running is interrupted.

func (*VM) Close

func (v *VM) Close()

Close refuses new calls (ErrClosed), cancels the running one and waits for it, drops pending completions and stops the owner goroutine.

func (*VM) HostFunc

func (v *VM) HostFunc(name string, fn func(c *CallScope, call goja.FunctionCall) goja.Value) goja.Value

HostFunc wraps a host function for JS. Call it on the owner goroutine (from inside a Call or Init). fn gets the scope of the call JS invoked it in. To throw, fn panics; JS always gets a plain Error (no Go value behind it, so e.value is undefined) whose message went through Redact and path scrubbing:

  • an error: "<name>: " + its message
  • a goja error value (rt.NewTypeError(...), ...): its message, keeping TypeError as TypeError
  • anything else, Go runtime errors included: "<name>: internal error"

Interrupts and stack overflows pass through untouched.

func (*VM) Init

func (v *VM) Init(ctx context.Context, inv Invocation, jsCtx func(c *CallScope) goja.Value) error

Init runs the bundle (the IIFE that defines `plugin`) and then plugin.default(jsCtx(c)) as one call, awaiting it if it is async. A nil jsCtx passes undefined.

type VMOptions

type VMOptions struct {
	Clock    Clock
	Watchdog *Watchdog
	Redact   func(string) string
}

VMOptions: a nil Clock is the real clock, a nil Watchdog the default one, a nil Redact the identity.

type Watchdog

type Watchdog struct {
	Meter     func() uint64
	Clock     Clock
	Interval  time.Duration
	Threshold uint64
}

Watchdog is the soft heap limit: goja has no per-runtime memory cap, so while a call runs it samples a process-wide meter and interrupts that call with ErrMemoryLimit when the meter has grown by more than Threshold since the call began. ponytail: process-wide, so it cannot tell the plugin's allocations from the app's; a child process with GOMEMLIMIT is the hard cap if one is ever needed.

Zero fields take defaults: Meter reads runtime.MemStats.HeapAlloc, Clock is the VM's, Interval 10 ms, Threshold 256 MB.

func (*Watchdog) Watch

func (w *Watchdog) Watch(v *VM, callID uint64) (release func())

Watch samples until release and can only ever abort callID: a tick after release, or while another call runs, does nothing.

type When

type When struct {
	Key       string          `json:"key"`
	Equals    json.RawMessage `json:"equals,omitempty"`
	NotEquals json.RawMessage `json:"notEquals,omitempty"`
}

When shows a field only if the earlier field Key equals (or does not equal) a value of that field's type.

func (*When) Match

func (w *When) Match(v json.RawMessage) bool

Match reports whether the condition holds for v, the referenced field's JSON value (nil when unset). Values compare by type and value: "true" never equals true, 3.0 equals 3.

Jump to

Keyboard shortcuts

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