sdk

package module
v0.0.0-...-1e0776f Latest Latest
Warning

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

Go to latest
Published: Jan 20, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

AIClient provides typed access to AI capabilities (embedding, chat).

This client wraps the low-level Invoke calls and handles proto marshaling internally, so plugin code doesn't need to import proto or pluginv1.

Usage

ai := sdk.NewAIClient(plugins)

// Generate embeddings
embedding, err := ai.GenerateEmbedding(ctx, "hello world")

// Chat completion
resp, err := ai.Chat(ctx, sdk.ChatRequest{
    Messages: []sdk.ChatMessage{
        {Role: "user", Content: "Hello!"},
    },
})

Package sdk provides utilities for building ViewRA plugins.

The SDK provides common functionality that all plugins need:

  • Logging with request ID tracking
  • Metrics collection
  • Data directory access
  • Settings schema building

Plugin Structure

All plugins embed sdk.Base to get common functionality:

type MyPlugin struct {
    sdk.Base
    // your fields
}

Logging

Use sdk.NewLogger in your main() to create properly configured loggers:

func main() {
    hclogger, logger := sdk.NewLogger("my-plugin")
    plugin := NewMyPlugin(logger)
    // ... serve plugin
}

Then use p.Log() or p.LogWithContext(ctx) in your plugin code:

func (p *MyPlugin) DoSomething(ctx context.Context) {
    p.LogWithContext(ctx).Info("doing something", "key", "value")
}

Data Directory

Plugins can store persistent data in their data directory:

func (p *MyPlugin) Initialize(ctx context.Context, dataDir string, config []byte) error {
    cachePath := filepath.Join(p.DataDir(), "cache.db")
    // ... use cache
}

Metrics

Track request metrics for health monitoring:

func (p *MyPlugin) HandleRequest(ctx context.Context) error {
    start := time.Now()
    defer func() {
        p.RecordRequest(time.Since(start))
    }()
    // ... handle request
}

Package sdk provides database utilities for ViewRA plugins.

The SQLClient provides managed SQL storage where the host handles table namespacing, connection pooling, and security enforcement. Plugins don't need to manage their own database connections or bundle SQLite drivers.

Usage

func (p *MyPlugin) Initialize(ctx context.Context, req *pluginv1.InitRequest) (*pluginv1.InitResponse, error) {
    db := p.storage.SQL()

    // Run migrations
    err := db.Migrate(ctx, []sdk.Migration{
        {Version: 1, SQL: `CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)`},
    })
    if err != nil {
        return nil, err
    }

    p.db = db
    return &pluginv1.InitResponse{Success: true}, nil
}

Table Namespacing

All table names are automatically prefixed by the host with plugin_{id}_ For example, if your plugin ID is "semantic-search" and you create a table named "embeddings", the actual table name will be "plugin_semantic_search_embeddings".

You don't need to worry about this - just use your table names as-is.

Dual Database Compatibility

SQL must work on both PostgreSQL and SQLite. Stick to common SQL features:

  • Basic types: TEXT, INTEGER, REAL, BLOB
  • CREATE TABLE, CREATE INDEX, DROP TABLE, DROP INDEX
  • PRIMARY KEY, UNIQUE, NOT NULL, DEFAULT
  • INSERT, UPDATE, DELETE, SELECT
  • WHERE, ORDER BY, LIMIT, OFFSET
  • JOIN, LEFT JOIN
  • COUNT, MAX, MIN, SUM, AVG

Avoid:

  • RETURNING clause (use LastInsertID instead)
  • Array types
  • SERIAL (use INTEGER PRIMARY KEY for auto-increment)
  • Database-specific JSON operators

Enricher plugin support for ViewRA metadata enricher plugins.

This file provides the EnricherPlugin interface and ServeEnricher() helper for building enricher plugins (like TMDb, MusicBrainz).

Quick Start

Create an enricher plugin that implements the EnricherPlugin interface:

type MyEnricher struct {
    sdk.Base
    storage *sdk.StorageClient
}

func (e *MyEnricher) GetCapabilities() sdk.EnricherCapabilities {
    return sdk.EnricherCapabilities{
        MediaTypes: []string{"movie", "tv"},
        Provides:   []string{"metadata", "artwork"},
    }
}

func (e *MyEnricher) Initialize(ctx context.Context, dataDir string, config []byte, services *sdk.HostServices) error {
    e.storage = services.Storage // Use host storage for caching
    return nil
}

// ... implement other methods

func main() {
    hclogger, logger := sdk.NewLogger("my-enricher")
    plugin := &MyEnricher{}
    plugin.SetLogger(logger)
    sdk.ServeEnricher(plugin, hclogger)
}

Event handling helpers for ViewRA plugins.

Plugins can subscribe to events from the host to react to changes in the media library, playback state, and more.

Available Events

Media events:

  • media.added: New media item added to library
  • media.updated: Media metadata updated
  • media.deleted: Media item removed

Playback events:

  • playback.started: User started playback
  • playback.stopped: User stopped playback
  • playback.completed: Playback reached end

Library events:

  • library.scan.started: Library scan started
  • library.scan.completed: Library scan finished

Usage

Implement the EventHandler interface and return subscriptions:

func (p *MyPlugin) GetSubscriptions() []string {
    return []string{"media.added", "media.updated"}
}

func (p *MyPlugin) OnEvent(ctx context.Context, event sdk.Event) bool {
    switch event.Type {
    case "media.added":
        var payload MediaAddedPayload
        if err := event.Unmarshal(&payload); err != nil {
            return false
        }
        // Handle new media
        return true
    }
    return false
}

Host service client wrappers for ViewRA plugins.

This file provides type-safe wrappers around the host services available to plugins. These services are exposed by the host and allow plugins to access data, storage, and more.

Available Services

The host exposes these services to plugins:

  • HostData: Access media library data (read-only)
  • HostStorage: Plugin-scoped key-value, SQL, and vector storage
  • HostPlugins: Capability-based plugin discovery and inter-plugin communication
  • HostWeather: Weather and time context for search queries
  • HostRatings: User ratings (favorites, likes, dislikes) for recommendations

AI Capabilities

For AI functionality (embeddings, chat), use PluginsClient to discover and connect to provider plugins that offer "embeddings" or "chat" capabilities. See PluginsClient.GetConnection() for details.

Usage

Plugins receive broker IDs in the InitRequest. Use these to connect:

func (p *MyPlugin) Initialize(ctx context.Context, req *pluginv1.InitRequest) (*pluginv1.InitResponse, error) {
    if req.HostStorageBrokerId > 0 {
        conn, _ := broker.Dial(req.HostStorageBrokerId)
        p.storage = sdk.NewStorageClient(conn)
    }
    if req.HostPluginsBrokerId > 0 {
        conn, _ := broker.Dial(req.HostPluginsBrokerId)
        // PluginsClient provides capability invocation via host-proxied generic invoke
        p.plugins = sdk.NewPluginsClient(conn)
    }
    return &pluginv1.InitResponse{Success: true}, nil
}

HTTP helpers for ViewRA plugin endpoints.

This package provides types and utilities for handling HTTP requests in plugins that expose custom routes.

Quick Start

Implement the HTTPProvider interface to expose routes:

func (p *MyPlugin) GetRoutes() []sdk.Route {
    return []sdk.Route{
        {Path: "/health", Methods: []string{"GET"}},
        {Path: "/items", Methods: []string{"GET", "POST"}},
        {Path: "/items/:id", Methods: []string{"DELETE"}},
    }
}

func (p *MyPlugin) HandleHTTP(ctx context.Context, req *sdk.HTTPRequest) (*sdk.HTTPResponse, error) {
    switch req.Path {
    case "/health":
        return sdk.JSONResponse(200, map[string]bool{"healthy": true})
    case "/items":
        if req.Method == "GET" {
            return p.listItems(ctx, req)
        }
        return p.createItem(ctx, req)
    default:
        return sdk.JSONError(404, "not found")
    }
}

Response Helpers

Use the response helpers for common patterns:

// JSON response with any data
sdk.JSONResponse(200, map[string]any{"items": items})

// Error response
sdk.JSONError(400, "invalid request")

// Empty success
sdk.EmptyResponse(204)

SSE Streaming

For streaming responses (like download progress):

func (p *MyPlugin) HandleHTTPStream(ctx context.Context, req *sdk.HTTPRequest, w sdk.HTTPStreamWriter) error {
    w.WriteHeader(200, "text/event-stream", nil)

    for progress := range downloadProgress {
        data, _ := json.Marshal(progress)
        w.WriteSSE("progress", data)
    }

    return nil
}

Logging utilities for ViewRA plugins.

ViewRA plugins use go-plugin for process isolation, which requires special handling of logs. This file provides logger adapters that ensure plugin logs are properly captured and forwarded to the host application.

Why Special Logging?

go-plugin runs plugins in separate processes. For logs to appear in the host's log output, they must be written in a format that go-plugin can parse and forward. The NewLogger function creates loggers configured for this.

Usage

In your plugin's main() function:

func main() {
    // Create both hclog (for go-plugin) and slog (for your code) loggers
    hclogger, logger := sdk.NewLogger("my-plugin")

    // Create your plugin with the slog logger
    plugin := internal.NewPlugin(logger)

    // Start the plugin server with the hclog logger
    go_plugin.Serve(&go_plugin.ServeConfig{
        HandshakeConfig: sdk.Handshake,
        Plugins: map[string]go_plugin.Plugin{
            "core": &MyGRPCPlugin{Impl: plugin},
        },
        GRPCServer: go_plugin.DefaultGRPCServer,
        Logger:     hclogger, // Important: pass hclogger here
    })
}

Then use the slog logger throughout your plugin:

func (p *Plugin) DoSomething() {
    p.logger.Info("doing something", "key", "value")
}

PluginsClient provides access to other plugins via the host-proxied capability invoke.

This allows plugins to invoke methods on other plugins that provide specific capabilities like "embedding", "chat", or custom plugin-defined capabilities. The host acts as a proxy, routing requests to the appropriate provider.

Usage

The PluginsClient is obtained from the HostServices:

plugins := hostServices.Plugins
if plugins.IsAvailable(ctx, "embedding") {
    respBytes, meta, err := plugins.Invoke(ctx, "embedding", "GenerateEmbedding", req)
    // Unmarshal respBytes to get the typed response
}

Capabilities

Capabilities are declared in plugin manifests via the "provides" field. Common capabilities include:

  • "embedding" - Vector embedding generation
  • "chat" - Chat/completion generation
  • "search" - Search over media

Plugins can also define custom capabilities for inter-plugin communication.

Provider plugin support for ViewRA AI provider plugins.

This file provides the ProviderPlugin interface and ServeProvider() helper for building AI provider plugins (like Ollama, OpenAI, Anthropic).

Quick Start

Create a provider plugin that implements the ProviderPlugin interface:

type MyProvider struct {
    sdk.Base
    apiKey string
}

func (p *MyProvider) GetProviderCapabilities() sdk.ProviderCapabilities {
    return sdk.ProviderCapabilities{
        ProviderID:        "my-provider",
        DisplayName:       "My Provider",
        SupportsChat:      true,
        SupportsEmbedding: true,
    }
}

// ... implement other methods

func main() {
    hclogger, logger := sdk.NewLogger("my-provider")
    plugin := &MyProvider{}
    plugin.SetLogger(logger)
    sdk.ServeProvider(plugin, hclogger)
}

Schema builder for ViewRA plugin settings.

This package provides a fluent API for building JSON Schema definitions with ViewRA-specific extensions. Plugin authors use this to define their settings UI without writing raw JSON.

Quick Start

Create a schema.go file in your plugin's internal package:

package internal

import "github.com/mantonx/viewra/pkg/plugin/sdk"

func SettingsSchema() *sdk.Schema {
    return sdk.NewSchema("My Plugin Settings").
        Meta(sdk.PluginMeta{
            DisplayName: "My Plugin",
            Description: "Does something useful",
            Icon:        "star",
        }).
        Property("api_key", sdk.String().
            Title("API Key").
            Description("Your API key").
            Format("password").
            Required()).
        Action(sdk.TestAction("test-connection", "/health"))
}

Then in your plugin.go, use it in GetSettingsSchema:

func (p *Plugin) GetSettingsSchema(ctx context.Context, _ *pluginv1.Empty) (*pluginv1.SettingsSchema, error) {
    return SettingsSchema().BuildSettingsSchema()
}

ViewRA Schema Extensions

ViewRA extends JSON Schema with custom fields:

  • x-viewra-meta: Plugin metadata for the UI (display name, icon, etc.)
  • x-viewra-actions: Interactive UI elements (test buttons, lists, forms)
  • x-viewra-sections: Group properties/actions by capability for filtering

Examples

Simple API provider (like OpenAI):

sdk.NewSchema("OpenAI Settings").
    Meta(sdk.PluginMeta{
        DisplayName: "OpenAI",
        Description: "OpenAI API for embeddings and chat",
        Tip:         "Requires API key. Usage is billed per token.",
        Icon:        "cloud",
    }).
    Property("api_key", sdk.String().
        Title("API Key").
        Format("password").
        Required()).
    Property("model", sdk.String().
        Title("Model").
        Default("gpt-4o-mini")).
    Action(sdk.TestAction("test-connection", "/health"))

Provider with model management (like Ollama):

sdk.NewSchema("Ollama Settings").
    Meta(sdk.PluginMeta{
        DisplayName: "Ollama",
        Description: "Local AI inference",
        IsLocal:     true,
        Icon:        "hard-drive",
    }).
    Property("base_url", sdk.String().
        Title("Server URL").
        Default("http://localhost:11434")).
    Property("model", sdk.String().
        Title("Model").
        EnumStrings("llama3", "mistral")). // Dynamic from installed models
    Section(sdk.NewSection("connection").
        Properties("base_url").
        Actions("test-connection").
        Capabilities("embedding", "chat")).
    Section(sdk.NewSection("models").
        Properties("model").
        Actions("model-list").
        Capabilities("chat")).
    Action(sdk.TestAction("test-connection", "/health")).
    Action(sdk.ListAction("model-list", "/models").
        Title("Available Models").
        TabTitle("Models").
        Display(sdk.NewListDisplay("name").
            SecondaryField("description")).
        ItemAction(sdk.NewDeleteAction("delete", "/models/:id").
            Confirm("Delete Model", "Are you sure?")))

Available Icons

Common icons: "cloud", "hard-drive", "brain", "compass", "star", "settings", "database", "film", "music", "tv", "search", "sparkles"

Property Formats

Special formats for string properties:

  • "password": Renders as a password field (masked input)
  • "uri": URL validation
  • "email": Email validation

Search provider plugin support for ViewRA.

This file provides the SearchProvider interface for building plugins that provide search functionality. Multiple search providers can be registered, with the highest priority enabled one being the default.

Quick Start

Implement the SearchProvider interface in your plugin:

type MySearchPlugin struct {
    sdk.Base
    // ...
}

func (p *MySearchPlugin) Search(ctx context.Context, req *sdk.SearchProviderRequest) (*sdk.SearchProviderResponse, error) {
    // Perform search
    return &sdk.SearchProviderResponse{
        Results: results,
        Total:   len(results),
    }, nil
}

func (p *MySearchPlugin) GetSuggestions(ctx context.Context, req *sdk.SuggestionRequest) (*sdk.SuggestionResponse, error) {
    // Return contextual suggestions
    return &sdk.SuggestionResponse{
        Suggestions: suggestions,
    }, nil
}

func (p *MySearchPlugin) GetSearchProviderInfo(ctx context.Context) (*sdk.SearchProviderInfo, error) {
    return &sdk.SearchProviderInfo{
        ID:           "my-search",
        Name:         "My Search",
        Description:  "AI-powered semantic search",
        Priority:     100,
        Capabilities: []string{"natural_language", "suggestions"},
    }, nil
}

Then declare the capability in plugin.yml:

provides:
  - search_provider

Trending provider plugin support for ViewRA.

This file provides the TrendingProvider interface for building plugins that provide trending/popular content data. Trending data is matched against the user's library to show relevant trending items.

Quick Start

