backend

package
v0.1.4 Latest Latest
Warning

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

Go to latest
Published: Feb 1, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package backend provides tool execution backend abstractions and registry.

This package defines the core Backend interface and provides infrastructure for managing multiple backend sources:

  • Backend interface for tool sources (local, MCP, HTTP, gRPC)
  • Registry for managing and discovering backends
  • Aggregator for multi-backend tool execution

Backend Types

Backends can be:

  • Local: In-process handlers registered directly
  • MCP: Model Context Protocol servers (stdio, SSE, etc.)
  • HTTP: RESTful tool APIs
  • gRPC: High-performance tool services

Registry

The Registry manages backend lifecycle:

registry := backend.NewRegistry()
registry.Register(localBackend)
registry.Register(mcpBackend)

// List all registered backends
for _, b := range registry.List() {
    fmt.Printf("%s: %s\n", b.Kind(), b.Name())
}

Aggregator

The Aggregator combines multiple backends for unified tool access:

agg := backend.NewAggregator(registry)
tools, _ := agg.ListAllTools(ctx)
result, _ := agg.Execute(ctx, "backend:tool", args)

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrBackendNotFound    = errors.New("backend not found")
	ErrBackendDisabled    = errors.New("backend disabled")
	ErrToolNotFound       = errors.New("tool not found in backend")
	ErrBackendUnavailable = errors.New("backend unavailable")
)

Common errors for backend operations.

View Source
var ErrBackendExists = errors.New("backend already registered")

ErrBackendExists is returned when registering a duplicate backend.

View Source
var ErrInvalidToolID = errors.New("invalid tool ID format")

ErrInvalidToolID is returned for malformed tool IDs.

Functions

func FormatToolID

func FormatToolID(backendName, tool string) string

FormatToolID builds a tool ID from backend and tool name.

func ParseToolID

func ParseToolID(id string) (backendName, tool string, err error)

ParseToolID splits a tool ID into backend and tool name.

Types

type Aggregator

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

Aggregator combines tools from multiple backends.

Example
package main

import (
	"context"
	"fmt"

	"github.com/jonwraymond/toolexec/backend"
	"github.com/jonwraymond/toolexec/backend/local"
)

func main() {
	// Create registry and backends
	reg := backend.NewRegistry()

	backend1 := local.New("math")
	backend1.RegisterHandler("add", local.ToolDef{
		Name:        "add",
		Description: "Adds two numbers",
		InputSchema: map[string]any{"type": "object"},
		Handler: func(ctx context.Context, args map[string]any) (any, error) {
			a, _ := args["a"].(float64)
			b, _ := args["b"].(float64)
			return a + b, nil
		},
	})

	backend2 := local.New("text")
	backend2.RegisterHandler("upper", local.ToolDef{
		Name:        "upper",
		Description: "Converts to uppercase",
		InputSchema: map[string]any{"type": "object"},
		Handler: func(ctx context.Context, args map[string]any) (any, error) {
			return "HELLO", nil
		},
	})

	_ = reg.Register(backend1)
	_ = reg.Register(backend2)

	// Create aggregator
	agg := backend.NewAggregator(reg)

	// List tools from all backends
	ctx := context.Background()
	tools, _ := agg.ListAllTools(ctx)
	fmt.Printf("Total tools: %d\n", len(tools))

	// Execute through aggregator (using backend:tool format)
	result, _ := agg.Execute(ctx, "math:add", map[string]any{"a": float64(5), "b": float64(3)})
	fmt.Printf("5 + 3 = %v\n", result)
}
Output:

Total tools: 2
5 + 3 = 8

func NewAggregator

func NewAggregator(registry *Registry) *Aggregator

NewAggregator creates a new tool aggregator.

func (*Aggregator) Execute

func (a *Aggregator) Execute(ctx context.Context, toolID string, args map[string]any) (any, error)

Execute invokes a tool through the backend registry.

func (*Aggregator) ListAllTools

func (a *Aggregator) ListAllTools(ctx context.Context) ([]model.Tool, error)

ListAllTools returns tools from all enabled backends.

type Backend

type Backend interface {
	// Kind returns the backend type (e.g., "local", "mcp", "http").
	Kind() string

	// Name returns the unique instance name for this backend.
	Name() string

	// Enabled returns whether this backend is currently enabled.
	Enabled() bool

	// ListTools returns all tools available from this backend.
	ListTools(ctx context.Context) ([]model.Tool, error)

	// Execute invokes a tool on this backend.
	Execute(ctx context.Context, tool string, args map[string]any) (any, error)

	// Start initializes the backend (connect to remote, start subprocess, etc.).
	Start(ctx context.Context) error

	// Stop gracefully shuts down the backend.
	Stop() error
}

Backend defines a source of tools. Backends can be local handlers, MCP servers, HTTP APIs, or custom implementations.

Contract: - Concurrency: implementations must be safe for concurrent use. - Context: methods must honor cancellation/deadlines. - Errors: use ErrBackendNotFound/ErrBackendDisabled/ErrToolNotFound/ErrBackendUnavailable where applicable.

