plugin_sdk

package
v2.2.1 Latest Latest
Warning

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

Go to latest
Published: Oct 2, 2026 License: AGPL-3.0 Imports: 24 Imported by: 0

README

Unified Plugin SDK for Midsommar

The Unified Plugin SDK provides a single, clean API for building plugins that work in both AI Studio and Microgateway contexts. Write your plugin once, and deploy it everywhere.

Quick Start

package main

import (
    "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk"
    pb "github.com/TykTechnologies/midsommar/v2/proto"
)

type MyPlugin struct {
    plugin_sdk.BasePlugin
}

func NewMyPlugin() *MyPlugin {
    return &MyPlugin{
        BasePlugin: plugin_sdk.NewBasePlugin(
            "my-plugin",
            "1.0.0",
            "My awesome plugin",
        ),
    }
}

// Implement the PostAuthHandler capability
func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) {
    // Your plugin logic here
    ctx.Services.Logger().Info("Request received", "app_id", ctx.AppID)

    // Allow the request
    return &pb.PluginResponse{Modified: false}, nil
}

func main() {
    plugin_sdk.Serve(NewMyPlugin())
}

Key Concepts

Runtime Detection

The SDK automatically detects whether it's running in AI Studio or Microgateway:

  • AI Studio: Management UI, policy configuration, service orchestration
  • Microgateway: Edge proxy, request processing, policy enforcement

Your plugin can adapt its behavior based on the runtime:

func (p *MyPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error {
    if ctx.Runtime == plugin_sdk.RuntimeStudio {
        // Setup for Studio (UI serving, etc.)
    } else {
        // Setup for Gateway (rate limiting, etc.)
    }
    return nil
}
Capability-Based Design

Plugins only implement the capabilities they need. Available capabilities:

Capability Purpose Example Use Cases
PreAuthHandler Process requests before authentication Early validation, request rewriting
AuthHandler Perform custom authentication Custom auth schemes, credential validation
PostAuthHandler Process requests after authentication Rate limiting, content filtering, enrichment
ResponseHandler Modify responses before sending to client Header manipulation, response transformation
DataCollector Collect telemetry data Export to analytics systems, monitoring
UIProvider Serve web UI assets for AI Studio Management interface, dashboards
ConfigProvider Provide configuration schema Admin UI form generation
AgentPlugin Implement conversational AI agent Custom agent behavior
Context and Services

The Context object provides:

  • Runtime information: Which environment (Studio/Gateway), request details
  • Services: KV storage, logging, app management
func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) {
    // Access services
    data, err := ctx.Services.KV().Read(ctx, "my-key")
    ctx.Services.Logger().Info("Processing request", "app_id", ctx.AppID)

    // Get app details
    app, err := ctx.Services.AppManager().GetApp(ctx, ctx.AppID)

    // Your logic here
    return &pb.PluginResponse{Modified: false}, nil
}

Available Services

KV Storage

Persistent key-value storage for plugin data:

// Write data
ctx.Services.KV().Write(ctx, "rate:app:123", []byte(`{"count": 5}`))

// Read data
data, err := ctx.Services.KV().Read(ctx, "rate:app:123")

// Delete data
deleted, err := ctx.Services.KV().Delete(ctx, "rate:app:123")

Important: In Studio, KV storage is PostgreSQL-backed and shared across all hosts. In Gateway, it uses local database storage per instance.

Logging

Structured logging with standard levels:

ctx.Services.Logger().Debug("Detailed debugging info", "key", value)
ctx.Services.Logger().Info("Information message", "key", value)
ctx.Services.Logger().Warn("Warning message", "key", value)
ctx.Services.Logger().Error("Error message", "key", value)
App Management

Access to application configuration and metadata:

// Get app details
appResp, err := ctx.Services.AppManager().GetApp(ctx, appID)
app := appResp.App

// List all apps
appsResp, err := ctx.Services.AppManager().ListApps(ctx, page, limit)

// Update app (including metadata)
updateReq := &mgmt.UpdateAppRequest{
    AppId:    appID,
    Name:     app.Name,
    Metadata: newMetadataJSON,
    // ... other fields
}
appResp, err := ctx.Services.AppManager().UpdateApp(ctx, updateReq)

Building a Complete Plugin

Here's a complete example of a rate limiter plugin that works in both contexts:

package main

import (
    "encoding/json"
    "fmt"
    "sync"

    "github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk"
    pb "github.com/TykTechnologies/midsommar/v2/proto"
)

type RateLimiterPlugin struct {
    plugin_sdk.BasePlugin
    rateLocks map[string]*sync.Mutex
}

func NewRateLimiterPlugin() *RateLimiterPlugin {
    return &RateLimiterPlugin{
        BasePlugin: plugin_sdk.NewBasePlugin(
            "rate-limiter",
            "1.0.0",
            "Token-based rate limiting",
        ),
        rateLocks: make(map[string]*sync.Mutex),
    }
}

// PostAuthHandler capability - enforces rate limits
func (p *RateLimiterPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) {
    appID := req.Request.Context.AppId

    // Get rate limit from KV
    key := fmt.Sprintf("rate:%d", appID)
    data, err := ctx.Services.KV().Read(ctx, key)
    if err != nil {
        // No rate limit configured
        return &pb.PluginResponse{Modified: false}, nil
    }

    var rateData struct {
        Limit int `json:"limit"`
        Count int `json:"count"`
    }
    json.Unmarshal(data, &rateData)

    // Check limit
    if rateData.Count >= rateData.Limit {
        return &pb.PluginResponse{
            Block:      true,
            StatusCode: 429,
            Body:       []byte(`{"error":"Rate limit exceeded"}`),
        }, nil
    }

    // Increment counter
    rateData.Count++
    updatedData, _ := json.Marshal(rateData)
    ctx.Services.KV().Write(ctx, key, updatedData)

    return &pb.PluginResponse{Modified: false}, nil
}

// UIProvider capability - serves management UI
func (p *RateLimiterPlugin) GetAsset(path string) ([]byte, string, error) {
    // Serve embedded UI assets
    return embeddedAssets.ReadFile(path)
}

func (p *RateLimiterPlugin) GetManifest() ([]byte, error) {
    return manifestFile, nil
}

func (p *RateLimiterPlugin) HandleRPC(method string, payload []byte) ([]byte, error) {
    // Handle RPC calls from UI
    switch method {
    case "setLimit":
        var req struct {
            AppID uint32 `json:"app_id"`
            Limit int    `json:"limit"`
        }
        json.Unmarshal(payload, &req)

        // Store limit in KV
        data, _ := json.Marshal(map[string]int{
            "limit": req.Limit,
            "count": 0,
        })
        ai_studio_sdk.WritePluginKV(context.Background(),
            fmt.Sprintf("rate:%d", req.AppID), data)

        return []byte(`{"success":true}`), nil
    }
    return nil, fmt.Errorf("unknown method: %s", method)
}

func main() {
    plugin_sdk.Serve(NewRateLimiterPlugin())
}

Plugin Manifest

For UI plugins, create a manifest.json:

{
  "name": "my-plugin",
  "version": "1.0.0",
  "description": "My awesome plugin",
  "ui": {
    "entry_point": "ui/webc/main.js",
    "components": [
      {
        "type": "page",
        "id": "settings",
        "label": "Settings",
        "icon": "settings"
      }
    ]
  },
  "permissions": {
    "scopes": [
      "apps.read",
      "apps.write",
      "kv.readwrite"
    ]
  }
}
Permissions (RBAC)

Every Studio plugin with pages or resource types gets a row in the role editor (plugin:<manifest id> with read/write/execute; plugins:execute holders and administrators have it all). Declare finer rows and per-method requirements with an rbac block, and check the caller in HandleRPCWithUser:

"rbac": {
  "resources": [{"key": "assets", "label": "Assets", "actions": ["read", "write", "delete", "publish"]}],
  "rpc_methods": {"admin_list_assets": "assets:read", "admin_release": "assets:publish", "admin_stats": "read"}
}
func (p *MyPlugin) HandleRPCWithUser(method string, payload []byte, user *plugin_sdk.PortalUserContext) ([]byte, error) {
    if !user.Can("assets:publish") { /* forbidden */ }
    ...
}

Runtime-only resources are registered with ctx.Services.Studio().RegisterPermissionResources(...) (scope rbac.register). See docs/site/docs/plugins-manifests.md ("Permissions (RBAC) Block") and plugins-service-api.md ("Permission Resources").

Configuration Schema

Provide a JSON Schema for configuration:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "default_limit": {
      "type": "integer",
      "title": "Default Rate Limit",
      "description": "Default requests per minute",
      "default": 100
    }
  }
}

Building and Deploying

Building
cd your-plugin
go build -o plugin-name .
Deploying to AI Studio
  1. Upload plugin binary via Admin UI or API
  2. Configure plugin settings
  3. Assign to LLMs or apps
Deploying to Microgateway
  1. Place plugin binary in plugins directory
  2. Configure in microgateway.yaml:
plugins:
  - name: my-plugin
    command: ./plugins/my-plugin
    hook_type: post_auth
    config:
      setting1: value1
  1. Restart gateway