Implement the TrendingProvider interface in your enricher plugin:

type TMDbPlugin struct {
    sdk.Base
    // ...
}

func (p *TMDbPlugin) GetTrending(ctx context.Context, req *sdk.TrendingRequest) (*sdk.TrendingResponse, error) {
    // Fetch trending from TMDb API
    return &sdk.TrendingResponse{
        Items:  items,
        Window: req.Window,
        Source: "tmdb",
    }, nil
}

func (p *TMDbPlugin) GetTrendingProviderInfo(ctx context.Context) (*sdk.TrendingProviderInfo, error) {
    return &sdk.TrendingProviderInfo{
        ID:          "tmdb",
        Name:        "TMDb Trending",
        Description: "Trending movies and TV shows from The Movie Database",
        Windows:     []string{"day", "week"},
        MediaTypes:  []string{"movie", "tv", "all"},
        UpdateFreq:  "daily",
    }, nil
}

Then declare the capability in plugin.yml:

provides:
  - enricher
  - trending

Package sdk provides vector storage utilities for ViewRA plugins.

The VectorClient provides managed vector storage with automatic indexing. The host uses pgvector (PostgreSQL) or sqlite-vec (SQLite) under the hood, providing fast approximate nearest neighbor (ANN) search.

Usage

func (p *MyPlugin) Initialize(ctx context.Context, req *pluginv1.InitRequest) (*pluginv1.InitResponse, error) {
    vec := p.storage.Vector()

    // Store an embedding
    err := vec.Store(ctx, sdk.Embedding{
        EntityType: "movie",
        EntityID:   123,
        Vector:     embedding,  // []float32 from your embedding model
        Text:       "The Matrix (1999) - A computer hacker learns...",
    })

    // Search for similar items
    results, err := vec.Search(ctx, sdk.VectorSearchRequest{
        QueryVector: queryEmbedding,
        Limit:       10,
    })
}

Entity Namespacing

Embeddings are automatically namespaced per-plugin. Each plugin has its own isolated vector index. Entity types and IDs are scoped to the plugin.

Embedding Dimensions

The host automatically detects the embedding dimensions from the first vector stored. All subsequent vectors must have the same dimensions. Common dimensions: 384 (MiniLM), 768 (BERT), 1536 (OpenAI ada-002), 3072 (OpenAI text-embedding-3-large)

Vector search plugin support for ViewRA semantic search plugins.

This file provides the VectorSearchPlugin interface and ServeVectorSearchEnricher() helper for building search plugins that provide semantic search, similarity matching, and embedding-based media indexing.

Widget plugin support for ViewRA widget plugins.

This file provides the WidgetPlugin interface and ServeWidget() helper for building widget plugins that provide HTTP routes and home screen widgets but don't enrich media items (like recommendations, continue watching, etc.).

Quick Start

Create a widget plugin that implements the WidgetPlugin interface:

type MyWidget struct {
    sdk.Base
    storage *sdk.StorageClient
}

func (w *MyWidget) Initialize(ctx context.Context, dataDir string, config []byte, services *sdk.HostServices) error {
    w.storage = services.Storage
    return nil
}

func (w *MyWidget) Shutdown(ctx context.Context) error {
    return nil
}

func (w *MyWidget) GetSettingsSchema() ([]byte, error) {
    return sdk.NewSchema().
        Widgets([]sdk.Widget{...}).
        Build()
}

func (w *MyWidget) Configure(settings []byte) error {
    return nil
}

func (w *MyWidget) GetRoutes() []sdk.Route {
    return []sdk.Route{...}
}

func (w *MyWidget) HandleHTTP(ctx context.Context, req *sdk.HTTPRequest) (*sdk.HTTPResponse, error) {
    // Handle requests
}

func main() {
    hclogger, logger := sdk.NewLogger("my-widget")
    plugin := &MyWidget{}
    plugin.SetLogger(logger)
    sdk.ServeWidget(plugin, hclogger)
}

Widget definitions for ViewRA home screen plugin integration.

Plugins can register widgets to appear on the home screen via their settings schema. Widgets are rendered by clients (web, iOS, Roku, etc.) using the data provided by the plugin.

Quick Start

In your plugin's schema.go, add widgets:

func SettingsSchema() *sdk.Schema {
    return sdk.NewSchema("My Plugin Settings").
        Meta(sdk.PluginMeta{...}).
        Widgets([]sdk.Widget{
            {
                ID:              "my-recommendations",
                Type:            sdk.WidgetTypeMediaRow,
                Location:        sdk.LocationHomepageSections,
                ClientTypes:     []string{sdk.ClientTypeAll},
                Priority:        80,
                CacheTTLSeconds: 600,
                Config: map[string]any{
                    "endpoint": "/recommendations",
                    "title":    "Recommended for You",
                },
                SettingsKey: "enabled",
            },
        })
}

Then implement the HTTP endpoint to return widget data:

func (p *Plugin) HandleHTTP(ctx context.Context, req *sdk.HTTPRequest) (*sdk.HTTPResponse, error) {
    if req.Path == "/recommendations" {
        items := p.getRecommendations(ctx, req.UserID)
        return jsonResponse(200, map[string]any{
            "title": "Recommended for You",
            "items": items,
        })
    }
    // ...
}

Index

Constants

View Source
const (
	// Media events
	EventMediaAdded   = "media.added"
	EventMediaUpdated = "media.updated"
	EventMediaDeleted = "media.deleted"

	// Playback events
	EventPlaybackStarted   = "playback.started"
	EventPlaybackStopped   = "playback.stopped"
	EventPlaybackCompleted = "playback.completed"

	// Library events
	EventLibraryScanStarted   = "library.scan.started"
	EventLibraryScanCompleted = "library.scan.completed"
)

Event types

View Source
const (
	RatingFavorite = "favorite"
	RatingUp       = "up"
	RatingDown     = "down"
)

Rating type constants.

View Source
const (
	// LocationHomepageTop is the top section of the homepage (search hero, featured content).
	LocationHomepageTop = "homepage-top"

	// LocationHomepageSections is the main content area with horizontal rows.
	LocationHomepageSections = "homepage-sections"
)

Widget locations define where a widget appears on the home screen.

View Source
const (
	// WidgetTypeSearchHero is a search input with AI-generated suggestion chips.
	// Best for: semantic-search plugin on web/iOS/Android.
	// Data shape: {placeholder, suggestions[], search_url}
	WidgetTypeSearchHero = "search-hero"

	// WidgetTypeFeaturedRow is a horizontal row of featured items.
	// Best for: constrained clients (Roku) as an alternative to search-hero.
	// Data shape: {title, items[]}
	WidgetTypeFeaturedRow = "featured-row"

	// WidgetTypeContinueRow is a continue watching row with progress bars.
	// Best for: showing in-progress content.
	// Data shape: {title, items[] with progress}
	WidgetTypeContinueRow = "continue-row"

	// WidgetTypeMediaRow is a generic horizontal media row.
	// Best for: recommendations, similar items, trending.
	// Data shape: {title, subtitle?, items[], see_all_url?}
	WidgetTypeMediaRow = "media-row"
)

Widget types define how a widget is rendered.

View Source
const (
	// ClientTypeAll means all clients should render this widget.
	ClientTypeAll = "all"

	// ClientTypeWeb is the web browser client.
	ClientTypeWeb = "web"

	// ClientTypeIOS is the iOS/tvOS client.
	ClientTypeIOS = "ios"

	// ClientTypeAndroid is the Android/Android TV client.
	ClientTypeAndroid = "android"

	// ClientTypeRoku is the Roku channel.
	ClientTypeRoku = "roku"

	// ClientTypeFireTV is the Amazon Fire TV client.
	ClientTypeFireTV = "firetv"

	// ClientTypeSmartTV is LG WebOS, Samsung Tizen, etc.
	ClientTypeSmartTV = "smarttv"
)

Client types identify which clients should render a widget.

Variables

View Source
var ErrNoRows = errors.New("sql: no rows in result set")

ErrNoRows is returned by QueryRow when no rows are found.

View Source
var Handshake = plugin.HandshakeConfig{
	ProtocolVersion:  1,
	MagicCookieKey:   "VIEWRA_PLUGIN",
	MagicCookieValue: "viewra-plugin-v1",
}

Handshake is the shared handshake config for all ViewRA plugins. Both host and plugin must agree on these values.

Functions

func GetHeader

func GetHeader(req *HTTPRequest, key string) string

GetHeader gets a header value (case-insensitive lookup assumed in Headers map).

Example:

contentType := sdk.GetHeader(req, "content-type")

func GetPathParam

func GetPathParam(req *HTTPRequest, key string) string

GetPathParam gets a path parameter or returns empty string.

Example:

id := sdk.GetPathParam(req, "id")

func GetQuery

func GetQuery(req *HTTPRequest, key, defaultValue string) string

GetQuery gets a query parameter with a default value.

Example:

limit := sdk.GetQuery(req, "limit", "10")

func GetRequestID

func GetRequestID(ctx context.Context) string

GetRequestID extracts the request ID from the gRPC context. Returns empty string if no request ID is present.

Example:

reqID := sdk.GetRequestID(ctx)
if reqID != "" {
    log.Info("handling request", "request_id", reqID)
}

func NewLogger

func NewLogger(name string) (hclog.Logger, *slog.Logger)

NewLogger creates a new logger pair for use in ViewRA plugins.

Returns:

  • hclog.Logger: Pass this to plugin.Serve() for go-plugin log forwarding
  • *slog.Logger: Use this in your plugin code for logging

The hclog.Logger outputs JSON to stderr, which go-plugin parses and forwards to the host. The slog.Logger wraps hclog so you can use the standard slog API.

Example:

func main() {
    hclogger, logger := sdk.NewLogger("my-plugin")

    plugin := NewMyPlugin(logger)

    go_plugin.Serve(&go_plugin.ServeConfig{
        HandshakeConfig: sdk.Handshake,
        Plugins: map[string]go_plugin.Plugin{...},
        GRPCServer: go_plugin.DefaultGRPCServer,
        Logger: hclogger,
    })
}

func NewLoggerWithLevel

func NewLoggerWithLevel(name string, level slog.Level) (hclog.Logger, *slog.Logger)

NewLoggerWithLevel creates a logger pair with a specific log level. Use this when you want to control the verbosity of plugin logs.

Example:

// Only log warnings and errors
hclogger, logger := sdk.NewLoggerWithLevel("my-plugin", slog.LevelWarn)

func ParseJSON

func ParseJSON(req *HTTPRequest, v any) error

ParseJSON parses the request body as JSON into the provided struct.

Example:

var req CreateItemRequest
if err := sdk.ParseJSON(httpReq, &req); err != nil {
    return sdk.JSONError(400, "invalid JSON")
}

func ServeEnricher

func ServeEnricher(impl EnricherPlugin, logger hclog.Logger)

ServeEnricher starts an enricher plugin server. Call this from your plugin's main() function.

If the plugin also implements TrendingProvider, it will automatically register the trending_provider service.

Example:

func main() {
    hclogger, logger := sdk.NewLogger("my-enricher")
    p := NewMyEnricher()
    p.SetLogger(logger)
    sdk.ServeEnricher(p, hclogger)
}

func ServeEnricherWithExtra

func ServeEnricherWithExtra(impl EnricherPlugin, logger hclog.Logger, extra map[string]plugin.Plugin)

ServeEnricherWithExtra starts an enricher plugin server with additional gRPC services. Use this when your plugin exposes additional interfaces beyond the standard enricher.

If the plugin also implements TrendingProvider, it will automatically register the trending_provider service.

Example (vector-search plugin):

func main() {
    hclogger, logger := sdk.NewLogger("semantic-search")
    p := NewVectorSearchPlugin()
    p.SetLogger(logger)
    sdk.ServeEnricherWithExtra(p, hclogger, map[string]plugin.Plugin{
        "vector_search": &VectorSearchGRPCPlugin{Impl: p},
    })
}

func ServeProvider

func ServeProvider(impl ProviderPlugin, logger hclog.Logger)

ServeProvider starts a provider plugin server. Call this from your plugin's main() function.

Example:

func main() {
    hclogger, logger := sdk.NewLogger("my-provider")
    p := NewMyProvider()
    p.SetLogger(logger)
    sdk.ServeProvider(p, hclogger)
}

func ServeVectorSearchEnricher

func ServeVectorSearchEnricher(impl VectorSearchEnricherPlugin, logger hclog.Logger)

ServeVectorSearchEnricher starts a plugin server for plugins that implement both EnricherPlugin (for auto-indexing in enrichment pipeline) and VectorSearchPlugin (for semantic search, similarity matching, and manual indexing).

func ServeWidget

func ServeWidget(impl WidgetPlugin, logger hclog.Logger)

ServeWidget starts a widget plugin server. Call this from your plugin's main() function.

Example:

func main() {
    hclogger, logger := sdk.NewLogger("my-widget")
    p := NewMyWidget()
    p.SetLogger(logger)
    sdk.ServeWidget(p, hclogger)
}

func WithRequestID

func WithRequestID(ctx context.Context, requestID string) context.Context

WithRequestID returns a context with the request ID in outgoing metadata. Use this when making calls to host services to propagate request tracing.

Example:

ctx = sdk.WithRequestID(ctx, requestID)
result, err := hostClient.DoSomething(ctx, req)

func WriteSSE

func WriteSSE(w HTTPStreamWriter, event string, data []byte) error

WriteSSE writes a Server-Sent Event to a stream writer. This is a convenience method for writing SSE-formatted data.

Example:

w.WriteHeader(200, "text/event-stream", nil)
sdk.WriteSSE(w, "progress", progressData)

func WriteSSEJSON

func WriteSSEJSON(w HTTPStreamWriter, event string, data any) error

WriteSSEJSON writes a Server-Sent Event with JSON data.

Example:

sdk.WriteSSEJSON(w, "progress", map[string]any{"percent": 50})

Types

type AIClient

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

AIClient provides typed access to AI capabilities. It wraps PluginsClient and handles proto marshaling internally.

func NewAIClient

func NewAIClient(plugins *PluginsClient) *AIClient

NewAIClient creates a new AI client from a PluginsClient. Returns nil if plugins is nil.

func (*AIClient) Chat

func (c *AIClient) Chat(ctx context.Context, req ChatRequest, opts ...InvokeOption) (*ChatResponse, error)

Chat sends a chat completion request. Uses the "chat" capability.

Example:

resp, err := ai.Chat(ctx, sdk.ChatRequest{
    Messages: []sdk.ChatMessage{
        {Role: "system", Content: "You are a helpful assistant."},
        {Role: "user", Content: "Hello!"},
    },
    Temperature: 0.7,
})

func (*AIClient) ChatStream

func (c *AIClient) ChatStream(ctx context.Context, req ChatRequest, opts ...InvokeOption) (<-chan ChatChunk, error)

ChatStream sends a streaming chat completion request. Returns a channel that receives response chunks until closed.

Example:

chunks, err := ai.ChatStream(ctx, sdk.ChatRequest{...})
for chunk := range chunks {
    if chunk.Done {
        break
    }
    fmt.Print(chunk.Content)
}

func (*AIClient) GenerateEmbedding

func (c *AIClient) GenerateEmbedding(ctx context.Context, text string, opts ...InvokeOption) ([]float32, error)

GenerateEmbedding generates an embedding vector for a single text. Uses the "embedding" capability.

Example:

embedding, err := ai.GenerateEmbedding(ctx, "hello world")
if err != nil {
    return err
}
fmt.Printf("Embedding dimensions: %d\n", len(embedding))

func (*AIClient) GenerateEmbeddingBatch

func (c *AIClient) GenerateEmbeddingBatch(ctx context.Context, texts []string, opts ...InvokeOption) ([][]float32, error)

GenerateEmbeddingBatch generates embeddings for multiple texts. Uses the "embedding" capability.

Example:

embeddings, err := ai.GenerateEmbeddingBatch(ctx, []string{"hello", "world"})

func (*AIClient) GenerateEmbeddingBatchWithModel

func (c *AIClient) GenerateEmbeddingBatchWithModel(ctx context.Context, texts []string, model string, opts ...InvokeOption) ([][]float32, error)