Example (Lifecycle)
package main

import (
	"context"
	"fmt"

	"github.com/jonwraymond/toolexec/backend/local"
)

func main() {
	// Create a local backend
	b := local.New("example")

	// Register a tool
	b.RegisterHandler("echo", local.ToolDef{
		Name:        "echo",
		Description: "Echoes input",
		InputSchema: map[string]any{"type": "object"},
		Handler: func(ctx context.Context, args map[string]any) (any, error) {
			return args["message"], nil
		},
	})

	ctx := context.Background()

	// Start the backend
	if err := b.Start(ctx); err != nil {
		fmt.Printf("Start failed: %v\n", err)
		return
	}

	// Check if enabled
	fmt.Printf("Enabled: %v\n", b.Enabled())

	// List available tools
	tools, _ := b.ListTools(ctx)
	fmt.Printf("Tools: %d\n", len(tools))

	// Execute a tool
	result, _ := b.Execute(ctx, "echo", map[string]any{"message": "hello"})
	fmt.Printf("Result: %v\n", result)

	// Stop the backend
	_ = b.Stop()
}
Output:

Enabled: true
Tools: 1
Result: hello

type ConfigurableBackend

type ConfigurableBackend interface {
	Backend

	Configure(raw []byte) error
}

ConfigurableBackend can be configured from raw bytes (YAML/JSON).

Contract: - Configure must validate config and return error on invalid input.

type Factory

type Factory func(name string) (Backend, error)

Factory creates backend instances.

type Info

type Info struct {
	Kind        string
	Name        string
	Enabled     bool
	Description string
	Version     string
}

Info contains metadata about a backend.

Example
package main

import (
	"fmt"

	"github.com/jonwraymond/toolexec/backend"
	"github.com/jonwraymond/toolexec/backend/local"
)

func main() {
	b := local.New("my-backend")

	info := backend.Info{
		Kind:        b.Kind(),
		Name:        b.Name(),
		Enabled:     b.Enabled(),
		Description: "A demo backend",
		Version:     "1.0.0",
	}

	fmt.Printf("Kind: %s\n", info.Kind)
	fmt.Printf("Name: %s\n", info.Name)
	fmt.Printf("Enabled: %v\n", info.Enabled)
}
Output:

Kind: local
Name: my-backend
Enabled: true

type Registry

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

Registry manages backend instances.

Example
package main

import (
	"context"
	"fmt"

	"github.com/jonwraymond/toolexec/backend"
	"github.com/jonwraymond/toolexec/backend/local"
)

func main() {
	// Create a registry
	reg := backend.NewRegistry()

	// Create and register a local backend
	localBackend := local.New("demo")
	localBackend.RegisterHandler("greet", local.ToolDef{
		Name:        "greet",
		Description: "Greets a user",
		InputSchema: map[string]any{"type": "object"},
		Handler: func(ctx context.Context, args map[string]any) (any, error) {
			name, _ := args["name"].(string)
			return fmt.Sprintf("Hello, %s!", name), nil
		},
	})

	_ = reg.Register(localBackend)

	// List registered backends
	backends := reg.List()
	fmt.Printf("Registered backends: %d\n", len(backends))

	// Get a specific backend
	b, ok := reg.Get("demo")
	fmt.Printf("Found 'demo': %v\n", ok)
	fmt.Printf("Backend kind: %s\n", b.Kind())
}
Output:

Registered backends: 1
Found 'demo': true
Backend kind: local

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates a new backend registry.

func (*Registry) Get

func (r *Registry) Get(name string) (Backend, bool)

Get retrieves a backend by name.

func (*Registry) List

func (r *Registry) List() []Backend

List returns all backends.

func (*Registry) ListByKind

func (r *Registry) ListByKind(kind string) []Backend

ListByKind returns backends matching the given kind.

func (*Registry) ListEnabled

func (r *Registry) ListEnabled() []Backend

ListEnabled returns enabled backends only.

func (*Registry) Names

func (r *Registry) Names() []string

Names returns backend names sorted for deterministic output.

func (*Registry) Register

func (r *Registry) Register(b Backend) error

Register adds a backend to the registry.

func (*Registry) RegisterFactory

func (r *Registry) RegisterFactory(kind string, factory Factory)

RegisterFactory registers a factory for a backend kind.

func (*Registry) StartAll

func (r *Registry) StartAll(ctx context.Context) error

StartAll starts all backends.

func (*Registry) StopAll

func (r *Registry) StopAll() error

StopAll stops all backends.

func (*Registry) Unregister

func (r *Registry) Unregister(name string)

Unregister removes a backend from the registry.

type StreamingBackend

type StreamingBackend interface {
	Backend

	ExecuteStream(ctx context.Context, tool string, args map[string]any) (<-chan any, error)
}

StreamingBackend supports streaming responses.

Contract: - If ExecuteStream returns nil error, the channel must be non-nil.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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