Migration from Old SDKs

From AI Studio SDK

Before (AI Studio SDK):

type MyPlugin struct {
    pb.UnimplementedPluginServiceServer
    serviceAPI mgmt.AIStudioManagementServiceClient
}

func (p *MyPlugin) OnInitialize(serviceAPI mgmt.AIStudioManagementServiceClient, pluginID uint32, config map[string]string) error {
    p.serviceAPI = serviceAPI
    return nil
}

func main() {
    plugin := &MyPlugin{}
    ai_studio_sdk.ServePlugin(plugin)
}

After (Unified SDK):

type MyPlugin struct {
    plugin_sdk.BasePlugin
}

func NewMyPlugin() *MyPlugin {
    return &MyPlugin{
        BasePlugin: plugin_sdk.NewBasePlugin("my-plugin", "1.0.0", "Description"),
    }
}

func (p *MyPlugin) Initialize(ctx plugin_sdk.Context, config map[string]string) error {
    // Services available via ctx.Services
    return nil
}

func main() {
    plugin_sdk.Serve(NewMyPlugin())
}
From Microgateway SDK

Before (Microgateway SDK):

type MyPlugin struct{}

func (p *MyPlugin) ProcessRequest(ctx context.Context, req *sdk.EnrichedRequest, pluginCtx *sdk.PluginContext) (*sdk.PluginResponse, error) {
    // Process request
}

func (p *MyPlugin) GetHookType() sdk.HookType {
    return sdk.HookTypePostAuth
}

func main() {
    sdk.ServePlugin(&MyPlugin{})
}

After (Unified SDK):

type MyPlugin struct {
    plugin_sdk.BasePlugin
}

func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) {
    // Same processing logic
}

func main() {
    plugin_sdk.Serve(NewMyPlugin())
}

Complete Example

See the llm-rate-limiter example for a complete, production-ready plugin that:

  • ✅ Works in both AI Studio and Microgateway
  • ✅ Provides UI for policy management
  • ✅ Enforces rate limits at the edge
  • ✅ Uses KV storage for policies and rate state
  • ✅ Includes configuration schema
  • ✅ Demonstrates all major capabilities

Troubleshooting

Plugin not loading
  1. Check plugin binary is executable: chmod +x plugin-name
  2. Verify handshake configuration matches (should be automatic with unified SDK)
  3. Check logs for initialization errors
Services not available

Ensure you're accessing services through the context:

func (p *MyPlugin) HandlePostAuth(ctx plugin_sdk.Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error) {
    // ✅ Correct
    ctx.Services.KV().Read(ctx, "key")

    // ❌ Wrong - services are on the context
    // p.kv.Read(ctx, "key")
}
Service broker not initialized

If you see "service broker ID not set" errors, the broker configuration is not being passed correctly. The SDK automatically extracts the broker ID from the config in the Initialize method, checking for both _service_broker_id (Microgateway) and service_broker_id (AI Studio). This should happen automatically - if it doesn't, check that your plugin manager is calling Initialize with the config map.

KV data not persisting

In Gateway context, KV data is local to each gateway instance. For shared data across gateways, store in App metadata via the AppManager service.

Best Practices

  1. Use Context Services: Always access KV, logging, and app management through ctx.Services
  2. Handle Both Runtimes: Check ctx.Runtime and adapt behavior accordingly
  3. Embed Assets: Use //go:embed for UI assets and manifests
  4. Graceful Degradation: If a service is unavailable, fail gracefully
  5. Structured Logging: Use the Logger service with key-value pairs
  6. Atomic Operations: Use mutexes for rate limiting and concurrent access
  7. Configuration Schema: Always provide a JSON schema for your config

API Reference

See the godoc for complete API reference:

godoc -http=:6060
# Visit http://localhost:6060/pkg/github.com/TykTechnologies/midsommar/v2/pkg/plugin_sdk/

Support

For issues, questions, or contributions, see the main Midsommar repository.

Documentation

Overview

Package plugin_sdk provides a unified SDK for building plugins that work in both AI Studio and Microgateway contexts. This SDK wraps the underlying proto definitions and provides a clean, simple interface for plugin developers.

Package plugin_sdk provides session management for long-lived broker connections. The session pattern keeps go-plugin broker connections alive for plugins that need background services like event pub/sub.

Index

Constants

View Source
const (
	MetadataVisibilityAdmin   = "admin"   // every field
	MetadataVisibilityPortal  = "portal"  // fields flagged portal_visible
	MetadataVisibilityGateway = "gateway" // fields flagged gateway_visible
)

Governed metadata audiences for StudioServices.GetObjectMetadataForAudience.

View Source
const (
	MetadataEnforcementAdvisory = "advisory"
	MetadataEnforcementEnforce  = "enforce"
)

Governed metadata enforcement levels reported by ResolvedMetadataSchema.

View Source
const (
	DefaultAccessAuto     = "auto"
	DefaultAccessExplicit = "explicit"
)

Default-team access modes for ResourceTypeRegistration.DefaultAccess.

View Source
const DefaultSessionTimeoutMs = 30000

Default session timeout in milliseconds

View Source
const MetadataPluginPermissionKey = "plugin_permission_key"

MetadataPluginPermissionKey is the PortalUserContext.Metadata entry that carries the calling plugin's own RBAC key ("plugin:<manifest id>") so a plugin can check plugin-relative permissions with Can.

View Source
const ResourceInstanceChangedEvent = "system.plugin_resource.instance_changed"

ResourceInstanceChangedEvent is the topic for instance change notifications.

Variables

This section is empty.

Functions

func CloseSession

func CloseSession(reason string)

CloseSession closes the active session. Called from the CloseSession RPC handler.

func DefaultHTTPClient

func DefaultHTTPClient() *http.Client

DefaultHTTPClient returns an HTTP client with sensible defaults for plugin use. Plugins should reuse this client across requests for connection pooling.

Defaults:

  • 30 second overall timeout
  • 10 second dial timeout
  • 10 second TLS handshake timeout
  • 100 max idle connections, 10 per host
  • 90 second idle connection timeout

Plugins can also create their own http.Client if different settings are needed.

func GetSessionBroker

func GetSessionBroker() *goplugin.GRPCBroker

GetSessionBroker returns the broker from the active session. Returns nil if no session is active.

func GetSessionBrokerID

func GetSessionBrokerID() uint32

GetSessionBrokerID returns the broker ID from the active session. Returns 0 if no session is active.

func GetSessionID

func GetSessionID() string

GetSessionID returns the current session ID. Returns empty string if no session is active.

func GetStoredBroker

func GetStoredBroker() *goplugin.GRPCBroker

GetStoredBroker returns the stored broker reference. Used by the session management to access the broker during OpenSession.

func InitSession

func InitSession(sessionID string, brokerID uint32, broker *goplugin.GRPCBroker) (firstSession bool, err error)

InitSession initializes the session with broker access. Called when OpenSession RPC is received from the host. Returns true if this is the first session (services should be started).

func IsSessionActive

func IsSessionActive() bool

IsSessionActive returns true if there's an active session.

func NotifyResourceInstanceChanged

func NotifyResourceInstanceChanged(ctx Context, resourceTypeSlug, instanceID string) error

NotifyResourceInstanceChanged publishes an event telling the platform that a resource instance's data (name, privacy score, metadata, active status) has changed. The platform subscribes to this event and refreshes cached instance details in all AppPluginResource rows that reference this instance.

Call this from your plugin whenever you update an instance's properties.

func ResetSession

func ResetSession()

ResetSession resets the session state for testing purposes. Should not be called in production code.

func SanitizeUTF8

func SanitizeUTF8(b []byte) string

SanitizeUTF8 ensures a byte slice produces a valid UTF-8 string by replacing invalid sequences with the Unicode replacement character (U+FFFD). This is necessary because protobuf3 string fields require valid UTF-8.

func SanitizeUTF8String

func SanitizeUTF8String(s string) string

SanitizeUTF8String ensures a string is valid UTF-8 by replacing invalid sequences with the Unicode replacement character (U+FFFD).

func SelfResourceObjectType

func SelfResourceObjectType(resourceTypeSlug string) string

SelfResourceObjectType returns the governed metadata object type for one of the calling plugin's own resource types without needing the numeric plugin ID: "plugin_resource:self:<slug>". Studio resolves it on every management API call.

func Serve

func Serve(userPlugin Plugin)

Serve starts the plugin and handles all the go-plugin setup. This is the main entry point for plugin developers.

Example usage:

func main() {
    plugin := &MyPlugin{}
    plugin_sdk.Serve(plugin)
}

func SetEventServiceBroker

func SetEventServiceBroker(broker *goplugin.GRPCBroker)

SetEventServiceBroker stores the broker for event service access. This is called during plugin initialization.

func SetEventServiceBrokerID

func SetEventServiceBrokerID(brokerID uint32)

SetEventServiceBrokerID stores the broker ID for dialing the event service. This is called when the plugin receives the broker ID from config.

func SyncResourceTypes

func SyncResourceTypes(ctx Context, regs []*ResourceTypeRegistration) error

