Documentation
¶
Overview ¶
Package extensions is the host side of the extension system: it adapts Pando's internal configuration and services to the public contract declared in pkg/extension, and builds the process-wide extension manager.
The split matters: pkg/extension may not import internal packages, because out-of-tree modules (the private enterprise module) import it. Everything that needs to touch internal/ lives here instead.
Index ¶
- Constants
- func ApplyTools(mgr *extension.Manager, coreTools []tools.BaseTool) []tools.BaseTool
- func Commands() []*cobra.Command
- func Forward[T any](ctx context.Context, mgr *extension.Manager, topic string, ...)
- func ForwardConfigEvents(ctx context.Context, mgr *extension.Manager)
- func Frontend(mgr *extension.Manager, core fs.FS) fs.FS
- func HasEventSubscribers(mgr *extension.Manager) bool
- func HasFrontendExtensions(mgr *extension.Manager) bool
- func HasMemoryExtensions(mgr *extension.Manager) bool
- func HasMemorySinks(mgr *extension.Manager) bool
- func IdentityResolver(mgr *extension.Manager) func(ctx context.Context) (Identity, bool)
- func InvalidateUIPolicy()
- func Load(ctx context.Context, opts Options) *extension.Manager
- func NewManager(opts Options) *extension.Manager
- func ProviderRequestDecorator(mgr *extension.Manager) provider.RequestDecorator
- func RegisterConfigOverlays(ctx context.Context, mgr *extension.Manager) error
- func RegisterRoutes(mgr *extension.Manager, mux *http.ServeMux) []string
- func RegisterUIPolicy(mgr *extension.Manager)
- func SearchMiddleware(mgr *extension.Manager) kb.SearchMiddleware
- func SetPromptRunner(fn PromptRunFunc)
- func SetUIPolicyResolver(fn func(ctx context.Context) UIPolicy)
- func StartEventPublisher(ctx context.Context, mgr *extension.Manager)
- func UIPolicyResolver(mgr *extension.Manager) func(ctx context.Context) UIPolicy
- func WrapHTTP(mgr *extension.Manager, next http.Handler) http.Handler
- type Attribution
- type Identity
- type MemoryPublisher
- type MemorySinkStatus
- type MemoryStatus
- type Options
- type Panel
- type PromptRunFunc
- type PublisherStats
- type UIPolicy
- type UIPolicyBanner
Constants ¶
const AssetPrefix = "ext"
AssetPrefix is where extension frontend assets live in the served asset tree. They are folded into the *static* layer rather than mounted on the API mux for one concrete reason: a browser cannot attach an Authorization header to a dynamic import(), so a panel bundle behind the API token check could never be loaded. Extension assets are therefore public, exactly like core's own JavaScript — see the warning on extension.FrontendProvider.
const ExtRoutePrefix = "/api/ext/"
ExtRoutePrefix is the single prefix every extension HTTP route lives under. Nothing an extension serves is reachable outside it, which is what makes the API surface auditable and lets a reverse proxy treat extensions as one block.
Variables ¶
This section is empty.
Functions ¶
func ApplyTools ¶
ApplyTools returns the tool set the model should see: the core tools passed in, plus every tool contributed by a loaded ToolProvider, with every ToolFilter applied and every ToolInterceptor wrapped around each tool.
A nil manager (no extensions in the build, or a code path that never loaded them) returns coreTools untouched, so callers do not need to guard.
func Commands ¶
Commands builds the cobra tree for every command contributed by an extension compiled into this binary. It is safe to call from init(): nothing is provisioned and no configuration is required.
Whether an extension is actually enabled is decided when a command runs, not here — see commandRunner.
Name collisions are rejected rather than resolved: two extensions claiming `sync` is a build-time mistake, and silently dropping one would make the binary's behaviour depend on registration order.
func Forward ¶
func Forward[T any](ctx context.Context, mgr *extension.Manager, topic string, src pubsub.Suscriber[T])
Forward subscribes to src and delivers its events to every EventSubscriber that asked for topic, until ctx is cancelled. It returns immediately; the fan-out runs in its own goroutine.
Events are dropped, never queued, when a subscriber is slow: the alternative is unbounded memory growth in the host because an extension misbehaves. The contract says so, and an extension that must not lose events buffers them.
func ForwardConfigEvents ¶ added in v0.703.4
ForwardConfigEvents delivers configuration changes to every EventSubscriber that asked for extension.TopicConfig, until ctx is cancelled. It returns immediately; the fan-out runs in its own goroutine, and nothing at all is started when no extension subscribes.
func Frontend ¶
Frontend returns the asset tree to serve: core's own assets, with any extension replacement, overlay and asset subtree applied.
It is the single place the three frontend mechanisms meet, so their precedence is decided once and is readable in one function:
overlay files > base (replacement, else core) > extension subtrees
A nil manager returns core unchanged, so callers need no guard.
func HasEventSubscribers ¶
HasEventSubscribers reports whether anything would consume forwarded events. Callers use it to avoid starting fan-out goroutines in a standard build.
func HasFrontendExtensions ¶
HasFrontendExtensions reports whether anything would change the asset tree, so callers can skip the composition entirely on a standard build.
func HasMemoryExtensions ¶
HasMemoryExtensions reports whether anything in mgr uses the memory capability, as either a sink or a search wrapper.
func HasMemorySinks ¶
HasMemorySinks reports whether any loaded extension observes memory writes.
func IdentityResolver ¶ added in v0.703.4
IdentityResolver returns a function that reports the current identity, or false when nothing knows one. The returned function is safe for concurrent use and always non-nil, so callers need no nil check; with no provider loaded it simply always returns false and the host behaves exactly as an unextended Pando.
func InvalidateUIPolicy ¶ added in v0.703.4
func InvalidateUIPolicy()
InvalidateUIPolicy drops the memoised policy so the next read asks the providers again. Call it when something is known to have changed the policy and the surface cannot wait for the memo to expire.
func Load ¶
Load builds the manager and loads every enabled extension.
A failure inside one extension is logged and recorded in its status, never returned as a startup error: an optional feature must not stop Pando from running. The manager is always usable, even when empty.
func NewManager ¶
NewManager builds the extension manager from the current configuration without loading anything yet.
func ProviderRequestDecorator ¶ added in v0.703.4
func ProviderRequestDecorator(mgr *extension.Manager) provider.RequestDecorator
ProviderRequestDecorator returns the per-request header hook for the loaded extensions, or nil when nothing implements the capability. Passing nil to provider.SetRequestDecorator is the same as never having called it, so the caller can wire the result unconditionally.
func RegisterConfigOverlays ¶ added in v0.703.4
RegisterConfigOverlays registers every loaded extension that provides a configuration overlay and applies the result.
Overlays are collected in extension load order, so an extension that depends on another (RequiresExtensions) overrides it, which is the same precedence the rest of the extension system uses.
Applying reloads the configuration. Subsystems that read config.Get() see the overlaid values immediately; subsystems that copied a value out at construction time learn about the change from the config event bus, where an overlay publishes an "overlay_applied" event naming the keys it changed.
func RegisterRoutes ¶
RegisterRoutes mounts the routes of every loaded HTTPEndpointProvider on mux. It returns the full patterns registered, for logging and tests.
A nil manager registers nothing, so callers need no guard.
func RegisterUIPolicy ¶ added in v0.703.4
RegisterUIPolicy installs the merged policy of mgr as the process-wide one and points the configuration write path at it, so a hidden or read-only path is refused as well as not offered.
It is safe to call with a manager that has no provider: that installs the empty policy, which is what an unextended Pando has.
func SearchMiddleware ¶
func SearchMiddleware(mgr *extension.Manager) kb.SearchMiddleware
SearchMiddleware builds the kb.SearchMiddleware for the wrappers in mgr, or nil when there are none. Chaining runs in registration order, so the first registered wrapper ends up outermost.
func SetPromptRunner ¶ added in v0.703.4
func SetPromptRunner(fn PromptRunFunc)
SetPromptRunner registers the host's prompt runner. Passing nil unregisters it, which is what tests and teardown do.
func SetUIPolicyResolver ¶ added in v0.703.4
SetUIPolicyResolver replaces the process-wide resolver. Passing nil removes it, restoring the unextended behaviour; that is what a host tearing a manager down does, and what a test does when it is finished.
func StartEventPublisher ¶ added in v0.703.4
StartEventPublisher routes events published through internal/extevents to the loaded subscribers until ctx is cancelled. Nothing is installed when no extension subscribes, which leaves every publish site in core at one atomic load.
func UIPolicyResolver ¶ added in v0.703.4
UIPolicyResolver returns a function reporting the merged policy of every loaded provider. The returned function is safe for concurrent use and always non-nil, so callers need no nil check; with no provider loaded it always returns the zero policy and every surface behaves exactly as an unextended Pando.
Types ¶
type Attribution ¶
Attribution identifies the instance a memory event came from. It is attribution only: isolation belongs to whatever store the sink talks to.
type Identity ¶ added in v0.703.4
Identity is the host-side copy of extension.Identity. The duplication is the same one internal/config makes for overlays: core packages consume this type without importing the extension contract, and the contract stays free of internal types.
type MemoryPublisher ¶
type MemoryPublisher struct {
// contains filtered or unexported fields
}
MemoryPublisher fans committed remembrance writes out to the MemorySinks in a manager. The zero value is not usable; build one with NewMemoryPublisher.
func NewMemoryPublisher ¶
func NewMemoryPublisher(mgr *extension.Manager, cfg config.ExtensionsMemoryConfig, attr func() Attribution) *MemoryPublisher
NewMemoryPublisher builds a publisher for the sinks in mgr. It returns nil when the capability is switched off, when nothing implements MemorySink, or when the configuration enables the capability without naming a single scope — an empty scope list shares nothing, and saying so out loud beats silently publishing everything or silently publishing nothing. attr is called per event rather than captured once, because the instance ID is only assigned after the IPC lock is taken, which happens later in startup than this wiring does.
func (*MemoryPublisher) Close ¶
func (p *MemoryPublisher) Close()
Close stops the async worker and waits for the queue to drain. Safe to call on a nil publisher and safe to call twice.
func (*MemoryPublisher) Observer ¶
func (p *MemoryPublisher) Observer() kb.WriteObserver
Observer returns the kb.WriteObserver to install on the store.
func (*MemoryPublisher) Stats ¶
func (p *MemoryPublisher) Stats() PublisherStats
Stats returns the host counters. Safe on a nil publisher.
type MemorySinkStatus ¶
type MemorySinkStatus struct {
ID string `json:"id"`
Name string `json:"name"`
Active bool `json:"active"`
DryRun bool `json:"dryRun"`
Destination string `json:"destination,omitempty"`
Scopes []string `json:"scopes,omitempty"`
Pending int `json:"pending"`
Sent int64 `json:"sent"`
Dropped int64 `json:"dropped"`
LastSyncAt string `json:"lastSyncAt,omitempty"`
LastError string `json:"lastError,omitempty"`
// Reports is false when the sink does not implement MemorySyncReporter, so
// the UI can say "shipping, state unknown" instead of "idle" — which would
// be a lie in exactly the case that matters.
Reports bool `json:"reports"`
}
MemorySinkStatus is one sink's reported state.
type MemoryStatus ¶
type MemoryStatus struct {
// Enabled reflects the configuration gate, not whether a sink exists.
Enabled bool `json:"enabled"`
// Active is true when the gate is open and something is actually wired to
// receive events.
Active bool `json:"active"`
DryRun bool `json:"dryRun"`
Mode string `json:"mode"`
Scopes []string `json:"scopes,omitempty"`
Paths []string `json:"paths,omitempty"`
Origins []string `json:"origins,omitempty"`
WrapSearch bool `json:"wrapSearch"`
Wrappers []string `json:"wrappers,omitempty"`
Host PublisherStats `json:"host"`
Sinks []MemorySinkStatus `json:"sinks"`
}
MemoryStatus is what /api/v1/extensions/memory returns.
func MemoryStatusOf ¶
func MemoryStatusOf(mgr *extension.Manager, cfg config.ExtensionsMemoryConfig, pub *MemoryPublisher) MemoryStatus
MemoryStatusOf assembles the status. Every field tolerates a nil manager or publisher: a standard build answers "off" rather than 404, so the UI has one code path instead of two.
type Options ¶
type Options struct {
// Config is the configuration to read from. Defaults to config.Get().
Config *config.Config
// Logger receives extension lifecycle messages. Defaults to the Pando
// structured logger.
Logger *slog.Logger
}
Options overrides how the manager is built. The zero value is what production code uses.
type Panel ¶
type Panel struct {
ID string `json:"id"`
Extension string `json:"extension"`
Title string `json:"title"`
Slot string `json:"slot"`
Entry string `json:"entry"`
Icon string `json:"icon,omitempty"`
Order int `json:"order"`
}
Panel is one entry of the merged UI manifest served to the shell. It is the wire shape: the extension declares a relative Entry, core resolves it to a URL the browser can import.
type PromptRunFunc ¶ added in v0.703.4
type PromptRunFunc func(ctx context.Context, req extension.PromptRequest) (extension.PromptResult, error)
PromptRunFunc performs one non-interactive turn. internal/app implements it.
type PublisherStats ¶
type PublisherStats struct {
Sinks int `json:"sinks"`
Published int64 `json:"published"`
Filtered int64 `json:"filtered"`
Dropped int64 `json:"dropped"`
Failed int64 `json:"failed"`
Queued int `json:"queued"`
}
PublisherStats are the host's own counters, independent of what each sink reports about itself.
type UIPolicy ¶ added in v0.703.4
type UIPolicy struct {
// HiddenSections lists the configuration paths no surface should render.
HiddenSections []string
// ReadOnlySections lists the paths rendered with their value but not
// editable.
ReadOnlySections []string
// ReadOnlyLabel is the caller-supplied text shown on a read-only field.
// Empty means the surface uses its own generic marker.
ReadOnlyLabel string
// Banner is the notice shown above the settings.
Banner UIPolicyBanner
}
UIPolicy is the host-side copy of extension.UIPolicy, the same duplication the overlay and identity capabilities make: core packages consume this type without importing the extension contract.
func CurrentUIPolicy ¶ added in v0.703.4
CurrentUIPolicy returns the process-wide merged policy, memoised for uiPolicyTTL. With no resolver installed it returns the zero policy.
The providers are asked outside the lock, and a call that arrives while another is resolving gets the previous value rather than starting a second resolution. That is what makes the call re-entrant: a provider that derives its policy from the configuration reaches the lock check, which reads the restricted paths, which lands back here, and must find an answer instead of its own call.
func (UIPolicy) Empty ¶ added in v0.703.4
Empty reports whether the policy asks for nothing, which is the state every standalone Pando is in.
func (UIPolicy) IsHidden ¶ added in v0.703.4
IsHidden reports whether path is covered by a hidden section. Coverage is the same both-directions, case-insensitive segment match the lock list uses, so a hidden "providerAccounts" covers "providerAccounts.anthropic.apiKey".
func (UIPolicy) IsReadOnly ¶ added in v0.703.4
IsReadOnly reports whether path is covered by a read-only section. A hidden path is not reported as read-only: it is not rendered at all, and the stronger statement wins.
func (UIPolicy) RestrictedPaths ¶ added in v0.703.4
RestrictedPaths returns every path the policy withdraws from local control, hidden and read-only together, sorted and deduplicated. It is what the configuration write path refuses.
type UIPolicyBanner ¶ added in v0.703.4
UIPolicyBanner is the host-side copy of extension.UIPolicyBanner.