GenerateEmbeddingBatchWithModel generates embeddings using a specific model.

func (*AIClient) GenerateEmbeddingWithModel

func (c *AIClient) GenerateEmbeddingWithModel(ctx context.Context, text, model string, opts ...InvokeOption) ([]float32, error)

GenerateEmbeddingWithModel generates an embedding using a specific model.

func (*AIClient) IsChatAvailable

func (c *AIClient) IsChatAvailable(ctx context.Context) bool

IsChatAvailable checks if any chat provider is available.

func (*AIClient) IsEmbeddingAvailable

func (c *AIClient) IsEmbeddingAvailable(ctx context.Context) bool

IsEmbeddingAvailable checks if any embedding provider is available.

type Action

type Action interface {
	// contains filtered or unexported methods
}

Action represents an x-viewra-actions entry. Actions are interactive UI elements that call plugin endpoints.

type AlbumMetadata

type AlbumMetadata struct {
	Title              *string
	AlbumArtist        *string
	Artist             *string
	Year               *int
	ReleaseDate        *string
	Genre              *string
	TotalTracks        *int
	TotalDiscs         *int
	RecordLabel        *string
	ReleaseType        *string
	Compilation        *bool
	MusicbrainzAlbumID *string
	SortTitle          *string
}

AlbumMetadata contains fields specific to music albums.

type ArtistMetadata

type ArtistMetadata struct {
	Name                *string
	SortName            *string
	MusicbrainzArtistID *string
	Bio                 *string
	Country             *string
	FormedYear          *int
	Genre               *string
}

ArtistMetadata contains fields specific to music artists.

type AudioTrack

type AudioTrack struct {
	Codec         string // "aac", "ac3", "eac3", "truehd", "dts", "dts-hd", "flac"
	Channels      int    // Number of channels (2, 6, 8, etc.)
	ChannelLayout string // "stereo", "5.1", "7.1", "Atmos"
	Language      string // ISO 639-1/2 code, e.g., "en", "eng"
	IsDefault     bool
	IsCommentary  bool
}

AudioTrack represents an audio stream in a media file.

type Badge

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

Badge represents a conditional badge configuration. Badges appear on list items when a field matches a value.

func NewBadge

func NewBadge(field string, value any, label, color string) Badge

NewBadge creates a badge configuration. The badge appears when item[field] == value.

Colors: "blue", "green", "emerald", "yellow", "red", "purple", "gray"

Example:

// Show "Installed" badge when installed field is true
sdk.NewBadge("installed", true, "Installed", "emerald")

// Show "Error" badge when status is "failed"
sdk.NewBadge("status", "failed", "Error", "red")

type Base

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

Base provides common functionality for all plugins. Plugin structs must embed this type to get logging, metrics, and data directory access.

Example:

type MyPlugin struct {
    sdk.Base
    apiKey string
}

func NewMyPlugin(logger *slog.Logger) *MyPlugin {
    p := &MyPlugin{}
    p.SetLogger(logger)
    return p
}

func (*Base) DataDir

func (b *Base) DataDir() string

DataDir returns the plugin's data directory path. Plugins can store persistent data here (caches, databases, etc.). The directory is created by the host and is unique to each plugin.

Example:

func (p *MyPlugin) getCachePath() string {
    return filepath.Join(p.DataDir(), "cache.json")
}

func (*Base) Init

func (b *Base) Init(dataDir string)

Init initializes the base plugin functionality. Called automatically by the SDK during plugin initialization. Use InitWithLogger for proper go-plugin log forwarding.

func (*Base) InitWithLogger

func (b *Base) InitWithLogger(dataDir string, logger *slog.Logger)

InitWithLogger initializes the base plugin with a specific logger. Use sdk.NewLogger() to create a logger that properly forwards to the host.

Example:

func (p *MyPlugin) Initialize(ctx context.Context, dataDir string, config []byte) error {
    _, logger := sdk.NewLogger("my-plugin")
    p.InitWithLogger(dataDir, logger)
    return nil
}

func (*Base) Log

func (b *Base) Log() *slog.Logger

Log returns the plugin's logger. Use this for logging without request context.

Example:

p.Log().Info("plugin started", "version", "1.0.0")

func (*Base) LogWithContext

func (b *Base) LogWithContext(ctx context.Context) *slog.Logger

LogWithContext returns a logger with the request ID from the context. Use this when handling requests to enable request tracing.

Example:

func (p *MyPlugin) HandleRequest(ctx context.Context, req *Request) error {
    p.LogWithContext(ctx).Info("handling request", "type", req.Type)
    // ...
}

func (*Base) Metrics

func (b *Base) Metrics() PluginMetrics

Metrics returns the current plugin metrics. These metrics are reported to the host for health monitoring.

func (*Base) RecordError

func (b *Base) RecordError()

RecordError records an error for metrics. Call this when a request fails to track error rates.

Example:

if err != nil {
    p.RecordError()
    return err
}

func (*Base) RecordRequest

func (b *Base) RecordRequest(latency time.Duration)

RecordRequest records a successful request for metrics. Call this after handling each request to track performance.

Example:

func (p *MyPlugin) HandleRequest(ctx context.Context) error {
    start := time.Now()
    defer p.RecordRequest(time.Since(start))
    // ... handle request
}

func (*Base) SetLogger

func (b *Base) SetLogger(logger *slog.Logger)

SetLogger sets the logger for this plugin. Call this early in your plugin initialization.

Example:

func NewMyPlugin(logger *slog.Logger) *MyPlugin {
    p := &MyPlugin{}
    p.SetLogger(logger)
    return p
}

type Capability

type Capability struct {
	Name      string           // Capability name (e.g., "embedding")
	Providers []PluginProvider // Plugins providing this capability
}

Capability describes an available capability and its providers.

type CapabilityDescription

type CapabilityDescription struct {
	Capability        string
	APIVersion        string
	SupportedVersions []string
	Methods           []CapabilityMethod
	Providers         []PluginProvider
}

CapabilityDescription describes a capability and its available methods.

type CapabilityError

type CapabilityError struct {
	Code      pluginv1.CapabilityErrorCode
	Message   string
	Details   string
	Retryable bool
}

CapabilityError represents a structured error from a capability call.

func (*CapabilityError) Error

func (e *CapabilityError) Error() string

type CapabilityMethod

type CapabilityMethod struct {
	Name         string
	Description  string
	IsStreaming  bool
	RequestType  string
	ResponseType string
}

CapabilityMethod describes a method available on a capability.

type CastMember

type CastMember struct {
	Name  string
	Role  string
	Thumb string
	Order int
}

CastMember represents an actor in the cast.

type ChatChunk

type ChatChunk struct {
	// Content is the text in this chunk
	Content string

	// Done indicates if this is the final chunk
	Done bool

	// FinishReason is set on the final chunk
	FinishReason string
}

ChatChunk is a chunk of a streaming chat response.

type ChatMessage

type ChatMessage struct {
	// Role is "system", "user", or "assistant"
	Role string

	// Content is the message text
	Content string
}

ChatMessage represents a message in a chat conversation.

type ChatRequest

type ChatRequest struct {
	// Messages is the conversation history
	Messages []ChatMessage

	// Model is the model to use (uses provider default if empty)
	Model string

	// Temperature controls randomness (0.0-2.0)
	Temperature float32

	// MaxTokens limits the response length
	MaxTokens int
}

ChatRequest contains the parameters for a chat completion.

type ChatResponse

type ChatResponse struct {
	// Content is the generated text
	Content string

	// FinishReason indicates why generation stopped: "stop", "length", "content_filter"
	FinishReason string

	// PromptTokens is the number of tokens in the prompt
	PromptTokens int

	// CompletionTokens is the number of tokens in the response
	CompletionTokens int
}

ChatResponse is the result of a chat completion.

type ConfigurableEnricher

type ConfigurableEnricher interface {
	// GetSettingsSchema returns a JSON Schema describing the plugin's configurable settings.
	// Use Schema.Build() to generate this from your schema definition.
	GetSettingsSchema() ([]byte, error)

	// Configure applies new settings to the plugin.
	Configure(settings []byte) error

	// IsConfigured returns whether the plugin is properly configured.
	// Plugins requiring API keys should return false until the key is set.
	// The host uses this to exclude unconfigured plugins from capability resolution.
	IsConfigured() bool
}

ConfigurableEnricher is an optional interface for enrichers that support runtime configuration.

type ConfigurableProvider

type ConfigurableProvider interface {
	// GetSettingsSchema returns the JSON Schema for provider settings.
	// Use Schema.Build() to generate this from your schema definition.
	GetSettingsSchema() ([]byte, error)

	// Configure applies new settings to the provider.
	Configure(settings []byte) error

	// IsConfigured returns whether the provider is properly configured.
	// Providers requiring API keys should return false until the key is set.
	// The host uses this to exclude unconfigured providers from capability resolution.
	IsConfigured() bool
}

ConfigurableProvider is an optional interface for providers with settings UI.

type DataClient

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

DataClient wraps the HostData service for media access.

func NewDataClient

func NewDataClient(conn *grpc.ClientConn) *DataClient

NewDataClient creates a new data client.

func (*DataClient) GetLibrary

func (c *DataClient) GetLibrary(ctx context.Context, libraryID int64) (*Library, error)

GetLibrary retrieves library information by ID.

func (*DataClient) GetMedia

func (c *DataClient) GetMedia(ctx context.Context, mediaID int64, mediaType string) (*Media, error)

GetMedia retrieves a single media item by ID.

func (*DataClient) GetMediaDetails

func (c *DataClient) GetMediaDetails(ctx context.Context, mediaID int64, mediaType string) (*MediaDetails, error)

GetMediaDetails retrieves full metadata for a media item. Includes plot, cast, genres, etc. for plugin indexing.

func (*DataClient) ListMediaByDirector

func (c *DataClient) ListMediaByDirector(ctx context.Context, mediaType, directorName string, libraryID int64, excludeIDs []int64, limit int) ([]*Media, error)

ListMediaByDirector returns media items directed by a specific person. Used for director-based recommendations and themed collections.

Parameters:

  • mediaType: "movie" or "tv_show"
  • directorName: director name to match (e.g., "Christopher Nolan", "Spielberg")
  • libraryID: library to filter by (0 = all libraries)
  • excludeIDs: entity IDs to exclude from results
  • limit: maximum results (default: 20, max: 100)

Example:

items, err := data.ListMediaByDirector(ctx, "movie", "Christopher Nolan", 0, []int64{}, 20)
for _, item := range items {
    fmt.Printf("Found: %s (%d)\n", item.Title, item.Year)
}

func (*DataClient) ListMediaByGenre

func (c *DataClient) ListMediaByGenre(ctx context.Context, mediaType, genre string, libraryID int64, excludeIDs []int64, limit int) ([]*Media, error)

ListMediaByGenre returns media items matching a genre pattern. Used for genre-based recommendations when semantic search is unavailable.

Parameters:

  • mediaType: "movie" or "tv_show"
  • genre: genre pattern to match (e.g., "Action", "Comedy")
  • libraryID: library to filter by (0 = all libraries)
  • excludeIDs: entity IDs to exclude from results
  • limit: maximum results (default: 20, max: 100)

Example:

items, err := data.ListMediaByGenre(ctx, "movie", "Action", 0, []int64{1, 2}, 20)
for _, item := range items {
    fmt.Printf("Found: %s (%d)\n", item.Title, item.Year)
}

func (*DataClient) ListMediaByLibrary

func (c *DataClient) ListMediaByLibrary(ctx context.Context, libraryID int64, limit, offset int) (*MediaList, error)

ListMediaByLibrary lists all media in a library with pagination.

type DeleteItemAction

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

DeleteItemAction represents a delete action with confirmation.

func NewDeleteAction

func NewDeleteAction(id, endpoint string) *DeleteItemAction

NewDeleteAction creates a delete item action. The endpoint should support DELETE method with the item ID. Use :id in the endpoint path for the item ID placeholder.

Example:

sdk.NewDeleteAction("delete", "/models/:id")

func (*DeleteItemAction) Confirm

func (a *DeleteItemAction) Confirm(title, message string) *DeleteItemAction

Confirm sets the confirmation dialog text. When set, a confirmation modal appears before deleting.

Example:

sdk.NewDeleteAction("delete", "/models/:id").
    Confirm("Delete Model", "Are you sure you want to delete this model?")

func (*DeleteItemAction) Label

func (a *DeleteItemAction) Label(label string) *DeleteItemAction

Label sets the action button label. Default is "Delete".

func (*DeleteItemAction) ShowWhen

func (a *DeleteItemAction) ShowWhen(field string, value any) *DeleteItemAction

ShowWhen sets a condition for showing this action.

Example:

// Only show delete on installed items
sdk.NewDeleteAction("delete", "/models/:id").ShowWhen("installed", true)

type DependsOnConfig

type DependsOnConfig struct {
	// Field is the property name this depends on
	Field string `json:"field"`
	// Value is the value the field must have for this to be visible
	Value any `json:"value"`
}

DependsOnConfig configures conditional visibility for a property or section.

type Embedding

type Embedding struct {
	// EntityType identifies the type of entity (e.g., "movie", "tv_show", "track")
	EntityType string

	// EntityID is the unique identifier within the entity type
	EntityID int64

	// Vector is the embedding vector (float32)
	Vector []float32

	// Text is the original text that was embedded (optional, for debugging)
	Text string

	// Model is the model used to generate the embedding (optional, for tracking)
	Model string
}

Embedding represents a stored embedding.

type EmptyState

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

EmptyState represents the empty state configuration for a list.

func NewEmptyState

func NewEmptyState(title string) *EmptyState

NewEmptyState creates an empty state configuration. Shown when the list has no items.

Example:

sdk.NewEmptyState("No models installed")

func (*EmptyState) Description

func (e *EmptyState) Description(desc string) *EmptyState

Description sets the empty state description.

Example:

sdk.NewEmptyState("No models").Description("Pull a model to get started")

func (*EmptyState) ShowCreate

func (e *EmptyState) ShowCreate(actionID string) *EmptyState

ShowCreate sets an action ID to show a "create" button. When clicked, switches to the referenced action (e.g., a create form).

type EnrichRequest

type EnrichRequest struct {
	MediaID     int64
	MediaType   string
	FilePath    string
	Title       string
	Year        int
	ExistingIDs map[string]string

	// TV-specific
	ShowTitle     string
	SeasonNumber  int
	EpisodeNumber int

	// Music-specific
	Artist      string
	Album       string
	TrackNumber int
}

EnrichRequest contains all information needed to enrich a media item.

type EnrichResponse

type EnrichResponse struct {
	Matched         bool
	Metadata        *EnrichedMetadata
	DiscoveredIDs   map[string]string
	Images          []EnrichedImage
	Skipped         bool
	SkipReason      string
	ConfidenceScore float32 // 0.0 to 1.0
}

EnrichResponse contains the results of enrichment.

func Match

func Match() *EnrichResponse

Match creates a successful match response.

func NoMatch

func NoMatch() *EnrichResponse

NoMatch creates a response indicating no match was found.

func Skip

func Skip(reason string) *EnrichResponse

Skip creates a skipped response with the given reason.

type EnrichedImage

type EnrichedImage struct {
	Type     string // "poster", "fanart", "banner", etc.
	Path     string // Local path or remote URL
	IsRemote bool
	Language string
	Width    int
	Height   int
	Rating   float32
}

EnrichedImage represents an image discovered by an enricher.

type EnrichedMetadata

type EnrichedMetadata struct {
	Title          *string
	OriginalTitle  *string
	SortTitle      *string
	Year           *int
	Plot           *string
	Tagline        *string
	Genres         []string
	Keywords       []Keyword
	ContentRating  *string
	RuntimeMinutes *int
	Rating         *float32
	RatingVotes    *int
	Directors      []string
	Writers        []string
	Cast           []CastMember
	Studios        []string
	SimilarTitles  []string // Titles of similar/recommended content (from TMDb recommendations)

	// Music-specific fields (for tracks)
	Artist      *string
	Album       *string
	ReleaseDate *string

	// Music album metadata
	AlbumMetadata *AlbumMetadata

	// Music artist metadata
	ArtistMetadata *ArtistMetadata
}