SyncResourceTypes (re)registers the plugin's resource types with AI Studio at runtime and deactivates any of the plugin's previously registered types that are not in regs. Use it when the set of types is defined dynamically (for example by administrators inside the plugin) rather than in the manifest.

Requires the "resource-types.manage" service scope. Studio-only.

The platform calls back ListResourceInstances for every registered type while this call is in flight, so do not hold locks that ListResourceInstances needs across the call.

func WaitForClose

func WaitForClose(timeoutMs int32) string

WaitForClose blocks until the session is closed or times out. Returns the reason for closing ("timeout", "explicit_close", etc.) Called from the OpenSession RPC handler.

Types

type AgentMessageChunk

type AgentMessageChunk = pb.AgentMessageChunk

Re-export commonly used proto types for convenience This allows plugin developers to import only plugin_sdk without importing proto directly

type AgentMessageRequest

type AgentMessageRequest = pb.AgentMessageRequest

Agent types

type AgentPlugin

type AgentPlugin interface {
	Plugin

	// HandleAgentMessage processes a user message and streams responses back.
	// req contains: user message, available LLMs/tools/datasources, conversation history
	// stream is used to send back content chunks, tool calls, thinking, errors, etc.
	// The agent should call stream.Send() for each chunk and end with a DONE chunk.
	HandleAgentMessage(req *pb.AgentMessageRequest, stream grpc.ServerStreamingServer[pb.AgentMessageChunk]) error

	// GetManifest returns the plugin manifest as JSON bytes.
	// The manifest declares permissions, scopes, and metadata.
	GetManifest() ([]byte, error)
}

AgentPlugin handles conversational AI agent interactions. This is for plugins that implement custom agent behavior.

type AnalyticsRequest

type AnalyticsRequest = pb.AnalyticsRequest

Re-export commonly used proto types for convenience This allows plugin developers to import only plugin_sdk without importing proto directly

type App

type App = pb.App

Auth context types

type AuditRecord

type AuditRecord struct {
	ID           uint32
	Timestamp    time.Time
	UserID       uint32
	UserEmail    string
	UserName     string
	Action       string
	Method       string
	Route        string
	Status       int
	ResourceType string
	ResourceID   string
	ResourceName string
	DiffJSON     string // {"field": {"old":..,"new":..}}, sensitive columns redacted
	Error        string
}

AuditRecord is one platform audit trail record, without request or response bodies.

type AuthHandler

type AuthHandler interface {
	Plugin
	HandleAuth(ctx Context, req *pb.AuthRequest) (*pb.AuthResponse, error)

	// GetAppByCredential retrieves app information during authentication.
	// This is called by the auth system to validate app credentials.
	GetAppByCredential(ctx Context, credential string) (*pb.App, error)

	// GetUserByCredential retrieves user information during authentication.
	// This is called by the auth system to validate user credentials.
	GetUserByCredential(ctx Context, credential string) (*pb.User, error)
}

AuthHandler performs custom authentication. Use this when you need to implement a custom authentication scheme.

type AuthRequest

type AuthRequest = pb.AuthRequest

Auth types

type AuthResponse

type AuthResponse = pb.AuthResponse

Re-export commonly used proto types for convenience This allows plugin developers to import only plugin_sdk without importing proto directly

type BasePlugin

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

BasePlugin is a convenience struct that provides default implementations of the Plugin interface. Plugin developers can embed this to reduce boilerplate.

func NewBasePlugin

func NewBasePlugin(name, version, description string) BasePlugin

NewBasePlugin creates a new BasePlugin with the given metadata

func (*BasePlugin) GetInfo

func (b *BasePlugin) GetInfo() PluginInfo

GetInfo returns the plugin metadata

func (*BasePlugin) Initialize

func (b *BasePlugin) Initialize(ctx Context, config map[string]string) error

Initialize provides a default no-op implementation

func (*BasePlugin) Shutdown

func (b *BasePlugin) Shutdown(ctx Context) error

Shutdown provides a default no-op implementation

type BudgetUsageRequest

type BudgetUsageRequest = pb.BudgetUsageRequest

Re-export commonly used proto types for convenience This allows plugin developers to import only plugin_sdk without importing proto directly

type ConfigProvider

type ConfigProvider interface {
	Plugin

	// GetConfigSchema returns the JSON Schema for plugin configuration.
	// The schema should follow jsonschema.org format.
	GetConfigSchema() ([]byte, error)
}

ConfigProvider provides configuration schema for the plugin. This is used by the admin UI to generate configuration forms.

type Context

type Context struct {
	// Runtime indicates whether this is Studio or Gateway
	Runtime RuntimeType

	// RequestID uniquely identifies this request (if applicable)
	RequestID string

	// AppID is the application making the request (if known)
	AppID uint32

	// UserID is the user making the request (if known)
	UserID uint32

	// LLMID is the LLM being accessed (if applicable)
	LLMID uint32

	// LLMSlug is the LLM slug/identifier (if applicable)
	LLMSlug string

	// Vendor is the LLM vendor (e.g., "anthropic", "openai")
	Vendor string

	// EdgeID is the edge instance identifier (only set in RuntimeGateway)
	// Empty string in RuntimeStudio context
	EdgeID string

	// EdgeNamespace is the edge namespace (only set in RuntimeGateway)
	// Empty string in RuntimeStudio context
	EdgeNamespace string

	// Metadata provides additional context as key-value pairs
	Metadata map[string]string

	// TraceContext provides distributed tracing context
	TraceContext map[string]string

	// Services provides access to host services (KV storage, logging, app management)
	Services ServiceBroker

	// Internal context for cancellation and timeouts
	context.Context
}

Context provides runtime information and services to the plugin. This is passed to most plugin methods to give access to the host environment.

func NewContext

func NewContext(baseCtx context.Context, services ServiceBroker, requestID string, appID, userID, llmID uint32, llmSlug, vendor string, metadata, traceContext map[string]string) Context

NewContext creates a new plugin context with the given parameters

type CustomEndpointHandler

type CustomEndpointHandler interface {
	Plugin

	// GetEndpointRegistrations declares which HTTP endpoints this plugin serves.
	// Called after Initialize() and again on plugin reload.
	GetEndpointRegistrations() ([]*pb.EndpointRegistration, error)

	// HandleEndpointRequest handles a non-streaming HTTP request.
	// Returns a complete HTTP response (status, headers, body).
	HandleEndpointRequest(ctx Context, req *pb.EndpointRequest) (*pb.EndpointResponse, error)

	// HandleEndpointRequestStream handles a streaming HTTP request (SSE, MCP Streamable HTTP).
	// Plugin sends EndpointResponseChunks: first HEADERS, then BODY chunks, finally DONE.
	// The gateway flushes each BODY chunk to the HTTP client immediately.
	// Plugins that only serve non-streaming endpoints can return an Unimplemented error.
	HandleEndpointRequestStream(ctx Context, req *pb.EndpointRequest, stream grpc.ServerStreamingServer[pb.EndpointResponseChunk]) error
}

CustomEndpointHandler allows plugins to register and serve custom HTTP endpoints. Endpoints are mounted under /plugins/{slug}/ on the microgateway. Each endpoint registration must explicitly declare whether authentication is required.

Plugins can serve both unary responses (HandleEndpointRequest) and streaming responses (HandleEndpointRequestStream) — the latter enables SSE and MCP Streamable HTTP support. Set stream_response=true on an EndpointRegistration to indicate the gateway should use the streaming path.

Most plugins should register a single "/*" catch-all and handle routing internally using the pre-split path_segments field on EndpointRequest.

This capability is supported in Gateway runtime. AI Studio support may be added later.

type DataCollectionResponse

type DataCollectionResponse = pb.DataCollectionResponse

Re-export commonly used proto types for convenience This allows plugin developers to import only plugin_sdk without importing proto directly

type DataCollector

type DataCollector interface {
	Plugin

	// HandleProxyLog processes raw proxy request/response logs.
	HandleProxyLog(ctx Context, req *pb.ProxyLogRequest) (*pb.DataCollectionResponse, error)

	// HandleAnalytics processes token usage and cost analytics.
	HandleAnalytics(ctx Context, req *pb.AnalyticsRequest) (*pb.DataCollectionResponse, error)

	// HandleBudgetUsage processes budget consumption data.
	HandleBudgetUsage(ctx Context, req *pb.BudgetUsageRequest) (*pb.DataCollectionResponse, error)
}

DataCollector collects telemetry data for analytics, monitoring, or billing. Use this when you need to export data to external systems.

type Direction

type Direction int

Direction controls event routing and prevents feedback loops. Events are routed based on their direction: - DirLocal: Events stay on the local bus, never forwarded - DirUp: Events flow from edge to control - DirDown: Events flow from control to edge(s)

const (
	// DirLocal indicates events that should only be processed locally
	DirLocal Direction = iota
	// DirUp indicates events flowing from edge to control
	DirUp
	// DirDown indicates events flowing from control to edge(s)
	DirDown
)

func DirectionFromInt32

func DirectionFromInt32(d int32) Direction

DirectionFromInt32 converts an int32 to Direction.

func (Direction) String

func (d Direction) String() string

String returns a human-readable representation of the Direction.

type EdgePayload

type EdgePayload struct {
	Payload           []byte            // Arbitrary payload data from edge plugin
	EdgeID            string            // Edge instance that sent the payload
	EdgeNamespace     string            // Namespace of the edge instance
	CorrelationID     string            // Optional correlation ID for tracking
	Metadata          map[string]string // Optional key-value metadata
	EdgeTimestamp     int64             // Unix timestamp when generated at edge
	ReceivedTimestamp int64             // Unix timestamp when received at control
}

EdgePayload represents data sent from an edge plugin to a control plane plugin

type EdgePayloadReceiver

type EdgePayloadReceiver interface {
	Plugin

	// AcceptEdgePayload is called when a payload arrives from an edge instance.
	// payload contains the raw data sent by the edge plugin via SendToControl().
	// edgeID identifies which edge instance sent the payload.
	// correlationID can be used for request/response matching or tracking.
	// metadata contains any additional key-value data from the edge plugin.
	//
	// Returns:
	//   - handled: true if this plugin processed the payload
	//   - error: non-nil if processing failed
	AcceptEdgePayload(ctx Context, payload *EdgePayload) (handled bool, err error)
}

EdgePayloadReceiver handles payloads sent from edge (microgateway) instances. Use this when you need to receive data from plugins running on edge instances that are connected to AI Studio via the hub-and-spoke architecture.

Example use case: An llm-cache plugin running on edge instances sends cache statistics or shared cache data back to a central plugin on AI Studio.

type EndpointRegistration

type EndpointRegistration = pb.EndpointRegistration

Custom endpoint types

type EndpointRequest

type EndpointRequest = pb.EndpointRequest

Re-export commonly used proto types for convenience This allows plugin developers to import only plugin_sdk without importing proto directly

type EndpointResponse

type EndpointResponse = pb.EndpointResponse

Re-export commonly used proto types for convenience This allows plugin developers to import only plugin_sdk without importing proto directly

type EndpointResponseChunk

type EndpointResponseChunk = pb.EndpointResponseChunk

Re-export commonly used proto types for convenience This allows plugin developers to import only plugin_sdk without importing proto directly

type EnrichedRequest

type EnrichedRequest = pb.EnrichedRequest

Re-export commonly used proto types for convenience This allows plugin developers to import only plugin_sdk without importing proto directly

type Event

type Event struct {
	// ID is a UUID for deduplication and tracing
	ID string

	// Topic is the logical topic name (e.g., "config.update", "metrics.report")
	Topic string

	// Origin is the node/plugin ID that created the event
	Origin string

	// Dir is the direction (informational - when received, always treated as local)
	Dir Direction

	// Payload is the application-specific data as JSON
	Payload json.RawMessage
}

Event represents an event received from the event bus.

type EventHandler

type EventHandler func(event Event)

EventHandler is a callback function invoked when an event is received.

type EventService

type EventService interface {
	// Publish sends an event to the event bus.
	// topic: logical topic name (e.g., "cache.hit", "config.reload")
	// payload: arbitrary data (will be JSON-encoded)
	// dir: routing direction:
	//   - DirLocal: stays on local bus only
	//   - DirUp: forwarded from edge to control
	//   - DirDown: forwarded from control to edge(s)
	Publish(ctx context.Context, topic string, payload interface{}, dir Direction) error

	// PublishRaw sends an event with a pre-encoded JSON payload.
	PublishRaw(ctx context.Context, topic string, payload []byte, dir Direction) error

	// Subscribe registers a handler for events on a specific topic.
	// Returns a subscription ID that can be used to unsubscribe.
	// The handler is called asynchronously for each matching event.
	Subscribe(topic string, handler EventHandler) (string, error)

	// SubscribeAll registers a handler for all events regardless of topic.
	// Returns a subscription ID that can be used to unsubscribe.
	SubscribeAll(handler EventHandler) (string, error)

	// Unsubscribe removes a subscription by ID.
	// After this call, the handler will no longer receive events.
	Unsubscribe(subscriptionID string) error
}

EventService provides pub/sub capabilities for plugins. This service allows plugins to publish events to the event bus and subscribe to receive events. Events can flow across node boundaries (edge ↔ control) based on their direction.

type GatewayServices

type GatewayServices interface {
	// GetApp retrieves app details from local Gateway database
	// Returns microgateway_management.GetAppResponse proto type
	GetApp(ctx context.Context, appID uint32) (interface{}, error)

	// ListApps lists apps from local Gateway database
	ListApps(ctx context.Context, page, limit int32, isActive *bool) (interface{}, error)

	// GetLLM retrieves LLM details from local Gateway database
	GetLLM(ctx context.Context, llmID uint32) (interface{}, error)

	// ListLLMs lists LLMs from local Gateway database
	ListLLMs(ctx context.Context, page, limit int32, vendor string, isActive *bool) (interface{}, error)

	// GetBudgetStatus retrieves budget status (Gateway-only feature)
	GetBudgetStatus(ctx context.Context, appID uint32, llmID *uint32) (interface{}, error)

	// GetModelPrice retrieves model pricing from local Gateway database
	GetModelPrice(ctx context.Context, modelName, vendor string) (interface{}, error)

	// ListModelPrices lists model prices from local Gateway database
	ListModelPrices(ctx context.Context, vendor string) (interface{}, error)

	// ValidateCredential validates an API credential (Gateway-only feature)
	ValidateCredential(ctx context.Context, secret string) (interface{}, error)

	// StoreApp writes an App from the control plane into the local gateway database.
	// Used by auth plugins that need to provision apps dynamically without waiting
	// for periodic config sync. Uses upsert semantics (creates if not exists, updates if exists).
	// The request should contain the full app configuration including resource associations.
	// Requires "apps.write" scope in the plugin manifest.
	StoreApp(ctx context.Context, appID uint32, name, description string, isActive bool,
		userID uint32, metadata string, namespace string,
		llmIDs, toolIDs, datasourceIDs []uint32) error

	// SendToControl queues a payload to be sent to the AI Studio control plane
	// This is used by plugins running on edge (microgateway) instances to send
	// data back to the control plane for aggregation or processing.
	// Returns the number of payloads pending in the queue.
	SendToControl(ctx context.Context, payload []byte, correlationID string, metadata map[string]string) (int64, error)

	// SendToControlJSON is a convenience method that JSON-encodes a value and sends it to control
	SendToControlJSON(ctx context.Context, value interface{}, correlationID string, metadata map[string]string) (int64, error)
}

GatewayServices provides access to Microgateway-specific services Only available when Runtime == RuntimeGateway

type GroupSummary

type GroupSummary struct {
	ID        uint32
	Name      string
	IsDefault bool
}

GroupSummary is a team.

type HeadersRequest

type HeadersRequest = pb.HeadersRequest

Response modification types

type HeadersResponse

type HeadersResponse = pb.HeadersResponse

Re-export commonly used proto types for convenience This allows plugin developers to import only plugin_sdk without importing proto directly

type HookType

type HookType string

HookType represents the type of plugin hook (for gateway compatibility)

const (
	HookTypePreAuth        HookType = "pre_auth"
	HookTypeAuth           HookType = "auth"
	HookTypePostAuth       HookType = "post_auth"
	HookTypeResponse       HookType = "response"
	HookTypeDataCollection HookType = "data_collection"
)

func GetHookType

func GetHookType(p Plugin) HookType

GetHookType returns the primary hook type for a plugin based on its capabilities. This is used for gateway compatibility to determine plugin placement in the chain.

type KVService

type KVService interface {
	// Read retrieves a value by key
	// Returns error if key doesn't exist or has expired
	Read(ctx context.Context, key string) ([]byte, error)

	// Write stores a value with the given key and optional expiration
	// expireAt is optional - pass nil for no expiration
	// Returns whether a new key was created (true) or existing updated (false)
	Write(ctx context.Context, key string, value []byte, expireAt *time.Time) (bool, error)

	// WriteWithTTL stores a value with a TTL (time-to-live)
	// Expiration is calculated as time.Now().Add(ttl)
	// Returns whether a new key was created (true) or existing updated (false)
	WriteWithTTL(ctx context.Context, key string, value []byte, ttl time.Duration) (bool, error)

	// Delete removes a key
	// Returns whether the key existed and was deleted
	Delete(ctx context.Context, key string) (bool, error)

	// List returns all keys with the given prefix
	// Returns empty slice if no keys match
	List(ctx context.Context, prefix string) ([]string, error)
}

KVService provides key-value storage for plugin data. In Studio: Uses PostgreSQL-backed storage shared across all hosts. In Gateway: Uses local database storage per gateway instance.

type LicenseInfo

type LicenseInfo struct {
	// Valid indicates whether a valid enterprise license is present
	Valid bool

	// DaysRemaining is the number of days until license expires (-1 for community/never expires)
	DaysRemaining int

	// Type is the license type: "community" or "enterprise"
	Type string

	// Entitlements is a list of enabled features/entitlements
	Entitlements []string

	// Organization is the licensed organization name (enterprise only)
	Organization string

	// ExpiresAt is the license expiration timestamp (zero for community licenses)
	ExpiresAt time.Time
}