EnrichedMetadata contains metadata fields that can be updated.

type EnricherCapabilities

type EnricherCapabilities struct {
	MediaTypes []string // "movie", "tv", "music"
	Provides   []string // "metadata", "artwork", "external_ids"
	IsLocal    bool     // Local enrichers get high concurrency
	RateLimit  int      // Requests per minute (0 = unlimited)
	Requires   []string // External IDs required (e.g., ["imdb"])
	Priority   int      // Execution priority (lower = earlier)
}

EnricherCapabilities describes what an enricher provides and requires.

type EnricherCoreGRPCPlugin

type EnricherCoreGRPCPlugin struct {
	plugin.Plugin
	Impl EnricherPlugin
	// contains filtered or unexported fields
}

EnricherCoreGRPCPlugin is the go-plugin for PluginCore service (enrichers).

func (*EnricherCoreGRPCPlugin) GRPCClient

func (p *EnricherCoreGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error)

func (*EnricherCoreGRPCPlugin) GRPCServer

func (p *EnricherCoreGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error

type EnricherGRPCPlugin

type EnricherGRPCPlugin struct {
	plugin.Plugin
	Impl EnricherPlugin
	// contains filtered or unexported fields
}

EnricherGRPCPlugin is the go-plugin for Enricher service.

func (*EnricherGRPCPlugin) GRPCClient

func (p *EnricherGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error)

func (*EnricherGRPCPlugin) GRPCServer

func (p *EnricherGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error

type EnricherPlugin

type EnricherPlugin interface {

	// GetCapabilities returns what this enricher provides and requires.
	GetCapabilities() EnricherCapabilities

	// Initialize is called when the plugin is loaded.
	// Config is the contents of config.yml passed by the host.
	// Services provides access to host services (storage, LLM, etc.) - may have nil fields.
	Initialize(ctx context.Context, dataDir string, config []byte, services *HostServices) error

	// Shutdown is called before the plugin is unloaded.
	// Use this to clean up any resources.
	Shutdown(ctx context.Context) error

	// Enrich processes a single media item.
	Enrich(ctx context.Context, req *EnrichRequest) (*EnrichResponse, error)
	// contains filtered or unexported methods
}

EnricherPlugin is the interface that enricher plugins must implement. Plugin authors implement this interface and use ServeEnricher() to run the plugin.

Plugin identity comes from plugin.yml manifest file, not code.

type EntityTypeStats

type EntityTypeStats struct {
	EntityType string // "movie", "tv_show", etc.
	Indexed    int64  // Number of indexed items
	Total      int64  // Total items in database
}

EntityTypeStats contains statistics for an entity type.

type Event

type Event struct {
	// Type is the event type, e.g., "media.added"
	Type string

	// Source identifies who emitted the event
	Source string

	// Timestamp is when the event occurred
	Timestamp time.Time

	// Payload is the JSON-encoded event data
	Payload []byte

	// CorrelationID links related events
	CorrelationID string
}

Event represents an event from the host.

func (*Event) Unmarshal

func (e *Event) Unmarshal(v any) error

Unmarshal decodes the event payload into the given struct.

Example:

var payload MediaAddedPayload
if err := event.Unmarshal(&payload); err != nil {
    return false
}

type EventHandler

type EventHandler interface {
	// GetSubscriptions returns event types this plugin wants to receive.
	GetSubscriptions() []string

	// OnEvent handles an incoming event.
	// Returns true if the event was handled.
	OnEvent(event Event) bool
}

EventHandler is an optional interface for plugins that handle events.

type FindSimilarRequest

type FindSimilarRequest struct {
	EntityType string // Type of the source entity
	EntityID   int64  // ID of the source entity
	Limit      int    // Maximum number of similar items to return
}

FindSimilarRequest contains parameters for finding similar items.

type HTTPEnricher

type HTTPEnricher interface {
	// GetRoutes returns HTTP routes this enricher exposes.
	GetRoutes() []Route

	// HandleHTTP handles a non-streaming HTTP request.
	HandleHTTP(ctx context.Context, req *HTTPRequest) (*HTTPResponse, error)
}

HTTPEnricher is an optional interface for enrichers that expose HTTP routes.

type HTTPProvider

type HTTPProvider interface {
	// GetRoutes returns HTTP routes this provider exposes.
	GetRoutes() []Route

	// HandleHTTP handles a non-streaming HTTP request.
	HandleHTTP(ctx context.Context, req *HTTPRequest) (*HTTPResponse, error)

	// HandleHTTPStream handles a streaming HTTP request (e.g., model download).
	// Return nil if not implemented.
	HandleHTTPStream(ctx context.Context, req *HTTPRequest, stream HTTPStreamWriter) error
}

HTTPProvider is an optional interface for providers that expose HTTP routes. Implement this for model management endpoints, health checks, etc.

type HTTPRequest

type HTTPRequest struct {
	// Path is the request path relative to the plugin, e.g., "/search"
	Path string

	// Method is the HTTP method: GET, POST, etc.
	Method string

	// Headers contains HTTP headers (lowercase keys)
	Headers map[string]string

	// Query contains query string parameters
	Query map[string]string

	// PathParams contains parameters extracted from :param patterns
	PathParams map[string]string

	// Body is the request body (for POST, PUT, PATCH)
	Body []byte

	// UserID is the authenticated user ID (empty if not authenticated)
	UserID string

	// IsAdmin indicates whether the user has admin role
	IsAdmin bool
}

HTTPRequest contains an incoming HTTP request to a plugin route.

type HTTPResponse

type HTTPResponse struct {
	// StatusCode is the HTTP status code
	StatusCode int

	// Headers contains response headers
	Headers map[string]string

	// Body is the response body
	Body []byte

	// ContentType is the Content-Type header (convenience, also in Headers)
	ContentType string
}

HTTPResponse is the response from a plugin route handler.

func EmptyResponse

func EmptyResponse(statusCode int) (*HTTPResponse, error)

EmptyResponse creates an empty response with just a status code. Useful for 204 No Content responses.

Example:

return sdk.EmptyResponse(204)

func JSONError

func JSONError(statusCode int, message string) (*HTTPResponse, error)

JSONError creates a JSON error response. The message is wrapped in {"error": "message"}.

Example:

return sdk.JSONError(400, "invalid request")

func JSONResponse

func JSONResponse(statusCode int, data any) (*HTTPResponse, error)

JSONResponse creates a JSON HTTP response. The data is marshaled to JSON automatically.

Example:

return sdk.JSONResponse(200, map[string]any{"items": items})

type HTTPStreamWriter

type HTTPStreamWriter interface {
	// WriteHeader sends the response status and headers.
	// Must be called before WriteChunk.
	WriteHeader(statusCode int, contentType string, headers map[string]string) error

	// WriteChunk sends a chunk of response data.
	WriteChunk(data []byte) error
}

HTTPStreamWriter is used for streaming HTTP responses.

type HostDataGRPCPlugin

type HostDataGRPCPlugin struct{ plugin.Plugin }

func (*HostDataGRPCPlugin) GRPCClient

func (p *HostDataGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error)

func (*HostDataGRPCPlugin) GRPCServer

func (p *HostDataGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error

type HostPluginsGRPCPlugin

type HostPluginsGRPCPlugin struct{ plugin.Plugin }

func (*HostPluginsGRPCPlugin) GRPCClient

func (p *HostPluginsGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error)

func (*HostPluginsGRPCPlugin) GRPCServer

func (p *HostPluginsGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error

type HostServices

type HostServices struct {
	// Storage provides key-value, SQL, and vector storage for plugins
	Storage *StorageClient

	// Data provides access to media database
	Data *DataClient

	// Weather provides weather/location data
	Weather *WeatherClient

	// Plugins provides capability-based access to other plugins
	Plugins *PluginsClient

	// Ratings provides read-only access to user ratings (favorites, likes, dislikes)
	Ratings *RatingsClient

	// Progress provides read-only access to user watch history
	Progress *ProgressClient
}

HostServices provides access to host-provided services. Fields may be nil if the service is not available.

For AI capabilities (embeddings, chat), use the Plugins client with capability-based discovery. Call Plugins.GetConnection("embeddings") or Plugins.GetConnection("chat") to get a connection to a provider plugin.

type HostStorageGRPCPlugin

type HostStorageGRPCPlugin struct{ plugin.Plugin }

func (*HostStorageGRPCPlugin) GRPCClient

func (p *HostStorageGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error)

func (*HostStorageGRPCPlugin) GRPCServer

func (p *HostStorageGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error

type HostWeatherGRPCPlugin

type HostWeatherGRPCPlugin struct{ plugin.Plugin }

func (*HostWeatherGRPCPlugin) GRPCClient

func (p *HostWeatherGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error)

func (*HostWeatherGRPCPlugin) GRPCServer

func (p *HostWeatherGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error

type IndexLibraryRequest

type IndexLibraryRequest struct {
	LibraryID   int64  // Library database ID
	LibraryType string // "movie", "tv", "music"
}

IndexLibraryRequest contains parameters for indexing a library.

type IndexLibraryResponse

type IndexLibraryResponse struct {
	Started bool   // Whether indexing was started
	Message string // Status message
}

IndexLibraryResponse contains the result of an indexing trigger.

type IndexingProgress

type IndexingProgress struct {
	EntityType  string // Entity type being indexed
	Total       int64  // Total items to process
	Processed   int64  // Items processed so far
	Failed      int64  // Items that failed
	LastError   string // Most recent error message
	StartedAt   int64  // Unix timestamp when started
	LastUpdated int64  // Unix timestamp of last update
}

IndexingProgress tracks indexing operation progress.

type InvokeMetadata

type InvokeMetadata struct {
	ProviderPlugin string            // Which plugin handled the request
	LatencyMs      int64             // How long the provider took
	Metadata       map[string]string // Response metadata from provider
}

InvokeMetadata contains metadata about an invoke call.

type InvokeOption

type InvokeOption func(*invokeOptions)

InvokeOption configures an Invoke call.

func WithAPIVersion

func WithAPIVersion(version string) InvokeOption

WithAPIVersion sets the API version for this invoke.

func WithMetadata

func WithMetadata(meta map[string]string) InvokeOption

WithMetadata sets request metadata for this invoke.

func WithPreferredPlugin

func WithPreferredPlugin(pluginID string) InvokeOption

WithPreferredPlugin sets the preferred provider plugin for this invoke.

type ItemAction

type ItemAction interface {
	// contains filtered or unexported methods
}

ItemAction represents an action button on a list item.

type Keyword

type Keyword struct {
	ID         int
	Name       string
	IsLocation bool
}

Keyword represents a keyword/tag for a media item.

type Library

type Library struct {
	ID        int64
	Name      string
	Path      string
	MediaType string // "movie", "tv", "music"
}

Library represents a media library.

type LibraryScanCompletedPayload

type LibraryScanCompletedPayload struct {
	LibraryID   int64 `json:"library_id"`
	ItemsAdded  int   `json:"items_added"`
	ItemsUpdate int   `json:"items_updated"`
	ItemsRemove int   `json:"items_removed"`
	DurationMs  int64 `json:"duration_ms"`
}

LibraryScanCompletedPayload is the payload for library.scan.completed events.

type LibraryScanStartedPayload

type LibraryScanStartedPayload struct {
	LibraryID int64  `json:"library_id"`
	ScanType  string `json:"scan_type"` // "full" or "incremental"
}

LibraryScanStartedPayload is the payload for library.scan.started events.

type ListActionDef

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

ListActionDef defines a list action that displays items from an endpoint. Use this for managing collections like models, sources, or configurations.

func ListAction

func ListAction(id, endpoint string) *ListActionDef

ListAction creates a list action builder. The endpoint should return a JSON response with:

{"items": [{"id": "1", "name": "Item 1", ...}, ...]}

Example:

sdk.ListAction("model-list", "/models")

func (*ListActionDef) DependsOn

func (a *ListActionDef) DependsOn(field string, value any) *ListActionDef

DependsOn sets conditional visibility - this action/tab only shows when the specified field has the specified value.

Example:

sdk.ListAction("embedding-models", "/models").
    TabTitle("Ollama Embedding Models").
    DependsOn("embedding_provider", "ai-local")

func (*ListActionDef) Display

func (a *ListActionDef) Display(display *ListDisplay) *ListActionDef

Display sets how list items are rendered. Use NewListDisplay to configure field mapping.

Example:

sdk.ListAction("models", "/models").
    Display(sdk.NewListDisplay("name").SecondaryField("description"))

func (*ListActionDef) EmptyState

func (a *ListActionDef) EmptyState(state *EmptyState) *ListActionDef

EmptyState sets what to show when the list is empty.

Example:

sdk.ListAction("models", "/models").
    EmptyState(sdk.NewEmptyState("No models installed").
        Description("Pull a model to get started"))

func (*ListActionDef) ItemAction

func (a *ListActionDef) ItemAction(action ItemAction) *ListActionDef

ItemAction adds an action that appears on each list item. Common item actions: delete, download, configure.

Example:

sdk.ListAction("models", "/models").
    ItemAction(sdk.NewDeleteAction("delete", "/models/:id"))

func (*ListActionDef) Params

func (a *ListActionDef) Params(params map[string]string) *ListActionDef

Params sets query parameters to include when calling the endpoint. Use this to filter list results.

Example:

sdk.ListAction("embedding-models", "/models").
    Params(map[string]string{"type": "embedding"})

func (*ListActionDef) ShowSystemInfo

func (a *ListActionDef) ShowSystemInfo() *ListActionDef

ShowSystemInfo enables display of system resource information. When enabled, shows available RAM/VRAM above the list. Useful for model lists where resource requirements matter.

The endpoint response should include:

{"items": [...], "systemInfo": {"ramBytes": 16000000000, "vramBytes": 8000000000, "hasGpu": true}}

func (*ListActionDef) TabTitle

func (a *ListActionDef) TabTitle(title string) *ListActionDef

TabTitle sets the tab title. When set, this action appears as a tab in the settings form instead of inline.

Example:

sdk.ListAction("models", "/models").TabTitle("Models")

func (*ListActionDef) Title

func (a *ListActionDef) Title(title string) *ListActionDef

Title sets the list header title.

Example:

sdk.ListAction("models", "/models").Title("Available Models")

type ListDisplay

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

ListDisplay configures how list items are rendered.

func NewListDisplay

func NewListDisplay(primaryField string) *ListDisplay

NewListDisplay creates a list display configuration. The primary field is required and shown as the main text for each item.

Example:

sdk.NewListDisplay("name").SecondaryField("description")

func (*ListDisplay) Badge

func (d *ListDisplay) Badge(badge Badge) *ListDisplay

Badge adds a conditional badge to list items. Badges are colored labels that appear when a condition is met.

Example:

sdk.NewListDisplay("name").
    Badge(sdk.NewBadge("installed", true, "Installed", "emerald")).
    Badge(sdk.NewBadge("canRun", false, "Too Large", "red"))

func (*ListDisplay) Metadata

func (d *ListDisplay) Metadata(fields ...string) *ListDisplay

Metadata sets additional fields to show as small metadata items.

Example:

sdk.NewListDisplay("name").Metadata("size", "created")

func (*ListDisplay) SecondaryField

func (d *ListDisplay) SecondaryField(field string) *ListDisplay

SecondaryField sets the secondary display field. Shown below the primary field in smaller text.

Example:

sdk.NewListDisplay("name").SecondaryField("description")

type Media

type Media struct {
	ID          int64
	MediaType   string
	Title       string
	Year        int
	FilePath    string
	LibraryID   int64
	ExternalIDs map[string]string
}

Media represents a media item.

type MediaAddedPayload

type MediaAddedPayload struct {
	MediaID   int64  `json:"media_id"`
	MediaType string `json:"media_type"`
	Title     string `json:"title"`
	Year      int    `json:"year"`
	LibraryID int64  `json:"library_id"`
}

MediaAddedPayload is the payload for media.added events.

type MediaDeletedPayload

type MediaDeletedPayload struct {
	MediaID   int64  `json:"media_id"`
	MediaType string `json:"media_type"`
}

MediaDeletedPayload is the payload for media.deleted events.

type MediaDetails

type MediaDetails struct {
	ID          int64
	MediaType   string
	Title       string
	Year        int
	LibraryID   int64
	ExternalIDs map[string]string

	// Rich metadata
	Plot             string
	Tagline          string
	Genres           []string
	Directors        []string
	Writers          []string
	Cast             []CastMember
	Studios          []string
	ContentRating    string
	RuntimeMinutes   int
	OriginalLanguage string
	CountryOfOrigin  string
	Producers        []string
	LocationKeywords []string
	ThemeKeywords    []string
	Composers        []string // Music composers
	Cinematographers []string // Directors of Photography
	SimilarTitles    []string // Similar/recommended titles from TMDb

	// Playback information for filtering by technical specs
	PlaybackInfo *PlaybackInfo

	// TV-specific
	ShowTitle     string
	SeasonNumber  int
	EpisodeNumber int

	// Music-specific
	ArtistName  string
	AlbumTitle  string
	Biography   string
	Country     string
	ReleaseType string
}

MediaDetails contains full metadata for plugin indexing.

type MediaItem

type MediaItem struct {
	// EntityType is the media type: "movie", "tv_show", "tv_episode", etc.
	EntityType string `json:"entity_type"`

	// EntityID is the database ID of the media item.
	EntityID int64 `json:"entity_id"`

	// Title is the display title.
	Title string `json:"title"`

	// Year is the release/air year.
	Year int `json:"year,omitempty"`

	// Poster is the URL to the poster image.
	Poster string `json:"poster,omitempty"`

	// Backdrop is the URL to the backdrop image.
	Backdrop string `json:"backdrop,omitempty"`

	// Reason is an optional recommendation reason.
	// Example: "Similar to Breaking Bad"
	Reason string `json:"reason,omitempty"`

	// Progress is optional playback progress (for continue watching).
	Progress *MediaProgress `json:"progress,omitempty"`

	// Rating is the user's rating for this item (if any).
	// Values: "up", "down", "favorite", or nil if not rated.
	Rating *string `json:"rating,omitempty"`
}

MediaItem represents a media item in widget data. Used in WidgetTypeMediaRow, WidgetTypeFeaturedRow, etc.

type MediaList

type MediaList struct {
	Items   []*MediaDetails
	Total   int
	HasMore bool
}

MediaList is a paginated list of media.

type MediaProgress

type MediaProgress struct {
	// Percent is the progress percentage (0-100).
	Percent int `json:"percent"`

	// PositionSeconds is the playback position in seconds.
	PositionSeconds int `json:"position_seconds"`

	// DurationSeconds is the total duration in seconds.
	DurationSeconds int `json:"duration_seconds"`
}

MediaProgress represents playback progress for a media item.

type MediaUpdatedPayload

type MediaUpdatedPayload struct {
	MediaID      int64    `json:"media_id"`
	MediaType    string   `json:"media_type"`
	UpdatedField []string `json:"updated_fields"`
}

MediaUpdatedPayload is the payload for media.updated events.

type Migration

type Migration struct {
	// Version is the migration version number. Must be unique and positive.
	// Migrations are applied in version order.
	Version int

	// SQL is the DDL statement(s) to execute for this migration.
	// Multiple statements can be separated by semicolons.
	SQL string
}

Migration represents a schema migration.

type MoodTag

type MoodTag struct {
	Tag        string
	Confidence float32
}

MoodTag represents a generated mood tag.

type PlaybackInfo

type PlaybackInfo struct {
	// Video specs
	Width           int    // Video width in pixels
	Height          int    // Video height in pixels
	ResolutionLabel string // "SD", "720p", "1080p", "4K", "8K"
	HDRFormat       string // "SDR", "HDR10", "HDR10+", "Dolby Vision", "HLG"
	VideoCodec      string // "h264", "hevc", "av1", etc.
	Bitrate         int64  // Video bitrate in bits/second

	// Audio and subtitle tracks
	AudioTracks    []AudioTrack
	SubtitleTracks []SubtitleTrack
}

PlaybackInfo contains technical playback metadata for filtering. Used for queries like "4K movies", "Dolby Vision content", "movies with subtitles".

type PlaybackStartedPayload

type PlaybackStartedPayload struct {
	SessionID string `json:"session_id"`
	UserID    string `json:"user_id"`
	MediaID   int64  `json:"media_id"`
	MediaType string `json:"media_type"`
}

PlaybackStartedPayload is the payload for playback.started events.

type PlaybackStoppedPayload

type PlaybackStoppedPayload struct {
	SessionID    string  `json:"session_id"`
	UserID       string  `json:"user_id"`
	MediaID      int64   `json:"media_id"`
	PositionSecs float64 `json:"position_secs"`
	DurationSecs float64 `json:"duration_secs"`
}

PlaybackStoppedPayload is the payload for playback.stopped events.

type PluginMeta

type PluginMeta struct {
	// DisplayName is the human-readable name shown in the UI.
	// Example: "OpenAI", "Ollama", "TMDB"
	DisplayName string `json:"displayName"`

	// ProviderName is the name shown in provider selection dropdowns.
	// If not set, DisplayName is used. Use this when the plugin name
	// differs from how it should appear as a provider option.
	// Example: DisplayName="AI Features", ProviderName="Ollama"
	ProviderName string `json:"providerName,omitempty"`

	// Description is a short description of what the plugin does.
	// Example: "OpenAI API for embeddings and chat"
	Description string `json:"description"`

	// Tip is optional help text shown as a tooltip.
	// Example: "Requires API key. Usage is billed per token."
	Tip string `json:"tip,omitempty"`

	// IsLocal indicates if this runs locally (no external API calls).
	// Local plugins show a "Local" badge in the UI.
	IsLocal bool `json:"isLocal,omitempty"`

	// Icon is the icon name to display. Common options:
	// "cloud", "hard-drive", "brain", "compass", "star", "settings"
	Icon string `json:"icon,omitempty"`
}

PluginMeta contains metadata for the plugin UI display. This appears in the settings page header when the plugin is selected.

type PluginMetrics

type PluginMetrics struct {
	// RequestsTotal is the total number of requests handled.
	RequestsTotal int64

	// ErrorsTotal is the total number of errors encountered.
	ErrorsTotal int64

	// AvgLatency is the average request latency.
	AvgLatency time.Duration
}

PluginMetrics contains plugin performance metrics. These are reported to the host for health monitoring and displayed in the UI.

type PluginProvider

type PluginProvider struct {
	ID          string // Plugin ID (e.g., "provider-ollama")
	Name        string // Human-readable name (e.g., "Ollama Provider")
	Enabled     bool   // Whether the plugin is enabled
	Configured  bool   // Whether the plugin is properly configured
	IsPreferred bool   // Whether this is the preferred provider
}

PluginProvider describes a plugin that provides a capability.

type PluginRefConfig

type PluginRefConfig struct {
	// Capability is the capability to filter plugins by (e.g., "embedding", "chat").
	// Only plugins providing this capability appear in the dropdown.
	Capability string

	// SettingsKey is the key where the referenced plugin's settings are stored.
	// If empty, defaults to "{capability}_provider_settings".
	SettingsKey string
}

PluginRefConfig configures a plugin reference property. Used to embed another plugin's settings inline within this plugin's form.

type PluginsClient

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

PluginsClient provides capability-based access to other plugins.

func NewPluginsClient

func NewPluginsClient(conn *grpc.ClientConn) *PluginsClient

NewPluginsClient creates a new plugins client for capability discovery and invocation.

func (*PluginsClient) ClearCapabilityPreference

func (c *PluginsClient) ClearCapabilityPreference(ctx context.Context, capability string) error

ClearCapabilityPreference removes the preference for a capability. After clearing, Invoke falls back to the first available provider.

Example:

err := plugins.ClearCapabilityPreference(ctx, "embedding")

func (*PluginsClient) Describe

func (c *PluginsClient) Describe(ctx context.Context, capability string) (*CapabilityDescription, error)

Describe returns metadata about a capability's available methods.

Example:

desc, err := plugins.Describe(ctx, "embedding")
for _, method := range desc.Methods {
    fmt.Printf("Method: %s (streaming: %v)\n", method.Name, method.IsStreaming)
}

func (*PluginsClient) FindSimilar

func (c *PluginsClient) FindSimilar(ctx context.Context, entityType string, entityID int64, limit int, opts ...InvokeOption) ([]SemanticSearchResult, string, error)

FindSimilar finds items similar to a given entity using vector search. Returns results ordered by similarity (highest first).

Example:

results, provider, err := plugins.FindSimilar(ctx, "movie", movieID, 10)
for _, r := range results {
    fmt.Printf("Similar %s %d (score: %.2f)\n", r.EntityType, r.EntityID, r.Similarity)
}

func (*PluginsClient) GetCapabilityPreferences

func (c *PluginsClient) GetCapabilityPreferences(ctx context.Context) (map[string]string, error)

GetCapabilityPreferences returns all configured capability preferences. Returns a map of capability name to preferred plugin ID.

Example:

prefs, err := plugins.GetCapabilityPreferences(ctx)
if embeddingPlugin, ok := prefs["embedding"]; ok {
    fmt.Printf("Embedding provider: %s\n", embeddingPlugin)
}

func (*PluginsClient) Invoke

func (c *PluginsClient) Invoke(ctx context.Context, capability, method string, req proto.Message, opts ...InvokeOption) ([]byte, *InvokeMetadata, error)

Invoke calls a method on a capability provider and returns the response payload. The caller is responsible for marshaling the request and unmarshaling the response.

Example:

req := &pluginv1.ProviderEmbeddingRequest{Text: "hello"}
respBytes, meta, err := plugins.Invoke(ctx, "embedding", "GenerateEmbedding", req)
if err != nil {
    return err
}
var resp pluginv1.ProviderEmbeddingResponse
proto.Unmarshal(respBytes, &resp)

func (*PluginsClient) InvokeStream

func (c *PluginsClient) InvokeStream(ctx context.Context, capability, method string, req proto.Message, opts ...InvokeOption) (<-chan *StreamChunk, error)

InvokeStream calls a streaming method on a capability provider. Returns a channel that receives response chunks until closed.

Example:

req := &pluginv1.ProviderChatRequest{...}
chunks, err := plugins.InvokeStream(ctx, "chat", "ChatStream", req)
if err != nil {
    return err
}
for chunk := range chunks {
    if chunk.Error != nil {
        return chunk.Error
    }
    var resp pluginv1.ProviderChatStreamChunk
    proto.Unmarshal(chunk.Payload, &resp)
    // Process chunk
}

func (*PluginsClient) IsAvailable

func (c *PluginsClient) IsAvailable(ctx context.Context, capability string) bool

IsAvailable checks if any plugin provides the specified capability.

Example:

if plugins.IsAvailable(ctx, "embedding") {
    // Embedding capability is available
}

func (*PluginsClient) IsVectorSearchAvailable

func (c *PluginsClient) IsVectorSearchAvailable(ctx context.Context) bool

IsVectorSearchAvailable returns true if any plugin provides vector_search capability. Use this to check before calling FindSimilar or SemanticSearch.

Example:

if plugins.IsVectorSearchAvailable(ctx) {
    results, err := plugins.FindSimilar(ctx, "movie", 123, 10)
}

func (*PluginsClient) ListCapabilities

func (c *PluginsClient) ListCapabilities(ctx context.Context) ([]Capability, error)

ListCapabilities returns all available capabilities and their providers. Useful for discovering what capabilities are available in the system.

Example:

caps, err := plugins.ListCapabilities(ctx)
for _, cap := range caps {
    fmt.Printf("Capability: %s, Providers: %d\n", cap.Name, len(cap.Providers))
}

func (*PluginsClient) ListProviders

func (c *PluginsClient) ListProviders(ctx context.Context, capability string) ([]PluginProvider, error)

ListProviders returns all plugins providing a specific capability. Includes both enabled and disabled plugins for UI display purposes.

Example:

providers, err := plugins.ListProviders(ctx, "embedding")
for _, p := range providers {
    fmt.Printf("Provider: %s (enabled: %v)\n", p.Name, p.Enabled)
}

func (*PluginsClient) SemanticSearch

func (c *PluginsClient) SemanticSearch(ctx context.Context, query string, entityTypes []string, limit int, userID string, opts ...InvokeOption) ([]SemanticSearchResult, string, error)

SemanticSearch performs semantic search across indexed media. Returns results ordered by relevance (highest first).

Example:

results, provider, err := plugins.SemanticSearch(ctx, "action movies with space battles", []string{"movie"}, 10, "user-123")
for _, r := range results {
    fmt.Printf("Found %s %d (score: %.2f)\n", r.EntityType, r.EntityID, r.Similarity)
}

func (*PluginsClient) SetCapabilityPreference

func (c *PluginsClient) SetCapabilityPreference(ctx context.Context, capability, pluginID string) error

SetCapabilityPreference sets the preferred plugin for a capability. Used by configuration plugins (e.g., ai-local) to route capabilities to specific providers. When other plugins invoke this capability, the preferred plugin will be used.

Example:

// Route all embedding requests to OpenAI
err := plugins.SetCapabilityPreference(ctx, "embedding", "provider-openai")

type PluginsClientAware

type PluginsClientAware interface {
	// SetPluginsClient receives the PluginsClient for capability broker access.
	// Called after Initialize() if the host provides the HostPlugins service.
	SetPluginsClient(client *PluginsClient)
}

PluginsClientAware is an optional interface for providers that need access to the capability broker for discovering and routing to other plugins. Implement this interface to receive a PluginsClient during initialization.

Example use case: The ai-local plugin uses this to set capability preferences when configured, routing embedding/chat requests to the selected provider.

type ProgressClient

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

ProgressClient wraps the HostProgress service for reading user watch history. Plugins use this to access watch progress for collaborative filtering and personalized recommendations.

func NewProgressClient

func NewProgressClient(conn *grpc.ClientConn) *ProgressClient

NewProgressClient creates a new progress client.

func (*ProgressClient) GetWatchedEntityIDs

func (c *ProgressClient) GetWatchedEntityIDs(ctx context.Context, userID, mediaType string, limit int) ([]int64, error)

GetWatchedEntityIDs returns just the entity IDs of watched items. More efficient than ListWatchedItems when only IDs are needed. Optional filter: mediaType (empty for all types).

func (*ProgressClient) HasWatchHistory

func (c *ProgressClient) HasWatchHistory(ctx context.Context, userID string) (bool, error)

HasWatchHistory returns whether a user has any watch history.

func (*ProgressClient) ListInProgressItems

func (c *ProgressClient) ListInProgressItems(ctx context.Context, userID, mediaType string, limit, offset int) ([]*WatchProgress, error)

ListInProgressItems returns items the user is currently watching (partial progress). Optional filter: mediaType (empty for all types).

func (*ProgressClient) ListWatchedItems

func (c *ProgressClient) ListWatchedItems(ctx context.Context, userID, mediaType string, limit, offset int) ([]*WatchProgress, error)

ListWatchedItems returns all watched items for a user. Optional filter: mediaType (empty for all types).

type Property

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

Property represents a JSON Schema property (a settings field). Use String(), Number(), Boolean(), Integer(), or Array() to create one.

func Array

func Array() *Property

Array creates an array property builder. Array properties can be used for multi-select with ItemsEnum().

Example:

sdk.Array().Title("Image Types").
    ItemsEnum("poster", "backdrop", "logo").
    Default([]string{"poster", "backdrop"})

func Boolean

func Boolean() *Property

Boolean creates a boolean property builder. Boolean properties render as toggle switches.

Example:

sdk.Boolean().Title("Enable Feature").Default(true)

func Integer

func Integer() *Property

Integer creates an integer property builder. Integer properties render as numeric inputs (whole numbers only).

Example:

sdk.Integer().Title("Max Tokens").Default(1000)

func Number

func Number() *Property

Number creates a number property builder. Number properties render as numeric inputs (allows decimals).

Example:

sdk.Number().Title("Temperature").Default(0.7)

func PluginRef

func PluginRef(capability string) *Property

PluginRef creates a plugin reference property builder. This renders as a dropdown to select a provider plugin, with the selected plugin's settings shown inline below the dropdown.

Use this in configuration plugins (like ai-local) to let users select and configure provider plugins for specific capabilities.

The property value is the selected plugin ID (string). The referenced plugin's settings are stored separately under the settingsKey.

Example:

// In ai-local schema:
sdk.PluginRef("embedding").
    Title("Embedding Provider").
    Description("Select which plugin provides embeddings")

This generates a UI with:

  • A dropdown listing all plugins providing the "embedding" capability
  • When a plugin is selected, its settings form appears inline
  • The selected plugin's settings are saved under "embedding_provider_settings"

func String

func String() *Property

String creates a string property builder. String properties render as text inputs.

Example:

sdk.String().Title("API Key").Format("password").Required()

func (*Property) Default

func (p *Property) Default(val any) *Property

Default sets the default value for the property. This value is used when no value has been saved.

Example:

sdk.String().Title("Model").Default("gpt-4o-mini")

func (*Property) DependsOn

func (p *Property) DependsOn(field string, value any) *Property

DependsOn sets conditional visibility - this property only shows when the specified field has the specified value.

Example:

sdk.String().Title("API Key").DependsOn("enabled", true)

func (*Property) Description

func (p *Property) Description(desc string) *Property

Description sets the property description shown as help text.

Example:

sdk.String().Title("API Key").Description("Get your key from the dashboard")

func (*Property) Enum

func (p *Property) Enum(values ...any) *Property

Enum sets the allowed values for the property. When set, the field renders as a dropdown select.

Example:

sdk.String().Title("Model").Enum("gpt-4", "gpt-3.5-turbo")

func (*Property) EnumStrings

func (p *Property) EnumStrings(values ...string) *Property

EnumStrings sets the allowed string values for the property. Convenience method for string enums.

Example:

sdk.String().Title("Model").EnumStrings("gpt-4", "gpt-3.5-turbo")

func (*Property) Format

func (p *Property) Format(format string) *Property

Format sets the property format for special rendering. Common formats:

  • "password": Renders as masked input
  • "uri": Validates as URL
  • "email": Validates as email

Example:

sdk.String().Title("API Key").Format("password")

func (*Property) ItemsEnum

func (p *Property) ItemsEnum(values ...string) *Property

ItemsEnum sets the allowed values for array items (multi-select). When used with Array(), renders as a multi-select dropdown.

Example:

sdk.Array().Title("Image Types").
    ItemsEnum("poster", "backdrop", "logo").
    Default([]string{"poster"})

func (*Property) Max

func (p *Property) Max(val float64) *Property

Max sets the maximum value for number/integer properties.

Example:

sdk.Number().Title("Similarity").Min(0.0).Max(1.0)

func (*Property) Min

func (p *Property) Min(val float64) *Property

Min sets the minimum value for number/integer properties.

Example:

sdk.Integer().Title("Batch Size").Min(10).Max(200)

func (*Property) Required

func (p *Property) Required() *Property

Required marks this property as required. Required fields must have a value before saving.

Example:

sdk.String().Title("API Key").Required()

func (*Property) SettingsKey

func (p *Property) SettingsKey(key string) *Property

SettingsKey sets the key where the referenced plugin's settings are stored. Default is "{capability}_provider_settings".

Example:

sdk.PluginRef("embedding").SettingsKey("custom_embedding_config")

func (*Property) Title

func (p *Property) Title(title string) *Property

Title sets the property title shown as the field label.

Example:

sdk.String().Title("API Key")

type PropertyType

type PropertyType string

PropertyType represents JSON Schema property types.

const (
	TypeString  PropertyType = "string"
	TypeNumber  PropertyType = "number"
	TypeInteger PropertyType = "integer"
	TypeBoolean PropertyType = "boolean"
	TypeArray   PropertyType = "array"
	TypeObject  PropertyType = "object"
)

type ProviderCapabilities

type ProviderCapabilities struct {
	// ProviderID is a unique identifier, e.g., "ollama", "openai", "anthropic"
	ProviderID string

	// DisplayName is the human-readable name, e.g., "Ollama (Local)"
	DisplayName string

	// Description describes the provider
	Description string

	// SupportsChat indicates if this provider supports chat completions
	SupportsChat bool

	// SupportsEmbedding indicates if this provider supports embedding generation
	SupportsEmbedding bool

	// SupportsStreaming indicates if this provider supports streaming responses
	SupportsStreaming bool

	// RequiresAPIKey indicates if this provider requires an API key
	RequiresAPIKey bool

	// RequiresURL indicates if this provider requires a custom URL (e.g., Ollama)
	RequiresURL bool

	// IsLocal indicates if this provider runs locally (affects UI display)
	IsLocal bool

	// DefaultChatModel is the default model ID for chat
	DefaultChatModel string

	// DefaultEmbeddingModel is the default model ID for embeddings
	DefaultEmbeddingModel string
}

ProviderCapabilities describes what an AI provider supports.

type ProviderCoreGRPCPlugin

type ProviderCoreGRPCPlugin struct {
	plugin.Plugin
	Impl ProviderPlugin
	// contains filtered or unexported fields
}

ProviderCoreGRPCPlugin is the go-plugin for PluginCore service.

func (*ProviderCoreGRPCPlugin) GRPCClient

func (p *ProviderCoreGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error)

func (*ProviderCoreGRPCPlugin) GRPCServer

func (p *ProviderCoreGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error

type ProviderHealth

type ProviderHealth struct {
	// Healthy indicates if the provider is ready to serve requests
	Healthy bool

	// Message is a human-readable status message
	Message string

	// Latency is the health check latency
	Latency time.Duration

	// Error is the error message if unhealthy
	Error string
}

ProviderHealth indicates the health of a provider.

type ProviderModel

type ProviderModel struct {
	// ID is the model identifier, e.g., "llama3.2", "gpt-4o-mini"
	ID string

	// Name is the human-readable name
	Name string

	// Description describes the model
	Description string

	// IsChat indicates if this is a chat/completion model
	IsChat bool

	// IsEmbedding indicates if this is an embedding model
	IsEmbedding bool

	// ContextLength is the context window size (for chat models)
	ContextLength int

	// EmbeddingDimensions is the embedding vector size (for embedding models)
	EmbeddingDimensions int

	// Size is the model size for display, e.g., "7B", "3.8GB"
	Size string

	// Recommended indicates if this model is recommended
	Recommended bool
}

ProviderModel describes a model available from a provider.

type ProviderPlugin

type ProviderPlugin interface {

	// GetProviderCapabilities returns what this provider supports.
	GetProviderCapabilities() ProviderCapabilities

	// Initialize is called when the plugin is loaded.
	// Config contains the contents of config.yml passed by the host.
	Initialize(ctx context.Context, dataDir string, config []byte, systemInfo *SystemInfo) error

	// Shutdown is called before the plugin is unloaded.
	Shutdown(ctx context.Context) error

	// HealthCheck verifies the provider is working (API key valid, service reachable).
	HealthCheck(ctx context.Context) (*ProviderHealth, error)

	// ListModels returns available models from this provider.
	ListModels(ctx context.Context) ([]ProviderModel, error)

	// GenerateEmbedding generates an embedding for a single text.
	GenerateEmbedding(ctx context.Context, text, model string) ([]float32, error)

	// GenerateEmbeddingBatch generates embeddings for multiple texts.
	GenerateEmbeddingBatch(ctx context.Context, texts []string, model string) ([][]float32, error)

	// Chat sends a chat completion request.
	Chat(ctx context.Context, req *ChatRequest) (*ChatResponse, error)

	// ChatStream sends a streaming chat completion request.
	// Implementations should send chunks to the provided channel and close it when done.
	ChatStream(ctx context.Context, req *ChatRequest, chunks chan<- ChatChunk) error
	// contains filtered or unexported methods
}

ProviderPlugin is the interface that AI provider plugins must implement. Providers offer LLM capabilities (embeddings, chat) to other plugins via the host.

Plugin identity comes from the plugin.yml manifest file.

type ProviderServiceGRPCPlugin

type ProviderServiceGRPCPlugin struct {
	plugin.Plugin
	Impl ProviderPlugin
	// contains filtered or unexported fields
}

ProviderServiceGRPCPlugin is the go-plugin for PluginProvider service.

func (*ProviderServiceGRPCPlugin) GRPCClient

func (p *ProviderServiceGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error)

func (*ProviderServiceGRPCPlugin) GRPCServer

func (p *ProviderServiceGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error

type RateLimit

type RateLimit struct {
	// RequestsPerMinute is the maximum requests per minute (0 = no limit)
	RequestsPerMinute int

	// PerUser limits per-user if true, otherwise global for this route
	PerUser bool
}

RateLimit configures rate limiting for a route.

type Rating

type Rating struct {
	ID         int64
	UserID     string
	EntityType string // "movie", "tv_show", "tv_episode"
	EntityID   int64
	Rating     string // "favorite", "up", "down"
	CreatedAt  time.Time
	UpdatedAt  time.Time
}

Rating represents a user's rating for a media item.

type RatingsClient

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

RatingsClient wraps the HostRatings service for reading user ratings. Plugins use this to access user preferences (favorites, likes, dislikes) for generating personalized recommendations.

func NewRatingsClient

func NewRatingsClient(conn *grpc.ClientConn) *RatingsClient

NewRatingsClient creates a new ratings client.

func (*RatingsClient) GetDownvotedIDs

func (c *RatingsClient) GetDownvotedIDs(ctx context.Context, userID, entityType string, limit int) ([]int64, error)

GetDownvotedIDs returns entity IDs that the user has downvoted (disliked).

func (*RatingsClient) GetFavoriteIDs

func (c *RatingsClient) GetFavoriteIDs(ctx context.Context, userID, entityType string, limit int) ([]int64, error)

GetFavoriteIDs returns entity IDs that the user has marked as favorite.

func (*RatingsClient) GetPositivelyRatedIDs

func (c *RatingsClient) GetPositivelyRatedIDs(ctx context.Context, userID, entityType string, limit int) ([]int64, error)

GetPositivelyRatedIDs returns entity IDs that the user has rated positively (favorite or up). This is useful for recommendations that treat both ratings as positive signals.

func (*RatingsClient) GetUpvotedIDs

func (c *RatingsClient) GetUpvotedIDs(ctx context.Context, userID, entityType string, limit int) ([]int64, error)

GetUpvotedIDs returns entity IDs that the user has upvoted (liked).

func (*RatingsClient) HasRatings

func (c *RatingsClient) HasRatings(ctx context.Context, userID string) (bool, error)

HasRatings returns whether a user has any ratings.

func (*RatingsClient) ListRatings

func (c *RatingsClient) ListRatings(ctx context.Context, userID, entityType, ratingType string) ([]*Rating, error)

ListRatings returns all ratings for a user. Optional filters: entityType (empty for all), ratingType (empty for all).

type Route

type Route struct {
	// Path is relative to the plugin namespace, e.g., "/search" or "/items/:id"
	// Supports :param syntax for path parameters
	Path string

	// Methods lists HTTP methods this route handles: GET, POST, PUT, DELETE, PATCH
	Methods []string

	// AdminOnly restricts this route to admin users
	AdminOnly bool

	// Description is human-readable text for API docs
	Description string

	// Capability is the capability name this route provides (e.g., "search", "chat")
	// Other plugins can invoke this capability via the host plugins service.
	Capability string

	// AliasPath is a stable URL alias for this route (e.g., "/api/search")
	// If set, the host will create an alias at this path that proxies to this route.
	// This allows plugins to claim well-known API paths.
	AliasPath string

	// Streaming indicates this route uses HandleHTTPStream instead of HandleHTTP
	Streaming bool

	// RateLimit optionally limits requests to this route
	RateLimit *RateLimit
}

Route describes an HTTP route exposed by a plugin.

type Row

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

Row is a single result row from QueryRow.

func (*Row) Scan

func (r *Row) Scan(dest ...any) error

Scan copies the row's columns into the provided destinations.

type Rows

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

Rows is an iterator over query results.

func (*Rows) Close

func (r *Rows) Close() error

Close closes the rows iterator. Always call this when done.

func (*Rows) Columns

func (r *Rows) Columns() []string

Columns returns the column names.

func (*Rows) Err

func (r *Rows) Err() error

Err returns any error that occurred during iteration.

func (*Rows) Next

func (r *Rows) Next() bool

Next advances to the next row. Returns false when there are no more rows.

func (*Rows) Scan

func (r *Rows) Scan(dest ...any) error

Scan copies the current row's columns into the provided destinations. The number of destinations must match the number of columns.

Supported destination types:

  • *string
  • *int, *int64, *int32
  • *float64, *float32
  • *[]byte
  • *bool
  • *any (receives the raw value)

type SQLClient

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

SQLClient provides managed SQL storage for plugins. All table names are automatically prefixed by the host with plugin_{id}_

func (*SQLClient) Exec

func (c *SQLClient) Exec(ctx context.Context, sql string, args ...any) (rowsAffected int64, lastInsertID int64, err error)

Exec executes a DDL or DML statement (CREATE, INSERT, UPDATE, DELETE). Returns the number of rows affected and the last insert ID (if applicable).

Example:

rowsAffected, lastID, err := db.Exec(ctx,
    `INSERT INTO items (name, value) VALUES (?, ?)`,
    "test", 42)

func (*SQLClient) Migrate

func (c *SQLClient) Migrate(ctx context.Context, migrations []Migration) error

Migrate runs schema migrations, applying any that haven't been run yet. Migrations are tracked in an _migrations table (auto-prefixed by host).

Example:

err := db.Migrate(ctx, []sdk.Migration{
    {Version: 1, SQL: `CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)`},
    {Version: 2, SQL: `ALTER TABLE items ADD COLUMN created_at TIMESTAMP`},
    {Version: 3, SQL: `CREATE INDEX idx_items_name ON items(name)`},
})

func (*SQLClient) Query

func (c *SQLClient) Query(ctx context.Context, sql string, args ...any) (*Rows, error)

Query executes a SELECT statement and returns an iterator over the results.

Example:

rows, err := db.Query(ctx, `SELECT id, name FROM items WHERE value > ?`, 10)
if err != nil {
    return err
}
defer rows.Close()

for rows.Next() {
    var id int64
    var name string
    if err := rows.Scan(&id, &name); err != nil {
        return err
    }
    // use id, name
}
return rows.Err()

func (*SQLClient) QueryRow

func (c *SQLClient) QueryRow(ctx context.Context, sql string, args ...any) *Row

QueryRow executes a SELECT expected to return at most one row. Returns a Row that can be scanned. If the query returns no rows, Scan will return ErrNoRows.

Example:

var name string
err := db.QueryRow(ctx, `SELECT name FROM items WHERE id = ?`, 1).Scan(&name)
if errors.Is(err, sdk.ErrNoRows) {
    // not found
}

type Schema

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

Schema builds a JSON Schema for plugin settings. Use NewSchema to create an instance and chain methods to configure it.

func NewSchema

func NewSchema(title string) *Schema

NewSchema creates a new schema builder with the given title. The title appears in the schema but is typically stripped by the UI since the plugin name is shown in the card header.

Example:

schema := sdk.NewSchema("My Plugin Settings")

func (*Schema) Action

func (s *Schema) Action(action Action) *Schema

Action adds an action to the x-viewra-actions extension. Actions create interactive UI elements like buttons and lists.

Action types:

  • TestAction: A "Test Connection" button
  • ListAction: A list of items with actions (e.g., model list)

Example:

schema.Action(sdk.TestAction("test-connection", "/health"))

func (*Schema) Build

func (s *Schema) Build() ([]byte, error)

Build serializes the schema to JSON bytes. Most plugins should use BuildSettingsSchema instead.

func (*Schema) BuildSettingsSchema

func (s *Schema) BuildSettingsSchema() (*pluginv1.SettingsSchema, error)

BuildSettingsSchema builds and returns a SettingsSchema proto message. Deprecated: Use Build() instead for SDK-only plugins.

Example:

func (p *Plugin) GetSettingsSchema() ([]byte, error) {
    return SettingsSchema().Build()
}

func (*Schema) Meta

func (s *Schema) Meta(meta PluginMeta) *Schema

Meta sets the x-viewra-meta extension for the schema. This metadata controls how the plugin appears in the UI.

Example:

schema.Meta(sdk.PluginMeta{
    DisplayName: "My Plugin",
    Description: "Does useful things",
    Icon:        "star",
})

func (*Schema) Property

func (s *Schema) Property(name string, prop *Property) *Schema

Property adds a property (setting field) to the schema. Properties are rendered as form fields in the settings UI. Use sdk.String(), sdk.Number(), sdk.Boolean(), or sdk.Integer() to create the property builder.

Example:

schema.Property("api_key", sdk.String().
    Title("API Key").
    Format("password").
    Required())

func (*Schema) Section

func (s *Schema) Section(section *Section) *Schema

Section adds a section to the x-viewra-sections extension. Sections group properties and actions by capability, allowing the UI to show only relevant settings based on context.

This is useful for plugins that support multiple capabilities (e.g., both embedding and chat) but have different settings for each.

Example:

schema.Section(sdk.NewSection("embedding").
    Properties("embedding_model").
    Actions("embedding-models").
    Capabilities("embedding"))

func (*Schema) Widgets

func (s *Schema) Widgets(widgets []Widget) *Schema

Widgets adds widgets to the x-viewra-widgets extension. Widgets are UI sections that the plugin provides for the home screen. Each widget has a type, location, and configuration that clients use to render the widget and fetch its data.

Example:

schema.Widgets([]sdk.Widget{
    {
        ID:              "my-recommendations",
        Type:            sdk.WidgetTypeMediaRow,
        Location:        sdk.LocationHomepageSections,
        ClientTypes:     []string{sdk.ClientTypeAll},
        Priority:        80,
        CacheTTLSeconds: 600,
        Config: map[string]any{
            "endpoint": "/recommendations",
            "title":    "Recommended for You",
        },
        SettingsKey: "enabled",
    },
})

type SearchProvider

type SearchProvider interface {

	// Search performs a search with the given request.
	// This is called when users search via the search hero or global search.
	Search(ctx context.Context, req *SearchProviderRequest) (*SearchProviderResponse, error)

	// GetSuggestions returns contextual suggestions for the search hero.
	// Suggestions can be personalized based on user, time, weather, etc.
	GetSuggestions(ctx context.Context, req *SuggestionRequest) (*SuggestionResponse, error)

	// GetSearchProviderInfo returns metadata about this search provider.
	// Used for provider selection UI and priority ordering.
	GetSearchProviderInfo(ctx context.Context) (*SearchProviderInfo, error)
	// contains filtered or unexported methods
}

SearchProvider is the interface that search provider plugins must implement. Plugins providing this capability offer search functionality to the home screen and other parts of the application.

type SearchProviderInfo

type SearchProviderInfo struct {
	// ID is a unique identifier for this provider.
	// Example: "semantic-search", "basic-search"
	ID string

	// Name is the human-readable name.
	// Example: "AI-Powered Search", "Basic Search"
	Name string

	// Description describes what this provider does.
	// Example: "Semantic search using AI embeddings"
	Description string

	// Icon is the icon name to display.
	// Common icons: "sparkles", "search", "brain"
	Icon string

	// Priority determines which provider is used by default.
	// Higher values = higher priority. Suggested ranges:
	//   100+ = AI-powered providers (semantic search)
	//   50   = Enhanced providers (fuzzy matching)
	//   10   = Basic providers (text search)
	Priority int

	// Capabilities lists what this provider supports.
	// Values:
	//   "natural_language" - Understands natural language queries
	//   "suggestions"      - Provides contextual suggestions
	//   "context_aware"    - Uses user context (time, weather, etc.)
	//   "fuzzy_matching"   - Handles typos and variations
	Capabilities []string
}

SearchProviderInfo contains metadata about a search provider.

type SearchProviderRequest

type SearchProviderRequest struct {
	// Query is the search query text.
	Query string

	// EntityTypes filters results by media types.
	// Empty means all types. Example: ["movie", "tv_show"]
	EntityTypes []string

	// Limit is the maximum number of results to return.
	Limit int

	// UserID is the requesting user (for context enrichment).
	UserID string
}

SearchProviderRequest contains parameters for a search request.

type SearchProviderResponse

type SearchProviderResponse struct {
	// Results is the list of search results.
	Results []SearchResult

	// Total is the total number of results (may be more than len(Results)).
	Total int

	// Provider is the ID of the provider that served this result.
	Provider string

	// Fallback indicates if this is a fallback result (basic text search).
	Fallback bool
}

SearchProviderResponse contains search results.

type SearchResult

type SearchResult struct {
	// EntityType is the media type: "movie", "tv_show", "tv_episode", etc.
	EntityType string

	// EntityID is the database ID of the media item.
	EntityID int64

	// Title is the display title.
	Title string

	// Year is the release/air year.
	Year int

	// Poster is the URL to the poster image.
	Poster string

	// Score is the relevance score (0.0-1.0).
	Score float32

	// Reason is an optional explanation for why this result matched.
	// Example: "Matches your search for 'sci-fi adventure'"
	Reason string
}

SearchResult represents a single search result.

type Section

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

Section represents an x-viewra-sections entry. Sections group properties and actions by capability, allowing the host UI to filter what's shown based on context.

For example, an AI provider plugin might have sections for:

  • "connection": base_url, test-connection (shown for both embedding and chat)
  • "embedding": embedding_model (shown only when used as embedding provider)
  • "chat": chat_model (shown only when used as chat provider)

func NewSection

func NewSection(id string) *Section

NewSection creates a section builder. The ID should be unique within the schema.

Example:

sdk.NewSection("connection")

func (*Section) Actions

func (s *Section) Actions(actions ...string) *Section

Actions sets which action IDs belong to this section. These should match action IDs defined with schema.Action().

Example:

sdk.NewSection("connection").Actions("test-connection")

func (*Section) Capabilities

func (s *Section) Capabilities(caps ...string) *Section

Capabilities sets which capabilities this section applies to. The host UI uses this to filter sections based on context.

Standard capabilities:

  • "embedding": Show when plugin is used as embedding provider
  • "chat": Show when plugin is used as chat provider

A section can belong to multiple capabilities.

Example:

// Connection settings shown for both embedding and chat
sdk.NewSection("connection").
    Properties("base_url").
    Actions("test-connection").
    Capabilities("embedding", "chat")

// Embedding model only shown for embedding capability
sdk.NewSection("embedding").
    Properties("embedding_model").
    Capabilities("embedding")

func (*Section) DependsOn

func (s *Section) DependsOn(field string, value any) *Section

DependsOn sets conditional visibility - this section only shows when the specified field has the specified value.

Example:

sdk.NewSection("advanced").
    Properties("timeout", "retries").
    DependsOn("enabled", true)

func (*Section) Properties

func (s *Section) Properties(props ...string) *Section

Properties sets which property names belong to this section. These should match property names defined with schema.Property().

Example:

sdk.NewSection("connection").Properties("base_url", "timeout")

func (*Section) Title

func (s *Section) Title(title string) *Section

Title sets the section title (optional, for display purposes).

type SemanticSearchRequest

type SemanticSearchRequest struct {
	Query       string   // Natural language search query
	EntityTypes []string // Filter by entity types: "movie", "tv_show", "tv_episode", etc.
	Limit       int      // Maximum number of results
	UserID      string   // Optional user ID for context enrichment (location, time)
}

SemanticSearchRequest contains parameters for semantic search.

type SemanticSearchResponse

type SemanticSearchResponse struct {
	Results []SemanticSearchResult
	Total   int
}

SemanticSearchResponse contains search results.

type SemanticSearchResult

type SemanticSearchResult struct {
	EntityType string  // "movie", "tv_show", etc.
	EntityID   int64   // Database ID of the entity
	Similarity float32 // Cosine similarity score (0.0 to 1.0)
	Text       string  // The indexed text that matched
}

SemanticSearchResult represents a single semantic search result.

type ShowWhen

type ShowWhen struct {
	Field string
	Value any
}

ShowWhen represents a condition for showing an action.

type StorageClient

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

StorageClient wraps the HostStorage service for plugin storage.

func NewStorageClient

func NewStorageClient(conn *grpc.ClientConn) *StorageClient

NewStorageClient creates a new storage client.

func (*StorageClient) Delete

func (c *StorageClient) Delete(ctx context.Context, key string) error

Delete removes a value from the plugin's key-value store.

func (*StorageClient) Get

func (c *StorageClient) Get(ctx context.Context, key string) ([]byte, bool, error)

Get retrieves a value from the plugin's key-value store.

func (*StorageClient) GetDatabasePath deprecated

func (c *StorageClient) GetDatabasePath(ctx context.Context) (string, error)

GetDatabasePath returns the path to the plugin's SQLite database.

Deprecated: Use SQL() instead for managed storage. Direct database access requires plugins to bundle their own SQLite driver and manage connections.

func (*StorageClient) List

func (c *StorageClient) List(ctx context.Context, prefix string, limit int) ([]string, error)

List lists keys with an optional prefix.

func (*StorageClient) SQL

func (c *StorageClient) SQL() *SQLClient

SQL returns a client for managed SQL storage. All table names are automatically prefixed with plugin_{id}_ by the host.

Example:

db := storage.SQL()
db.Exec(ctx, `CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)`)
db.Exec(ctx, `INSERT INTO items (name) VALUES (?)`, "test")
rows, _ := db.Query(ctx, `SELECT id, name FROM items`)

func (*StorageClient) Set

func (c *StorageClient) Set(ctx context.Context, key string, value []byte, ttlSeconds int64) error

Set stores a value in the plugin's key-value store. Use ttlSeconds=0 for no expiration.

func (*StorageClient) Vector

func (c *StorageClient) Vector() *VectorClient

Vector returns a client for managed vector storage. Embeddings are automatically indexed for fast similarity search. Uses pgvector (Postgres) or sqlite-vec (SQLite) under the hood.

Example:

vec := storage.Vector()
vec.Store(ctx, sdk.Embedding{EntityType: "movie", EntityID: 123, Vector: embedding})
results, _ := vec.Search(ctx, sdk.VectorSearchRequest{QueryVector: query, Limit: 10})

type StreamChunk

type StreamChunk struct {
	Payload        []byte            // Serialized response proto
	ProviderPlugin string            // Which plugin is streaming
	Metadata       map[string]string // Chunk metadata
	Error          error             // Error if chunk failed
}

StreamChunk represents a chunk from a streaming response.

type StreamingItemAction

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

StreamingItemAction represents a streaming action (e.g., model pull). The endpoint should return Server-Sent Events with progress updates.

func NewStreamingAction

func NewStreamingAction(id, label, endpoint string) *StreamingItemAction

NewStreamingAction creates a streaming item action. Use this for long-running operations like downloads.

The endpoint receives POST with item data and should return SSE:

data: {"status": "Downloading...", "percent": 50}
data: {"done": true}

Example:

sdk.NewStreamingAction("pull", "Pull", "/models/pull")

func (*StreamingItemAction) ShowWhen

func (a *StreamingItemAction) ShowWhen(field string, value any) *StreamingItemAction

ShowWhen sets a condition for showing this action. The action only appears on items where item[field] == value.

Example:

// Only show "Pull" on items that aren't installed
sdk.NewStreamingAction("pull", "Pull", "/models/pull").
    ShowWhen("installed", false)

type SubtitleTrack

type SubtitleTrack struct {
	Language   string // ISO 639-1/2 code
	Codec      string // "srt", "ass", "pgs", "vobsub"
	IsForced   bool
	IsSDH      bool // Subtitles for Deaf/Hard of Hearing
	IsExternal bool // External file vs embedded
}

SubtitleTrack represents a subtitle stream in a media file.

type Suggestion

type Suggestion struct {
	// ID is a unique identifier for this suggestion.
	ID string `json:"id"`

	// Label is the display text.
	Label string `json:"label"`

	// Icon is the icon name to display.
	// Common icons: "fire", "cloud-rain", "sun", "moon", "snowflake", "heart"
	Icon string `json:"icon,omitempty"`

	// Description is an optional subtitle.
	Description string `json:"description,omitempty"`

	// Style affects rendering: "primary", "secondary", "accent".
	Style string `json:"style,omitempty"`

	// Action defines what happens when the suggestion is clicked.
	Action SuggestionAction `json:"action"`
}

Suggestion represents a search suggestion in the search hero widget.

type SuggestionAction

type SuggestionAction struct {
	// Type is the action type: "search", "filter", "navigate".
	Type string `json:"type"`

	// Query is the search query for Type="search".
	Query string `json:"query,omitempty"`

	// Filter is filter parameters for Type="filter".
	Filter map[string]string `json:"filter,omitempty"`

	// URL is the navigation URL for Type="navigate".
	URL string `json:"url,omitempty"`
}

SuggestionAction defines the action for a search suggestion.

type SuggestionRequest

type SuggestionRequest struct {
	// UserID is the requesting user (for personalization).
	UserID string

	// Context describes where suggestions will be shown.
	// Values: "homepage" (search hero), "search_focus" (focused search input)
	Context string

	// Limit is the maximum number of suggestions to return.
	Limit int
}

SuggestionRequest contains parameters for getting search suggestions.

type SuggestionResponse

type SuggestionResponse struct {
	// Suggestions is the list of suggestions to display.
	Suggestions []Suggestion
}

SuggestionResponse contains search suggestions.

type SystemInfo

type SystemInfo struct {
	// RAM in bytes
	RAMBytes uint64

	// Available RAM in bytes
	RAMAvailableBytes uint64

	// Whether the system has a GPU
	HasGPU bool

	// VRAM in bytes
	VRAMBytes uint64

	// GPU name
	GPUName string

	// CPU core count
	CPUCores int

	// CPU model name
	CPUModel string

	// Operating system: "linux", "darwin", "windows"
	OS string

	// Architecture: "amd64", "arm64"
	Arch string
}

SystemInfo contains host system resource information. Useful for providers that need to recommend models based on available resources.

type TestActionDef

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

TestActionDef defines a test/health-check action. This renders as a "Test Connection" button that calls your health endpoint.

func TestAction

func TestAction(id, endpoint string) *TestActionDef

TestAction creates a test connection action. The endpoint should return a JSON response with:

{"success": true, "message": "Connected successfully"}

or on failure:

{"success": false, "error": "Connection failed"}

Example:

sdk.TestAction("test-connection", "/health")

func (*TestActionDef) Title

func (a *TestActionDef) Title(title string) *TestActionDef

Title sets the action button label. Default is "Test Connection".

Example:

sdk.TestAction("test-connection", "/health").Title("Verify API Key")

type TrendingItem

type TrendingItem struct {
	// ExternalID is the external identifier in format "source:id".
	// Example: "tmdb:12345", "imdb:tt1234567"
	ExternalID string `json:"external_id"`

	// MediaType is "movie" or "tv".
	MediaType string `json:"media_type"`

	// Title is the display title.
	Title string `json:"title"`

	// Year is the release/air year.
	Year int `json:"year"`

	// Popularity is the provider-specific popularity score.
	// Higher is more popular. Scale varies by provider.
	Popularity float32 `json:"popularity"`

	// PosterPath is the external URL to the poster image.
	// Example: "https://image.tmdb.org/t/p/w500/abc123.jpg"
	PosterPath string `json:"poster_path,omitempty"`

	// Overview is a brief description/plot summary.
	Overview string `json:"overview,omitempty"`

	// LocalID is the matched local library ID (filled in by the core service).
	// Nil if not matched to local library.
	LocalID *int64 `json:"local_id,omitempty"`

	// LocalMatched indicates whether this item was matched to the local library.
	LocalMatched bool `json:"local_matched"`
}

TrendingItem represents a single trending item from an external source.

type TrendingProvider

type TrendingProvider interface {

	// GetTrending returns currently trending items from this provider.
	// The response contains external IDs that the core service matches
	// against the user's local library.
	GetTrending(ctx context.Context, req *TrendingRequest) (*TrendingResponse, error)

	// GetTrendingProviderInfo returns metadata about this trending source.
	// Used for provider selection and capability discovery.
	GetTrendingProviderInfo(ctx context.Context) (*TrendingProviderInfo, error)
	// contains filtered or unexported methods
}

TrendingProvider is the interface that trending provider plugins must implement. Plugins providing this capability offer trending/popular content data from external sources (TMDb, Trakt, etc.).

type TrendingProviderGRPCPlugin

type TrendingProviderGRPCPlugin struct {
	plugin.Plugin
	Impl TrendingProvider
}

TrendingProviderGRPCPlugin is the go-plugin for TrendingProviderService.

func (*TrendingProviderGRPCPlugin) GRPCClient

func (p *TrendingProviderGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error)

func (*TrendingProviderGRPCPlugin) GRPCServer

func (p *TrendingProviderGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error

type TrendingProviderInfo

type TrendingProviderInfo struct {
	// ID is a unique identifier for this provider.
	// Example: "tmdb", "trakt"
	ID string

	// Name is the human-readable name.
	// Example: "TMDb Trending", "Trakt Popular"
	Name string

	// Description describes what this provider offers.
	// Example: "Trending movies and TV shows from The Movie Database"
	Description string

	// Windows lists supported time windows.
	// Example: ["day", "week"]
	Windows []string

	// MediaTypes lists supported media types.
	// Example: ["movie", "tv", "all"]
	MediaTypes []string

	// UpdateFreq describes how often data is updated.
	// Values: "hourly", "daily", "weekly"
	UpdateFreq string
}

TrendingProviderInfo contains metadata about a trending provider.

type TrendingRequest

type TrendingRequest struct {
	// MediaType filters by media type.
	// Values: "movie", "tv", "all" (default: "all")
	MediaType string

	// Window is the time window for trending.
	// Values: "day", "week" (default: "week")
	Window string

	// Limit is the maximum number of items to return.
	// Default: 20
	Limit int

	// Region is an optional ISO 3166-1 country code for regional trending.
	// Example: "US", "GB", "DE"
	Region string
}

TrendingRequest contains parameters for a trending request.

type TrendingResponse

type TrendingResponse struct {
	// Items is the list of trending items.
	Items []TrendingItem

	// Window is the time window used.
	Window string

	// Source is the provider that served this data.
	// Example: "tmdb", "trakt"
	Source string

	// CachedAt is the Unix timestamp when this data was fetched.
	// Used for cache management.
	CachedAt int64
}

TrendingResponse contains trending items from a provider.

type TrendingResult

type TrendingResult struct {
	// Items contains trending items matched to the local library.
	Items []TrendingItem

	// Source is the provider that served the original data.
	Source string

	// Window is the time window used.
	Window string

	// TotalMatched is the number of items matched to local library.
	TotalMatched int

	// TotalTrending is the total trending items from the provider.
	TotalTrending int
}

TrendingResult is the processed trending data with local library matches. Returned by the core TrendingService after matching.

type VectorClient

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

VectorClient provides managed vector storage for plugins. Embeddings are automatically indexed for fast similarity search.

func (*VectorClient) Count

func (c *VectorClient) Count(ctx context.Context, entityType string) (int64, error)

Count returns the number of embeddings. If entityType is empty, returns total count across all types.

Example:

total, _ := vec.Count(ctx, "")
movies, _ := vec.Count(ctx, "movie")

func (*VectorClient) Delete

func (c *VectorClient) Delete(ctx context.Context, entityType string, entityID int64) error

Delete removes an embedding.

Example:

err := vec.Delete(ctx, "movie", 123)

func (*VectorClient) DeleteAll

func (c *VectorClient) DeleteAll(ctx context.Context) (int64, error)

DeleteAll removes all embeddings for this plugin. Returns the number of deleted embeddings.

func (*VectorClient) DeleteByType

func (c *VectorClient) DeleteByType(ctx context.Context, entityType string) (int64, error)

DeleteByType removes all embeddings for an entity type. Returns the number of deleted embeddings.

Example:

count, err := vec.DeleteByType(ctx, "movie")
fmt.Printf("Deleted %d embeddings\n", count)

func (*VectorClient) Get

func (c *VectorClient) Get(ctx context.Context, entityType string, entityID int64) (*Embedding, error)

Get retrieves an embedding by entity type and ID. Returns nil if the embedding doesn't exist.

Example:

emb, err := vec.Get(ctx, "movie", 123)
if emb != nil {
    fmt.Printf("Found embedding with %d dimensions\n", len(emb.Vector))
}

func (*VectorClient) Search

Search performs similarity search using the query vector. Returns results ordered by similarity (highest first).

Example:

results, err := vec.Search(ctx, sdk.VectorSearchRequest{
    QueryVector:   queryEmbedding,
    EntityTypes:   []string{"movie", "tv_show"},
    Limit:         10,
    MinSimilarity: 0.5,
})
for _, r := range results.Results {
    fmt.Printf("%s %d: %.2f - %s\n", r.EntityType, r.EntityID, r.Similarity, r.Text)
}

func (*VectorClient) SearchText

func (c *VectorClient) SearchText(ctx context.Context, query string, entityTypes []string, limit int) (*VectorSearchResponse, error)

SearchText performs text-based search on embedding text field. This is useful for name/title searches where semantic search may fail.

Example:

results, err := vec.SearchText(ctx, "Christopher Nolan", []string{"movie"}, 50)
for _, r := range results.Results {
    fmt.Printf("%s %d: %s\n", r.EntityType, r.EntityID, r.Text)
}

func (*VectorClient) Store

func (c *VectorClient) Store(ctx context.Context, emb Embedding) error

Store saves or updates an embedding for an entity. If an embedding already exists for the entity, it is replaced.

Example:

err := vec.Store(ctx, sdk.Embedding{
    EntityType: "movie",
    EntityID:   123,
    Vector:     embedding,
    Text:       "The Matrix (1999)",
    Model:      "nomic-embed-text",
})

func (*VectorClient) StoreBatch

func (c *VectorClient) StoreBatch(ctx context.Context, embeddings []Embedding) error

StoreBatch saves multiple embeddings in a single transaction. This is more efficient than calling Store multiple times.

Example:

err := vec.StoreBatch(ctx, []sdk.Embedding{
    {EntityType: "movie", EntityID: 1, Vector: emb1},
    {EntityType: "movie", EntityID: 2, Vector: emb2},
})

type VectorSearchEnricherPlugin

type VectorSearchEnricherPlugin interface {
	EnricherPlugin
	VectorSearchPlugin
}

VectorSearchEnricherPlugin combines EnricherPlugin and VectorSearchPlugin interfaces. Plugins should implement this combined interface for full functionality.

type VectorSearchGRPCPlugin

type VectorSearchGRPCPlugin struct {
	plugin.Plugin
	Impl VectorSearchPlugin
}

VectorSearchGRPCPlugin implements plugin.GRPCPlugin for the VectorSearch service.

func (*VectorSearchGRPCPlugin) GRPCClient

func (p *VectorSearchGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error)

func (*VectorSearchGRPCPlugin) GRPCServer

func (p *VectorSearchGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error

type VectorSearchPlugin

type VectorSearchPlugin interface {

	// Search performs semantic search across indexed media.
	// The query is converted to an embedding and matched against stored embeddings.
	Search(ctx context.Context, req *SemanticSearchRequest) (*SemanticSearchResponse, error)

	// FindSimilar finds items similar to a given entity.
	// Uses the entity's stored embedding to find similar items.
	FindSimilar(ctx context.Context, req *FindSimilarRequest) (*SemanticSearchResponse, error)

	// GetStatus returns the current indexing status and statistics.
	GetStatus(ctx context.Context) (*VectorSearchStatus, error)

	// IndexLibrary triggers indexing for all media in a library.
	// Indexing runs in the background; use GetStatus to monitor progress.
	IndexLibrary(ctx context.Context, req *IndexLibraryRequest) (*IndexLibraryResponse, error)

	// CancelIndexing cancels any running indexing operation.
	CancelIndexing(ctx context.Context) error
	// contains filtered or unexported methods
}

VectorSearchPlugin is the interface that vector search plugins must implement. Plugins implementing this interface should also implement EnricherPlugin for auto-indexing support in the enrichment pipeline.

type VectorSearchRequest

type VectorSearchRequest struct {
	// QueryVector is the embedding to search for similar items
	QueryVector []float32

	// EntityTypes filters results by type (empty = all types)
	EntityTypes []string

	// Limit is the maximum number of results (default: 20)
	Limit int

	// Offset for pagination
	Offset int

	// MinSimilarity filters results below this threshold (0.0-1.0)
	MinSimilarity float32
}

VectorSearchRequest specifies search parameters.

type VectorSearchResponse

type VectorSearchResponse struct {
	// Results ordered by similarity (highest first)
	Results []VectorSearchResult

	// TotalCount is the total number of matching embeddings
	TotalCount int
}

VectorSearchResponse contains search results.

type VectorSearchResult

type VectorSearchResult struct {
	// EntityType of the matched embedding
	EntityType string

	// EntityID of the matched embedding
	EntityID int64

	// Similarity score (0.0-1.0, higher is more similar)
	Similarity float32

	// Text that was embedded
	Text string
}

VectorSearchResult represents a search result.

type VectorSearchStatus

type VectorSearchStatus struct {
	IsIndexing   bool              // Whether indexing is currently running
	Progress     *IndexingProgress // Current operation progress (if indexing)
	Stats        []EntityTypeStats // Per-entity-type statistics
	TotalIndexed int64             // Total embeddings stored
}

VectorSearchStatus contains the current indexing status.

type WatchProgress

type WatchProgress struct {
	MediaID         int64
	MediaType       string  // "movie", "tv_episode"
	ProgressSeconds float64 // Current playback position
	DurationSeconds float64 // Total duration
	ProgressPercent float64 // Progress as percentage (0-100)
	IsWatched       bool    // True if marked as watched
	LastWatchedAt   time.Time
}

WatchProgress represents a user's watch progress for a media item.

type Weather

type Weather struct {
	Temperature   float32 // Celsius
	Humidity      int     // Percentage
	IsDay         bool
	Precipitation float32 // mm
	CloudCover    int     // Percentage
	WeatherCode   int     // WMO code
	Condition     string  // sunny, cloudy, rainy, snowy, stormy, foggy
	TimeOfDay     string  // morning, afternoon, evening, night
	Season        string  // spring, summer, fall, winter
}

Weather contains current weather and time context.

type WeatherClient

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

WeatherClient wraps the HostWeather service for context enrichment.

func NewWeatherClient

func NewWeatherClient(conn *grpc.ClientConn) *WeatherClient

NewWeatherClient creates a new weather client.

func (*WeatherClient) GetWeather

func (c *WeatherClient) GetWeather(ctx context.Context, userID string) (*Weather, error)

GetWeather returns current weather for a user's location. Returns nil if the user hasn't enabled location sharing.

type Widget

type Widget struct {
	// ID is a unique identifier for this widget within the plugin.
	// Example: "rec-for-you", "search-hero"
	ID string `json:"id"`

	// Type determines how the widget is rendered.
	// Use WidgetTypeSearchHero, WidgetTypeMediaRow, etc.
	Type string `json:"type"`

	// Location specifies where on the home screen this widget appears.
	// Use LocationHomepageTop or LocationHomepageSections.
	Location string `json:"location"`

	// ClientTypes specifies which clients should render this widget.
	// Use ["all"] for all clients, or specific types like ["web", "ios"].
	// Clients filter widgets by their type and only render matching ones.
	ClientTypes []string `json:"client_types"`

	// Priority determines order within a location (higher = rendered first).
	// Use 100 for critical widgets, 50 for normal, 0 for fallback.
	Priority int `json:"priority"`

	// CacheTTLSeconds is how long clients should cache this widget's data.
	// Use 60 for frequently changing data, 3600 for stable data.
	CacheTTLSeconds int `json:"cache_ttl_seconds,omitempty"`

	// Config contains widget-specific configuration passed to the client.
	// Common fields:
	//   - endpoint: HTTP endpoint to fetch widget data (e.g., "/recommendations")
	//   - title: Default title for the widget
	//   - show_suggestions: For search-hero, whether to show suggestion chips
	Config map[string]any `json:"config,omitempty"`

	// RequiredCapability makes this widget only appear when a capability is available.
	// Example: "search_provider" - only show if semantic search is enabled.
	RequiredCapability string `json:"required_capability,omitempty"`

	// SettingsKey references a boolean setting that controls widget visibility.
	// When the setting is false, the widget is hidden.
	// Example: "enabled", "show_recommendations"
	SettingsKey string `json:"settings_key,omitempty"`
}

Widget defines a UI section that a plugin provides for the home screen. Widgets are registered via the settings schema and rendered by clients.

type WidgetCoreGRPCPlugin

type WidgetCoreGRPCPlugin struct {
	plugin.Plugin
	Impl WidgetPlugin
	// contains filtered or unexported fields
}

WidgetCoreGRPCPlugin implements plugin.GRPCPlugin for WidgetPlugin.

func (*WidgetCoreGRPCPlugin) GRPCClient

func (p *WidgetCoreGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error)

func (*WidgetCoreGRPCPlugin) GRPCServer

func (p *WidgetCoreGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error

type WidgetDataRequest

type WidgetDataRequest struct {
	// WidgetID is the widget being requested.
	WidgetID string

	// UserID is the requesting user (for personalization).
	UserID string

	// ClientType is the type of client making the request.
	ClientType string

	// ImageSize is the preferred image size ("small", "medium", "large").
	ImageSize string
}

WidgetDataRequest contains parameters for fetching widget data. This is passed to plugins when the home service requests widget data.

type WidgetDataResponse

type WidgetDataResponse struct {
	// Data is the widget-specific data to render.
	// Shape depends on widget type (see WidgetType* constants).
	Data map[string]any `json:"data"`

	// CacheTTLSeconds overrides the widget's default cache TTL.
	CacheTTLSeconds int `json:"cache_ttl_seconds,omitempty"`
}

WidgetDataResponse is the response from a plugin for widget data.

type WidgetError

type WidgetError struct {
	// WidgetID is the widget that failed.
	WidgetID string

	// PluginID is the plugin that provides the widget.
	PluginID string

	// Error is the error message.
	Error string

	// Timestamp is when the error occurred.
	Timestamp int64
}

WidgetError represents an error from widget data fetching. Errors are logged but widgets with errors are silently omitted from the response.

type WidgetPlugin

type WidgetPlugin interface {

	// Initialize is called when the plugin is loaded.
	// Config is the contents of config.yml passed by the host.
	// Services provides access to host services (storage, data, etc.) - may have nil fields.
	Initialize(ctx context.Context, dataDir string, config []byte, services *HostServices) error

	// Shutdown is called before the plugin is unloaded.
	// Use this to clean up any resources.
	Shutdown(ctx context.Context) error

	// GetSettingsSchema returns a JSON Schema describing the plugin's configurable settings.
	// Use Schema.Build() to generate this from your schema definition.
	// Include Widgets() to register home screen widgets.
	GetSettingsSchema() ([]byte, error)

	// Configure applies new settings to the plugin.
	Configure(settings []byte) error

	// IsConfigured returns whether the plugin is properly configured.
	// Plugins requiring API keys should return false until the key is set.
	// The host uses this to exclude unconfigured plugins from capability resolution.
	IsConfigured() bool

	// GetRoutes returns HTTP routes this plugin exposes.
	GetRoutes() []Route

	// HandleHTTP handles a non-streaming HTTP request.
	HandleHTTP(ctx context.Context, req *HTTPRequest) (*HTTPResponse, error)
	// contains filtered or unexported methods
}

WidgetPlugin is the interface that widget plugins must implement. Widget plugins provide HTTP routes and home screen widgets but don't enrich media.

Plugin identity comes from plugin.yml manifest file, not code.

Jump to

Keyboard shortcuts

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