LicenseInfo contains information about the host's license status. This is a unified type that works in both AI Studio and Microgateway contexts.

func GetLicenseInfo

func GetLicenseInfo(ctx context.Context, runtime RuntimeType) (*LicenseInfo, error)

GetLicenseInfo retrieves license information from the host. This is a runtime-aware function that works in both AI Studio and Microgateway contexts. All plugins can call this without requiring special scopes.

func (*LicenseInfo) HasEntitlement

func (l *LicenseInfo) HasEntitlement(entitlement string) bool

HasEntitlement checks if a specific entitlement is enabled

func (*LicenseInfo) IsEnterprise

func (l *LicenseInfo) IsEnterprise() bool

IsEnterprise returns true if this is an enterprise license

type ListAppsOptions

type ListAppsOptions struct {
	IsActive  *bool
	Namespace string
	UserID    *uint32
}

ListAppsOptions provides optional filters for listing apps

type LogService

type LogService interface {
	Debug(msg string, fields ...interface{})
	Info(msg string, fields ...interface{})
	Warn(msg string, fields ...interface{})
	Error(msg string, fields ...interface{})
}

LogService provides structured logging

type MCPServerSummary

type MCPServerSummary struct {
	ID             uint32
	Name           string
	Slug           string
	Description    string
	Kind           string
	PrivacyScore   *int // nil until an administrator sets it
	IsActive       bool
	DashboardState string
	Brokerable     bool
	Tags           []string
	UpdatedAt      time.Time
}

MCPServerSummary is an MCP server as governance plugins see it.

type ManifestProvider

type ManifestProvider interface {
	Plugin

	// GetManifest returns the plugin manifest as JSON bytes.
	// The manifest declares hooks, permissions, and metadata.
	GetManifest() ([]byte, error)
}

ManifestProvider provides a plugin manifest without requiring full UI capabilities. This is useful for gateway-only plugins that need to be installed via Studio but don't have a UI component.

type MetadataAuditEntry

type MetadataAuditEntry struct {
	ID            uint32
	Action        string // set | merge | delete
	UserID        uint32
	Source        string // "user", "plugin:<id>", "system", ...
	BeforeJSON    string
	AfterJSON     string
	HooksExecuted []string
	CreatedAt     time.Time
}

MetadataAuditEntry is one change to an object's governed metadata.

type Notification

type Notification struct {
	// ID is an optional dedupe key. The platform scopes it to the plugin, so two
	// calls with the same ID produce a single notification per recipient.
	ID string
	// Type is a free-form label stored on the notification (e.g. "access_request").
	Type string
	// Title is required (max 255 characters).
	Title string
	// Content is the body, rendered as markdown in the admin UI (max 10000 characters).
	Content string
	// NotifyAdmins delivers the notification to every admin with notifications enabled.
	NotifyAdmins bool
	// UserID delivers the notification to a specific user (0 = none).
	UserID uint32
	// Link is what the notification opens when clicked: a same-origin path
	// ("/admin/...", "/portal/...") or an http(s) URL. Optional.
	Link string
	// Links gives each audience its own target and overrides Link: "admin"
	// for the admin fan-out, "portal" for the user named by UserID.
	Links map[string]string
}

Notification describes an in-app notification raised by a plugin via StudioServices.CreateNotification.

type ObjectHookHandler

type ObjectHookHandler interface {
	Plugin

	// GetObjectHookRegistrations declares which object operations this plugin wants to handle.
	// Returns a list of registrations specifying object types, hook types, and execution priority.
	GetObjectHookRegistrations() ([]*pb.ObjectHookRegistration, error)

	// HandleObjectHook processes a single object hook invocation.
	// req contains: hook type, object type, object data (JSON), user ID, operation ID
	// Returns: allow/reject decision, optional modified object, plugin metadata
	HandleObjectHook(ctx Context, req *pb.ObjectHookRequest) (*pb.ObjectHookResponse, error)
}

ObjectHookHandler intercepts CRUD operations on AI Studio objects (LLMs, Datasources, Tools, Users). Use this to implement custom validation, enrichment, or integration with external systems. Hooks are executed in priority order, and "before_*" hooks can reject operations.

type PermissionAction

type PermissionAction string

PermissionAction is one of the platform's permission verbs.

const (
	PermissionRead    PermissionAction = "read"
	PermissionWrite   PermissionAction = "write"
	PermissionDelete  PermissionAction = "delete"
	PermissionExecute PermissionAction = "execute"
	// PermissionPublish is the workflow verb: make an object live.
	PermissionPublish PermissionAction = "publish"
)

type PermissionResource

type PermissionResource struct {
	// Key is kebab-case and unique within the plugin; the platform registers
	// it as "plugin:<manifest id>:<key>".
	Key         string
	Label       string
	Description string
	// Actions offered, read first (e.g. read, write, delete, publish).
	Actions []PermissionAction
	// Sensitive withholds read from read-only system roles.
	Sensitive bool
}

PermissionResource is an RBAC resource a plugin contributes to the role editor beneath its own entry (see StudioServices.RegisterPermissionResources and the manifest's "rbac.resources").

type Plugin

type Plugin interface {
	// GetInfo returns metadata about the plugin
	GetInfo() PluginInfo

	// Initialize is called when plugin starts.
	// config contains runtime-specific configuration from the host.
	// The Context parameter provides runtime information (Studio vs Gateway)
	// and access to host services (KV storage, logging, etc.).
	Initialize(ctx Context, config map[string]string) error

	// Shutdown is called before plugin stops.
	// Plugins should clean up resources and complete in-flight operations.
	Shutdown(ctx Context) error
}

Plugin is the base interface ALL plugins must implement. This provides the minimum lifecycle management required for any plugin.

type PluginContext

type PluginContext = pb.PluginContext

Re-export commonly used proto types for convenience This allows plugin developers to import only plugin_sdk without importing proto directly

type PluginInfo

type PluginInfo struct {
	Name        string // Plugin name (e.g., "llm-rate-limiter")
	Version     string // Semantic version (e.g., "1.0.0")
	Description string // Human-readable description
}

PluginInfo provides plugin metadata returned by GetInfo()

type PluginRequest

type PluginRequest = pb.PluginRequest

Request/Response types

type PluginResponse

type PluginResponse = pb.PluginResponse

Re-export commonly used proto types for convenience This allows plugin developers to import only plugin_sdk without importing proto directly

type PortalUIProvider

type PortalUIProvider interface {
	Plugin

	// HandlePortalRPC processes RPC calls from the portal (end-user) UI.
	// This is SEPARATE from HandleRPC which only handles admin UI calls.
	// method: The RPC method name (e.g., "submit_feedback", "get_user_data")
	// payload: JSON payload as bytes from the portal frontend
	// userCtx: Authenticated portal user context (never nil)
	// Returns: JSON response as bytes
	HandlePortalRPC(method string, payload []byte, userCtx *PortalUserContext) ([]byte, error)
}

PortalUIProvider serves portal-facing UI pages and handles portal RPC calls. This is SEPARATE from UIProvider to enforce security boundaries between admin and portal contexts. Plugins can implement both UIProvider (for admin UI) and PortalUIProvider (for portal UI).

Note: PortalUIProvider does not include GetAsset/GetManifest - those come from UIProvider. A plugin that wants portal UI must implement both UIProvider (for assets/manifest) and PortalUIProvider (for portal RPC).

type PortalUserContext

type PortalUserContext struct {
	UserID   uint32
	Email    string
	Name     string
	IsAdmin  bool
	Groups   []string
	Metadata map[string]string
	// Permissions is the caller's effective permission set as
	// "resource:action" strings; ["*"] means full administrator. Empty for
	// portal users with no administrative role.
	Permissions []string
}

PortalUserContext provides information about the authenticated portal user. This is passed to plugins on portal RPC calls so they can make authorization decisions.

func (*PortalUserContext) Can

func (u *PortalUserContext) Can(perm string) bool

Can reports whether the user holds a permission on this plugin. perm may be a bare action ("write" → the plugin's base resource), a sub-resource permission ("assets:publish" → a resource declared by the plugin) or a full platform permission ("llms:read"). The platform hands the plugin the caller's permissions with its own grants spelled out, so plugins:execute holders and full administrators pass without the plugin knowing the umbrella rule. On a host that does not supply the plugin key, Can falls back to IsAdmin so older deployments keep their admin-only behaviour.

func (*PortalUserContext) HasPermission

func (u *PortalUserContext) HasPermission(perm string) bool

HasPermission reports whether the user holds perm ("resource:action"). The wildcard grants everything, and any action on a resource satisfies a read check for that resource, mirroring the platform's evaluator.

func (*PortalUserContext) PluginPermissionKey

func (u *PortalUserContext) PluginPermissionKey() string

PluginPermissionKey returns the calling plugin's RBAC key ("plugin:<manifest id>") as supplied by the platform, or "" on hosts that predate per-plugin permissions.

type PostAuthHandler

type PostAuthHandler interface {
	Plugin
	HandlePostAuth(ctx Context, req *pb.EnrichedRequest) (*pb.PluginResponse, error)
}

PostAuthHandler processes requests after authentication. This is the most common hook for implementing request policies like rate limiting, content filtering, or request enrichment.

type PreAuthHandler

type PreAuthHandler interface {
	Plugin
	HandlePreAuth(ctx Context, req *pb.PluginRequest) (*pb.PluginResponse, error)
}

PreAuthHandler processes requests before authentication. Use this when you need to modify or block requests before they reach the auth layer.

type ProxyLogRequest

type ProxyLogRequest = pb.ProxyLogRequest

Data collection types

type ResolvedMetadataSchema

type ResolvedMetadataSchema struct {
	// FieldsJSON is a JSON array of field definitions:
	// {key,label,description,type,required,severity,vocabulary_slug,pattern,min,max,
	//  max_length,warn_if_past,portal_visible,gateway_visible,order}.
	FieldsJSON string
	// JSONSchema is a draft-07 JSON Schema for the values object (vocabularies as enums).
	JSONSchema string
	// VocabulariesJSON maps vocabulary slug → [{value,label,description,deprecated}]
	// for every vocabulary referenced by FieldsJSON.
	VocabulariesJSON string
	// Enforcement is MetadataEnforcementAdvisory or MetadataEnforcementEnforce.
	Enforcement string
	// SchemaSlugs lists the schemas that contributed fields.
	SchemaSlugs []string
}

ResolvedMetadataSchema is the merged governed metadata schema for one object type. All payloads are JSON strings so plugins can hand them to their own UI unchanged.

func (*ResolvedMetadataSchema) HasFields

func (r *ResolvedMetadataSchema) HasFields() bool

HasFields reports whether any governed field applies to the object type.

func (*ResolvedMetadataSchema) IsEnforced

func (r *ResolvedMetadataSchema) IsEnforced() bool

IsEnforced reports whether an enforcing schema applies (hard errors block saves).

type ResourceFormComponent

type ResourceFormComponent struct {
	// Tag is the custom element tag name (e.g., "mcp-server-selector").
	Tag string

	// EntryPoint is the path to the JS file relative to plugin assets
	// (e.g., "ui/webc/mcp-selector.js").
	EntryPoint string
}

ResourceFormComponent declares a Web Component that the platform will render inside the App Create/Edit form for resource selection.

type ResourceInstance

type ResourceInstance struct {
	// ID is the plugin-assigned unique identifier for this instance.
	// Stored as a string to accommodate any ID format.
	ID string

	// Name is the human-readable display name.
	Name string

	// Description is an optional description.
	Description string

	// PrivacyScore is the privacy score (0-100). Only meaningful
	// if the ResourceTypeRegistration has HasPrivacyScore=true.
	PrivacyScore int

	// Metadata is arbitrary plugin-defined metadata as JSON bytes.
	// This is opaque to the platform but included in config snapshots.
	//
	// SECURITY: Do NOT store secrets, credentials, or PII in this field.
	// Metadata is propagated to all gateways, cached in the join table,
	// and may appear in logs or be accessible to other plugins.
	Metadata []byte

	// IsActive indicates if this instance is currently usable.
	IsActive bool

	// AccessGrantedViaApp optionally overrides the resource type's
	// AccessGrantedViaApp for this one instance. Nil inherits the type value.
	AccessGrantedViaApp *bool
}

ResourceInstance represents a single instance of a plugin resource type. Instances are managed by the plugin (stored in plugin KV or external systems) and exposed to the platform via the ResourceProvider interface.

type ResourceProvider

type ResourceProvider interface {
	Plugin

	// GetResourceTypeRegistrations declares the resource types this plugin provides.
	// Called once after Initialize() and again on plugin reload.
	GetResourceTypeRegistrations() ([]*ResourceTypeRegistration, error)

	// ListResourceInstances returns all instances of a given resource type.
	// The platform calls this to populate selection UI in the App form.
	ListResourceInstances(ctx Context, resourceTypeSlug string) ([]*ResourceInstance, error)

	// GetResourceInstance retrieves a single resource instance by its ID.
	GetResourceInstance(ctx Context, resourceTypeSlug string, instanceID string) (*ResourceInstance, error)

	// ValidateResourceSelection is called during app create/update to let the plugin
	// validate the selected resource instances. Returns nil if valid, or an error
	// describing why the selection is invalid.
	ValidateResourceSelection(ctx Context, resourceTypeSlug string, instanceIDs []string, appID uint32) error

	// CreateResourceInstance creates a new instance from a submission approval.
	// payload is a JSON-encoded creation payload defined by the plugin.
	CreateResourceInstance(ctx Context, resourceTypeSlug string, payload []byte) (*ResourceInstance, error)
}

ResourceProvider allows plugins to register custom resource types that can be associated with Apps. Resource instances are managed by the plugin via RPC calls, while the platform handles association, privacy validation, group-based access control, and submission workflow integration.

This capability is Studio-only. Gateway plugins receive resource associations in the config snapshot but do not provide resources.

Example:

func (p *MyPlugin) GetResourceTypeRegistrations() ([]*plugin_sdk.ResourceTypeRegistration, error) {
    return []*plugin_sdk.ResourceTypeRegistration{{
        Slug:            "mcp_servers",
        Name:            "MCP Servers",
        HasPrivacyScore: true,
    }}, nil
}

func (p *MyPlugin) ListResourceInstances(ctx plugin_sdk.Context, slug string) ([]*plugin_sdk.ResourceInstance, error) {
    // Return instances from your plugin's storage
}

type ResourceTypeRegistration

type ResourceTypeRegistration struct {
	// Slug is the machine-readable identifier (e.g., "mcp_servers").
	// Must be unique per plugin. Used in API paths and DB storage.
	Slug string

	// Name is the human-readable display name (e.g., "MCP Servers").
	Name string

	// Description explains what this resource type is.
	Description string

	// Icon is an optional icon identifier (Material icon name or asset path).
	Icon string

	// HasPrivacyScore indicates whether instances carry a privacy score.
	// If true, instances' privacy scores are subject to the generalized
	// privacy rule: no resource score may exceed the max LLM score in the app.
	HasPrivacyScore bool

	// SupportsSubmissions indicates whether community users can submit
	// new instances of this resource type through the submission workflow.
	SupportsSubmissions bool

	// SupportsMetadata indicates whether instances of this resource type can
	// carry governed metadata (Enterprise). When true, admins can attach
	// schema-validated metadata to instances under the object type
	// "plugin_resource:<plugin_id>:<slug>". Must also be set as
	// "supports_metadata" on the resource type in manifest.json.
	SupportsMetadata bool

	// FormComponent declares how the plugin provides its App Form UI section.
	// If nil, the platform renders a standard multi-select populated via ListResourceInstances.
	FormComponent *ResourceFormComponent

	// SubmissionSchema is an optional JSON Schema (with "type": "object")
	// describing the payload of community submissions for this type. When set,
	// the portal renders the submission form from it and the platform validates
	// submitted payloads against it before accepting them. Only meaningful when
	// SupportsSubmissions is true.
	SubmissionSchema string

	// AccessGrantedViaApp declares that an App credential is what grants
	// access to instances of this type (for example servers proxied by the
	// gateway, whose handler checks the App's plugin resource bindings).
	// Only such types are offered in the App forms, show "Build app" in the
	// portal catalog and travel in the gateway config snapshot. Types whose
	// instances are informational, or gated by the plugin's own access
	// request flow, should set this to false.
	//
	// When nil the platform applies its default: true when the plugin also
	// declares the custom_endpoint hook, false otherwise.
	AccessGrantedViaApp *bool

	// PortalDetailPath is an optional path template to an instance's page in
	// the portal. When access is not granted through an App the unified
	// catalog opens it as the item's detail view, in place of the built-in
	// detail page; otherwise the built-in page links to it. "{id}" is
	// replaced with the URL-escaped instance ID, e.g.
	// "/portal/plugins/asset-catalog#/assets/{id}". Must be a same-origin
	// path starting with "/"; portal plugin routes match the pathname
	// exactly, so carry the ID in the hash of a registered portal route.
	PortalDetailPath string

	// DefaultAccess decides whether active instances are granted to the
	// Default team (which every user joins). DefaultAccessAuto (or empty,
	// the default) grants them, so every user sees them. DefaultAccessExplicit
	// leaves access to team grants: an administrator's on the Teams page, or
	// the plugin's own through StudioServices.SetResourceInstanceGroups. Use
	// it for instances that are an internal record until someone decides to
	// share them. Community Edition has no team segmentation and always
	// behaves as DefaultAccessAuto.
	DefaultAccess string
}

ResourceTypeRegistration declares a resource type provided by a plugin. Each plugin can register one or more resource types that appear in the App creation/editing flow and participate in the governance model.

type ResponseHandler

type ResponseHandler interface {
	Plugin

	// OnBeforeWriteHeaders is called before response headers are sent to the client.
	// Use this to modify or add response headers.
	OnBeforeWriteHeaders(ctx Context, req *pb.HeadersRequest) (*pb.HeadersResponse, error)

	// OnBeforeWrite is called before response body is sent to the client.
	// This is called for both regular responses and streaming chunks.
	OnBeforeWrite(ctx Context, req *pb.ResponseWriteRequest) (*pb.ResponseWriteResponse, error)
}

ResponseHandler modifies response headers and body before sending to client. Use this when you need to transform responses or add custom headers.

type ResponseWriteRequest

type ResponseWriteRequest = pb.ResponseWriteRequest

Re-export commonly used proto types for convenience This allows plugin developers to import only plugin_sdk without importing proto directly

type ResponseWriteResponse

type ResponseWriteResponse = pb.ResponseWriteResponse

Re-export commonly used proto types for convenience This allows plugin developers to import only plugin_sdk without importing proto directly

type RouterSummary

type RouterSummary struct {
	ID             uint32
	Kind           string // "model_router" | "semantic_router"
	Name           string
	Slug           string
	Namespace      string
	Description    string
	Active         bool
	LLMIDs         []uint32
	ModelRouterIDs []uint32 // semantic routers only: targets that are model routers
	UpdatedAt      time.Time
}

RouterSummary is a model or semantic router and where it can send requests.

type RuntimeType

type RuntimeType string

RuntimeType indicates where the plugin is running

const (
	RuntimeStudio  RuntimeType = "studio"  // Running in AI Studio
	RuntimeGateway RuntimeType = "gateway" // Running in Microgateway
)

type Schedule

type Schedule struct {
	ID             string                 // Unique identifier from manifest
	Name           string                 // Human-readable name
	Cron           string                 // Cron expression (e.g., "0 * * * *")
	Timezone       string                 // Timezone for cron evaluation (e.g., "America/New_York", "UTC")
	Enabled        bool                   // Whether schedule is currently enabled
	TimeoutSeconds int                    // Maximum execution time in seconds
	Config         map[string]interface{} // Schedule-specific configuration from manifest
}

Schedule represents a cron-based task definition.

type SchedulerPlugin

type SchedulerPlugin interface {
	Plugin

	// ExecuteScheduledTask is called when a scheduled task needs to run.
	// ctx provides access to services (KV, logging) but has no request context.
	// schedule contains the schedule definition including ID, name, cron expression, and custom config.
	// Returns error if execution failed (will be recorded in execution history).
	ExecuteScheduledTask(ctx Context, schedule *Schedule) error
}

SchedulerPlugin allows plugins to execute tasks on a cron-based schedule. Use this when you need periodic background tasks like data synchronization, cleanup operations, or scheduled processing.

type ServiceBroker

type ServiceBroker interface {
	// KV returns the key-value storage service (works in both contexts)
	KV() KVService

	// Logger returns the logging service (works in both contexts)
	Logger() LogService

	// Gateway returns Gateway-specific services (only available in RuntimeGateway)
	// Returns nil if called in Studio context
	Gateway() GatewayServices

	// Studio returns Studio-specific services (only available in RuntimeStudio)
	// Returns nil if called in Gateway context
	Studio() StudioServices

	// Events returns the event pub/sub service (works in both contexts)
	// Allows plugins to publish and subscribe to events that flow across
	// edge/control boundaries via the event bridge system.
	Events() EventService
}

ServiceBroker provides access to host services. Services are accessed via runtime-specific methods for clarity and type safety.

type SessionAware

type SessionAware interface {
	// OnSessionReady is called when a session is first established.
	// This is the place to set up event subscriptions, start background goroutines,
	// or initialize resources that require a live broker connection.
	// This is only called once per plugin lifetime (on the first OpenSession),
	// not on session renewals (subsequent OpenSession calls after timeout).
	OnSessionReady(ctx Context)

	// OnSessionClosing is called before a session is explicitly closed.
	// This is NOT called on session timeout (timeouts expect the host to re-open).
	// Use this to clean up resources before the plugin shuts down.
	OnSessionClosing(ctx Context)
}

SessionAware is an optional interface for plugins that need session lifecycle callbacks. Implement this when your plugin needs to set up or tear down resources tied to the session-based broker connection (e.g., event subscriptions, background tasks).

The session pattern keeps go-plugin broker connections alive for plugins that need persistent background services like event pub/sub.

Example:

func (p *MyPlugin) OnSessionReady(ctx Context) {
    // Subscribe to events, start background tasks
    p.eventSubID, _ = ctx.Services.Events().Subscribe("my-topic", p.handleEvent)
}

func (p *MyPlugin) OnSessionClosing(ctx Context) {
    // Cleanup subscriptions before session ends
    ctx.Services.Events().Unsubscribe(p.eventSubID)
}

type SessionState

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

SessionState manages the state of a plugin session. A session keeps the broker connection alive for background services.

type StreamCompleteHandler

type StreamCompleteHandler interface {
	Plugin

	// OnStreamComplete is called after a streaming response has finished.
	// The accumulated_response contains all SSE chunks concatenated together.
	// This can be used to cache the complete response for future requests.
	OnStreamComplete(ctx Context, req *pb.StreamCompleteRequest) (*pb.StreamCompleteResponse, error)
}

StreamCompleteHandler processes completed streaming responses. Use this when you need to cache, analyze, or process the full response after all streaming chunks have been received and sent to the client. This is called asynchronously after the streaming response completes.

type StudioServices

type StudioServices interface {
	// GetApp retrieves app details from AI Studio database
	// Returns ai_studio_management.GetAppResponse proto type
	GetApp(ctx context.Context, appID uint32) (interface{}, error)

	// ListApps lists apps from AI Studio database
	ListApps(ctx context.Context, page, limit int32) (interface{}, error)

	// UpdateAppWithMetadata updates app configuration including metadata
	UpdateAppWithMetadata(ctx context.Context, appID uint32, name, description string, isActive bool, llmIDs, toolIDs, datasourceIDs []uint32, monthlyBudget *float64, metadata string) (interface{}, error)

	// PatchAppMetadata atomically updates a single metadata key on an app.
	// To set a key: PatchAppMetadata(ctx, appID, "mykey", `"myvalue"`, false)
	// To delete a key: PatchAppMetadata(ctx, appID, "mykey", "", true)
	// Returns the full metadata JSON string after the operation.
	PatchAppMetadata(ctx context.Context, appID uint32, key, value string, deleteKey bool) (string, error)

	// ListAppsWithFilters lists apps with optional filtering by user_id, namespace, and is_active
	ListAppsWithFilters(ctx context.Context, page, limit int32, opts *ListAppsOptions) (interface{}, error)

	// GetLLM retrieves LLM details from AI Studio database
	GetLLM(ctx context.Context, llmID uint32) (interface{}, error)

	// ListLLMs lists LLMs from AI Studio database
	ListLLMs(ctx context.Context, page, limit int32) (interface{}, error)

	// ListTools lists tools (Studio-only feature)
	ListTools(ctx context.Context, page, limit int32) (interface{}, error)

	// CallLLM proxies LLM requests (for agent plugins, Studio-only)
	CallLLM(ctx context.Context, llmID uint32, model string, messages interface{}, temperature float64, maxTokens int32) (interface{}, error)

	// UpdatePluginConfig updates the plugin's own configuration in the database
	// configJSON should be a valid JSON string containing the full configuration
	// Returns success status and any error message
	UpdatePluginConfig(ctx context.Context, pluginID uint32, configJSON string) (bool, string, error)

	// UpdateLLMPlugins updates plugin associations for an LLM
	// If append is true, the plugin is added to existing associations; otherwise replaces all
	// Returns success status and the final list of plugin IDs
	UpdateLLMPlugins(ctx context.Context, llmID uint32, pluginIDs []uint32, append bool) (bool, string, []uint32, error)

	// CreateNotification raises an in-app notification (and email when SMTP is
	// configured) for all admins and/or a specific user.
	// Requires the "notifications.write" service scope.
	CreateNotification(ctx context.Context, n Notification) error

	// RegisterResourceTypes (re)registers the plugin's resource types at runtime.
	// When deactivateMissing is true, previously registered types absent from
	// regs are deactivated. Returns the number of types registered and deactivated.
	// Requires the "resource-types.manage" service scope.
	RegisterResourceTypes(ctx context.Context, regs []ResourceTypeRegistration, deactivateMissing bool) (registered uint32, deactivated uint32, err error)

	// RegisterPermissionResources (re)registers the plugin's runtime RBAC
	// resources: rows in the role editor beneath the plugin's own entry, keyed
	// "plugin:<manifest id>:<key>". Use it for resources only known at runtime
	// (asset classes an administrator defines); static ones belong in the
	// manifest's "rbac.resources". With removeMissing, runtime resources absent
	// from specs are removed (manifest-declared ones never are). Returns the
	// counts and the plugin's permission key. Requires the "rbac.register"
	// service scope.
	RegisterPermissionResources(ctx context.Context, specs []PermissionResource, removeMissing bool) (registered uint32, removed uint32, pluginKey string, err error)

	// GetObjectMetadata returns the stored governed metadata values as JSON and whether a record exists.
	// Requires the metadata.read scope.
	GetObjectMetadata(ctx context.Context, objectType, objectID string) (valuesJSON string, found bool, err error)

	// GetObjectMetadataForAudience narrows the stored values to one audience
	// (MetadataVisibilityPortal or MetadataVisibilityGateway). For the portal it also
	// returns displayJSON: [{key,label,type,value}] with vocabulary labels and user
	// names resolved, i.e. exactly what the built-in portal shows end users.
	// Requires the metadata.read scope.
	GetObjectMetadataForAudience(ctx context.Context, objectType, objectID, visibility string) (valuesJSON string, displayJSON string, found bool, err error)

	// SetObjectMetadata validates and stores governed metadata. merge=true keeps existing keys.
	// Returns the stored values and the validation result as JSON. When an enforcing schema
	// rejects the values, err is nil and ok is false; inspect validationResultJSON for details.
	// Requires the metadata.write scope.
	SetObjectMetadata(ctx context.Context, objectType, objectID, valuesJSON string, merge bool) (ok bool, storedValuesJSON string, validationResultJSON string, err error)

	// DeleteObjectMetadata removes the governed metadata of an object. Call it when the
	// plugin deletes the resource instance so no orphaned record remains. Absent records succeed.
	// Requires the metadata.write scope.
	DeleteObjectMetadata(ctx context.Context, objectType, objectID string) error

	// GetResolvedMetadataSchema returns the merged schema for an object type: field
	// definitions, the draft-07 JSON Schema, the vocabularies those fields use and the
	// enforcement level. Enough to render the form in the plugin's own UI.
	// Requires the metadata.read scope.
	GetResolvedMetadataSchema(ctx context.Context, objectType string) (*ResolvedMetadataSchema, error)

	// ValidateObjectMetadataForPublish validates the values an object will
	// hold once it goes live (stored record for objectID merged with
	// valuesJSON; "" objectID = being created): fields marked "required to
	// publish" are required and the result is always enforced. Call it before
	// moving a resource instance into a live/approved state.
	ValidateObjectMetadataForPublish(ctx context.Context, objectType, objectID, valuesJSON string) (valid bool, resultJSON string, err error)

	// ValidateObjectMetadata validates values without storing them.
	// Requires the metadata.read scope.
	ValidateObjectMetadata(ctx context.Context, objectType, valuesJSON string) (valid bool, enforced bool, resultJSON string, err error)

	// ListObjectMetadataAudit returns the governed metadata history of an
	// object, newest first (limit 0 = 100, capped at 500). Community Edition
	// returns an empty list. Requires the metadata.read scope.
	ListObjectMetadataAudit(ctx context.Context, objectType, objectID string, limit int) ([]MetadataAuditEntry, error)

	// ListAuditRecords returns the platform audit records for one resource
	// ("llm", "tool", "datasource", "mcp_server", "model_router",
	// "semantic_router", "app"), newest first, without request or response
	// bodies; mutationsOnly drops reads. Enterprise only: Community Edition
	// answers codes.Unimplemented. Requires the audit.read scope.
	ListAuditRecords(ctx context.Context, resourceType, resourceID string, limit int, mutationsOnly bool) ([]AuditRecord, error)

	// ListMCPServers / GetMCPServer read MCP servers without upstream URLs,
	// auth details or definitions. Requires the mcp-servers.read scope.
	ListMCPServers(ctx context.Context, page, limit int32) ([]MCPServerSummary, int64, error)
	GetMCPServer(ctx context.Context, serverID uint32) (*MCPServerSummary, error)

	// ListModelRouters, GetModelRouter, ListSemanticRouters and
	// GetSemanticRouter read routers with the LLMs (and, for semantic
	// routers, the model routers) they can send requests to. Enterprise
	// only. Requires the routers.read scope.
	ListModelRouters(ctx context.Context, page, limit int32) ([]RouterSummary, int64, error)
	GetModelRouter(ctx context.Context, routerID uint32) (*RouterSummary, error)
	ListSemanticRouters(ctx context.Context, page, limit int32) ([]RouterSummary, int64, error)
	GetSemanticRouter(ctx context.Context, routerID uint32) (*RouterSummary, error)

	// ListGroups lists every team, with the Default team flagged.
	ListGroups(ctx context.Context) ([]GroupSummary, error)
	// GetResourceInstanceGroups returns the IDs of the teams granted an instance.
	GetResourceInstanceGroups(ctx context.Context, resourceTypeSlug, instanceID string) ([]uint32, error)
	// SetResourceInstanceGroups adds groupIDs to an instance's grants, or with
	// replace sets them to exactly groupIDs. Returns the grants afterwards.
	SetResourceInstanceGroups(ctx context.Context, resourceTypeSlug, instanceID string, groupIDs []uint32, replace bool) ([]uint32, error)
	// ListAccessibleResourceInstances returns which instances of a type a user
	// can reach: seeAll when the user manages teams, otherwise the instance
	// IDs granted to the user's teams.
	ListAccessibleResourceInstances(ctx context.Context, resourceTypeSlug string, userID uint32) (seeAll bool, instanceIDs []string, err error)

	// SetAppGovernanceState suspends or reactivates an App (isActive nil
	// leaves it alone) and raises or clears governance flags on it (an empty
	// value clears a flag); the change is audited with the reason. It cannot
	// change what the App may access. Returns whether anything changed.
	// Requires the apps.lifecycle scope.
	SetAppGovernanceState(ctx context.Context, appID uint32, isActive *bool, flags map[string]string, reason string) (changed bool, err error)
}

StudioServices provides access to AI Studio-specific services Only available when Runtime == RuntimeStudio

type SubmissionEnvelope

type SubmissionEnvelope struct {
	Source               string                 `json:"source"` // always "submission"
	SubmissionID         uint32                 `json:"submission_id"`
	Submitter            SubmissionUser         `json:"submitter"`
	Reviewer             SubmissionUser         `json:"reviewer"`
	FinalPrivacyScore    int                    `json:"final_privacy_score"`
	SuggestedPrivacy     int                    `json:"suggested_privacy"`
	PrivacyJustification string                 `json:"privacy_justification"`
	AssignedCatalogues   []uint32               `json:"assigned_catalogues"`
	DocumentationURL     string                 `json:"documentation_url"`
	Notes                string                 `json:"notes"`
	Attestations         interface{}            `json:"attestations"`
	ResourcePayload      map[string]interface{} `json:"resource_payload"`
}

SubmissionEnvelope is the payload AI Studio passes to ResourceProvider.CreateResourceInstance when an administrator approves a community submission for one of the plugin's resource types.

The RPC is issued outside the platform's database transaction, so a plugin must treat SubmissionID as an idempotency key: if an instance already exists for the same submission, return it instead of creating a duplicate.

func ParseSubmissionEnvelope

func ParseSubmissionEnvelope(payload []byte) (*SubmissionEnvelope, error)

ParseSubmissionEnvelope decodes the payload passed to CreateResourceInstance. Payloads without a "source" field are treated as a bare resource payload (the pre-envelope contract), so plugins can accept both shapes.

type SubmissionUser

type SubmissionUser struct {
	ID    uint32 `json:"id"`
	Email string `json:"email"`
	Name  string `json:"name"`
}

SubmissionUser identifies a user involved in a community submission.

type UIProvider

type UIProvider interface {
	Plugin

	// GetAsset serves a static asset file (JS, CSS, images, etc.).
	// assetPath is relative to the plugin's asset root (e.g., "ui/webc/dashboard.js").
	// Returns: (content []byte, mimeType string, error)
	GetAsset(assetPath string) ([]byte, string, error)

	// ListAssets returns a list of all available assets.
	// pathPrefix can be used to filter assets (e.g., "ui/").
	ListAssets(pathPrefix string) ([]*pb.AssetInfo, error)

	// GetManifest returns the plugin manifest as JSON bytes.
	// The manifest declares UI components, permissions, and metadata.
	GetManifest() ([]byte, error)

	// HandleRPC processes custom RPC method calls from the UI.
	// method: The RPC method name (e.g., "get_statistics", "update_settings")
	// payload: JSON payload as bytes from the frontend
	// Returns: JSON response as bytes
	HandleRPC(method string, payload []byte) ([]byte, error)
}

UIProvider serves web UI assets for the AI Studio admin interface. This is only relevant for Studio plugins that have a management UI.

type User

type User = pb.User

Re-export commonly used proto types for convenience This allows plugin developers to import only plugin_sdk without importing proto directly

type UserAwareRPCHandler

type UserAwareRPCHandler interface {
	UIProvider

	// HandleRPCWithUser processes an admin RPC call with the caller's identity.
	HandleRPCWithUser(method string, payload []byte, userCtx *PortalUserContext) ([]byte, error)
}

UserAwareRPCHandler is an optional extension of UIProvider for plugins that need to know which administrator issued an admin RPC call (for audit trails, ownership fields, or per-admin behaviour).

When a plugin implements this interface, admin UI calls (POST /api/v1/plugins/:id/rpc/:method) are routed to HandleRPCWithUser with the authenticated caller instead of HandleRPC. Plugins that do not implement it keep receiving HandleRPC unchanged. userCtx is never nil; IsAdmin is always true on this path because the route is admin-only.

Jump to

Keyboard shortcuts

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