provider

package
v0.36.0 Latest Latest
Warning

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

Go to latest
Published: Mar 27, 2026 License: MIT Imports: 16 Imported by: 0

README ΒΆ

HotPlex Providers: AI Agent Abstraction Layer

The provider package defines the bridge between HotPlex and various AI CLI agents (e.g., Claude Code, OpenCode). It abstracts platform-specific CLI protocols, event formats, and execution models into a unified interface.

πŸ› Architecture Overview

The Providers act as Strategy Adapters in the HotPlex ecosystem. They handle the low-level details of interacting with different AI agents while exposing a consistent API to the Engine.

graph TD
    Engine[HotPlex Engine] --> Factory[Provider Factory]
    Factory --> Claude[Claude Code Provider]
    Factory --> OpenCode[OpenCode Provider]
    Factory --> Pi[Pi Provider]
    Factory --> Plugin[Plugin Registry]
    Plugin --> Custom[Third-party Providers]

    subgraph Interface [Unified Interface]
        Provider[Provider Interface]
        Event[Normalized Event Model]
    end

    Claude -.-> Provider
    OpenCode -.-> Provider
    Pi -.-> Provider
    Custom -.-> Provider
Key Architectural Concepts
  • Provider (Interface): The core contract that defines how to start a CLI, send user input, and parse the resulting stream of events.
  • Normalized Event Model: Regardless of the provider's native output (JSON, SSE, plain text), this package converts it into a standard ProviderEvent stream (e.g., thinking, tool_use, answer, error).
  • Factory Pattern: The ProviderFactory allows for dynamic registration and creation of providers based on configuration.
  • Plugin System: Third-party providers can be registered via RegisterPlugin() without modifying core code.
  • Protocol Translation: Each provider implementation handles the specific "dialect" of its underlying CLI.

πŸ”Œ Plugin System (RFC #216)

The plugin system enables third-party extensions without modifying HotPlex core.

Plugin Interface
type ProviderPlugin interface {
    Type() ProviderType
    New(cfg ProviderConfig, logger *slog.Logger) (Provider, error)
    Meta() ProviderMeta
}
Registration
// external/myprovider/plugin.go
import "github.com/hrygo/hotplex/provider"

type myPlugin struct{}

func (p *myPlugin) Type() provider.ProviderType { return "my-ai" }
func (p *myPlugin) New(cfg provider.ProviderConfig, logger *slog.Logger) (provider.Provider, error) {
    return &myProviderImpl{cfg: cfg, logger: logger}, nil
}
func (p *myPlugin) Meta() provider.ProviderMeta {
    return provider.ProviderMeta{
        Type:        "my-ai",
        DisplayName: "My AI Provider",
        BinaryName:  "my-ai-cli",
        Features: provider.ProviderFeatures{
            SupportsResume:     true,
            SupportsStreamJSON: true,
        },
    }
}

func init() {
    provider.RegisterPlugin(&myPlugin{})
}
Type Checking
// Built-in types
provider.ProviderTypeClaudeCode.IsRegistered() // true
provider.ProviderTypeOpenCode.IsRegistered()   // true

// Plugin types
provider.ProviderType("my-ai").IsRegistered()  // true after plugin registration

See docs/provider-extension-guide.md for detailed extension guide.


πŸ›  Developer Guide

1. Implementing a New Provider

To support a new AI CLI tool, implement the Provider interface (defined in provider.go):

type Provider interface {
    Name() string
    Metadata() ProviderMeta
    BuildCLIArgs(sessionID string, opts *ProviderSessionOptions) []string
    BuildInputMessage(prompt string, taskInstructions string) (map[string]any, error)
    ParseEvent(line string) ([]*ProviderEvent, error)
    DetectTurnEnd(event *ProviderEvent) bool
    ValidateBinary() (string, error)
    CleanupSession(sessionID string, workDir string) error
}
2. Registering with the Factory

Option A - Using Plugin System (Recommended):

func init() {
    provider.RegisterPlugin(&myPlugin{})
}

Option B - Direct Factory Registration:

provider.GlobalProviderFactory.Register("my-new-ai", func(cfg ProviderConfig, logger *slog.Logger) (Provider, error) {
    return &MyNewProvider{...}, nil
})
3. Using the Provider
pCfg := provider.ProviderConfig{Type: "claude-code", Enabled: true}
prv, err := provider.CreateProvider(pCfg)
if err != nil {
    // handle error
}

πŸ— Event Normalization Mapping

Each provider must map its internal events to these standard types:

Standard Type Description
thinking AI is reasoning (e.g., Claude's thinking block).
tool_use AI is about to execute a local tool.
tool_result The result of a tool execution.
answer Final or streaming text response.
permission_request AI needs user approval for a sensitive action.
error A provider-level or tool-level error.

πŸ“Š Token Usage & Context Window Management

HotPlex provides deep integration with Claude Code's stream-json mode to track costs and monitor context window limits in real-time.

1. Claude Code modelUsage Structure

In stream-json mode, the result event contains a modelUsage map. HotPlex maps this to ModelUsageStats in types.go:

Field Description
inputTokens Cumulative input tokens (not including cache hits).
outputTokens Cumulative output tokens (including thinking blocks).
cacheReadInputTokens Tokens retrieved from Anthropic's prompt cache (90% discount).
cacheCreationInputTokens Tokens written to cache (ephemeral 5m or 1h).
contextWindow The model's total context capacity (e.g., 200,000 or 1,000,000).
maxOutputTokens The model's maximum output limit.
webSearchRequests Count of tool-level web search operations.
2. Multi-Model Context Strategy

Claude Code supports switching models mid-session (via /model). In such cases, modelUsage may contain multiple entries. HotPlex applies the following logic:

  • Token Aggregation: InputTokens, OutputTokens, and Cost are summed across all models for total session reporting.
  • Primary Model Selection: The model with the highest inputTokens is considered the Primary Model.
  • Context Reporting: The ContextWindow and MaxOutputTokens of the Primary Model are propagated in ProviderEventMeta to drive UI usage indicators.
3. Context Window Calculation

The HotPlex Engine calculates context utilization percentage using the normalized metadata:

Total Context Used = inputTokens + cacheReadInputTokens + cacheCreationInputTokens
Usage % = (Total Context Used / ContextWindow) * 100

βš™οΈ Configuration

Providers are configured via the ProviderConfig struct, which can be loaded from YAML/JSON:

provider:
  type: "claude-code"
  enabled: true
  default_model: "claude-3-5-sonnet"
  allowed_tools: ["ls", "cat"]
  extra_args: ["--verbose"]

πŸ“ File Structure

provider/
β”œβ”€β”€ provider.go        # Core interfaces and types
β”œβ”€β”€ types.go           # Configuration and constant definitions
β”œβ”€β”€ plugin.go          # Plugin system (RFC #216)
β”œβ”€β”€ factory.go         # Provider factory and registry
β”œβ”€β”€ event.go           # Event types and normalization
β”œβ”€β”€ permission.go      # Permission handling
β”œβ”€β”€ claude_provider.go # Claude Code implementation
β”œβ”€β”€ opencode_provider.go # OpenCode implementation
β”œβ”€β”€ pi_provider.go     # Pi implementation
└── README.md          # This file

Package Path: github.com/hrygo/hotplex/provider Core Components: Provider, ProviderPlugin, ProviderFactory, ProviderEvent

Documentation ΒΆ

Index ΒΆ

Constants ΒΆ

View Source
const (
	OCPartText       = "text"
	OCPartReasoning  = "reasoning"
	OCPartTool       = "tool"
	OCPartStepStart  = "step-start"
	OCPartStepFinish = "step-finish"
)

OpenCode Part types (from research results).

View Source
const (
	OCEventMessagePartUpdated = "message.part.updated"
	OCEventMessageUpdated     = "message.updated"
	OCEventSessionStatus      = "session.status"
	OCEventSessionIdle        = "session.idle"
	OCEventSessionError       = "session.error"
	OCEventPermissionUpdated  = "permission.updated"
)

SSE Event types from opencode serve.

View Source
const (
	ToolStatusPending   = "pending"
	ToolStatusRunning   = "running"
	ToolStatusCompleted = "completed"
	ToolStatusError     = "error"
)

Tool status constants for type safety

View Source
const (
	PiEventTypeAgentStart          = "agent_start"
	PiEventTypeAgentEnd            = "agent_end"
	PiEventTypeTurnStart           = "turn_start"
	PiEventTypeTurnEnd             = "turn_end"
	PiEventTypeMessageStart        = "message_start"
	PiEventTypeMessageUpdate       = "message_update"
	PiEventTypeMessageEnd          = "message_end"
	PiEventTypeToolExecutionStart  = "tool_execution_start"
	PiEventTypeToolExecutionUpdate = "tool_execution_update"
	PiEventTypeToolExecutionEnd    = "tool_execution_end"
	PiEventTypeSession             = "session"
	PiEventTypeAutoCompactionStart = "auto_compaction_start"
	PiEventTypeAutoCompactionEnd   = "auto_compaction_end"
	PiEventTypeAutoRetryStart      = "auto_retry_start"
	PiEventTypeAutoRetryEnd        = "auto_retry_end"
)

Pi event types from the JSON output stream. Reference: https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/json.md

View Source
const (
	PiContentTypeText     = "text"
	PiContentTypeThinking = "thinking"
	PiContentTypeToolCall = "toolCall"
	PiContentTypeImage    = "image"
)

Pi content block types.

Variables ΒΆ

This section is empty.

Functions ΒΆ

func BoolValue ΒΆ added in v0.23.4

func BoolValue(pb *bool, defaultVal bool) bool

BoolValue returns the value of a bool pointer if not nil, otherwise returns defaultVal.

func InitGlobalProviderFactory ΒΆ

func InitGlobalProviderFactory()

InitGlobalProviderFactory initializes the global provider factory. This is called automatically on first use.

func IsPluginRegistered ΒΆ added in v0.22.0

func IsPluginRegistered(t ProviderType) bool

IsPluginRegistered checks if a plugin type is registered.

func PtrBool ΒΆ added in v0.23.4

func PtrBool(b bool) *bool

PtrBool returns a pointer to the given bool value.

func RegisterPlugin ΒΆ added in v0.22.0

func RegisterPlugin(p ProviderPlugin)

RegisterPlugin registers a provider plugin with the global registry. This is typically called from an init() function in the plugin package.

Example:

func init() {
    provider.RegisterPlugin(&myCustomPlugin{})
}

Plugins registered here are automatically available through the GlobalProviderFactory after initialization.

func ResolveBinaryPath ΒΆ added in v0.22.0

func ResolveBinaryPath(cfg ProviderConfig, meta ProviderMeta) (string, error)

ResolveBinaryPath resolves the binary path from config or PATH lookup. This is a shared helper for all provider implementations.

func ValidateProviderConfig ΒΆ

func ValidateProviderConfig(cfg ProviderConfig) error

ValidateProviderConfig validates a provider configuration.

func WritePermissionResponse ΒΆ added in v0.12.0

func WritePermissionResponse(w io.Writer, behavior PermissionBehavior, message string) error

WritePermissionResponse writes a permission response to stdout/stdin. Format: single-line JSON with newline terminator.

Types ΒΆ

type AssistantMessage ΒΆ

type AssistantMessage struct {
	ID      string         `json:"id,omitempty"`
	Type    string         `json:"type,omitempty"`
	Role    string         `json:"role,omitempty"`
	Content []ContentBlock `json:"content,omitempty"`
}

AssistantMessage represents the structured message emitted by the model.

type ClaudeCodeProvider ΒΆ

type ClaudeCodeProvider struct {
	ProviderBase
	// contains filtered or unexported fields
}

func NewClaudeCodeProvider ΒΆ

func NewClaudeCodeProvider(cfg ProviderConfig, logger *slog.Logger) (*ClaudeCodeProvider, error)

NewClaudeCodeProvider creates a new Claude Code provider instance.

func (*ClaudeCodeProvider) BuildCLIArgs ΒΆ

func (p *ClaudeCodeProvider) BuildCLIArgs(providerSessionID string, opts *ProviderSessionOptions) []string

BuildCLIArgs constructs Claude Code CLI arguments.

func (*ClaudeCodeProvider) BuildInputMessage ΒΆ

func (p *ClaudeCodeProvider) BuildInputMessage(prompt string, taskInstructions string) (map[string]any, error)

BuildInputMessage constructs the stream-json input message.

func (*ClaudeCodeProvider) CheckSessionMarker ΒΆ

func (p *ClaudeCodeProvider) CheckSessionMarker(providerSessionID string) bool

CheckSessionMarker checks if a session marker exists for the given ID.

func (*ClaudeCodeProvider) CleanupSession ΒΆ added in v0.17.0

func (p *ClaudeCodeProvider) CleanupSession(providerSessionID string, workDir string) error

CleanupSession overrides ProviderBase to delete Claude Code's project session file. This is necessary when starting a fresh session to prevent "Session ID is already in use" errors or when executing a /reset command to completely scrub the context.

func (*ClaudeCodeProvider) DetectTurnEnd ΒΆ

func (p *ClaudeCodeProvider) DetectTurnEnd(event *ProviderEvent) bool

DetectTurnEnd checks if the event signals turn completion.

func (*ClaudeCodeProvider) GetMarkerDir ΒΆ

func (p *ClaudeCodeProvider) GetMarkerDir() string

GetMarkerDir returns the session marker directory path.

func (*ClaudeCodeProvider) ParseEvent ΒΆ

func (p *ClaudeCodeProvider) ParseEvent(line string) ([]*ProviderEvent, error)

ParseEvent parses a Claude Code stream-json line into one or more ProviderEvents.

func (*ClaudeCodeProvider) ValidateBinary ΒΆ

func (p *ClaudeCodeProvider) ValidateBinary() (string, error)

ValidateBinary checks if the Claude CLI is available.

func (*ClaudeCodeProvider) VerifySession ΒΆ added in v0.29.0

func (p *ClaudeCodeProvider) VerifySession(providerSessionID string, workDir string) bool

VerifySession checks if a CLI session data file exists and can be resumed. This prevents "No conversation found with session ID" errors when the marker exists but the CLI session data has been deleted or expired.

type ContentBlock ΒΆ

type ContentBlock struct {
	Type      string         `json:"type"`
	Text      string         `json:"text,omitempty"`
	Name      string         `json:"name,omitempty"`
	ID        string         `json:"id,omitempty"`
	ToolUseID string         `json:"tool_use_id,omitempty"`
	Input     map[string]any `json:"input,omitempty"`
	Content   string         `json:"content,omitempty"`
	IsError   bool           `json:"is_error,omitempty"`
}

ContentBlock represents an atomic unit of model output.

func (*ContentBlock) GetUnifiedToolID ΒΆ

func (b *ContentBlock) GetUnifiedToolID() string

GetUnifiedToolID returns a tool identifier suitable for matching calls with results.

type DecisionDetail ΒΆ added in v0.12.0

type DecisionDetail struct {
	Type    string `json:"type"` // "ask", "allow", "deny"
	Reason  string `json:"reason,omitempty"`
	Options []struct {
		Name string `json:"name"`
	} `json:"options,omitempty"`
}

DecisionDetail contains the permission decision details.

type EventMeta ΒΆ

type EventMeta struct {
	DurationMs       int64  `json:"duration_ms,omitempty"`
	TotalDurationMs  int64  `json:"total_duration_ms,omitempty"`
	ToolName         string `json:"tool_name,omitempty"`
	ToolID           string `json:"tool_id,omitempty"`
	Status           string `json:"status,omitempty"`
	ErrorMsg         string `json:"error_msg,omitempty"`
	InputTokens      int32  `json:"input_tokens,omitempty"`
	OutputTokens     int32  `json:"output_tokens,omitempty"`
	CacheWriteTokens int32  `json:"cache_write_tokens,omitempty"`
	CacheReadTokens  int32  `json:"cache_read_tokens,omitempty"`
	InputSummary     string `json:"input_summary,omitempty"`
	OutputSummary    string `json:"output_summary,omitempty"`
	FilePath         string `json:"file_path,omitempty"`
	LineCount        int32  `json:"line_count,omitempty"`
	Progress         int32  `json:"progress,omitempty"`
	TotalSteps       int32  `json:"total_steps,omitempty"`
	CurrentStep      int32  `json:"current_step,omitempty"`
}

EventMeta contains detailed metadata for streaming events.

type EventWithMeta ΒΆ

type EventWithMeta struct {
	EventType string     `json:"event_type"`
	EventData string     `json:"event_data"`
	Meta      *EventMeta `json:"meta,omitempty"`
}

EventWithMeta extends the basic event with metadata for observability.

func NewEventWithMeta ΒΆ

func NewEventWithMeta(eventType, eventData string, meta *EventMeta) *EventWithMeta

NewEventWithMeta creates a new EventWithMeta with guaranteed non-nil Meta.

type FinishReason ΒΆ added in v0.36.0

type FinishReason string

FinishReason represents the reason why a step finished

const (
	ReasonMaxTokens FinishReason = "max_tokens"
	ReasonToolUse   FinishReason = "tool_use"
	ReasonEndTurn   FinishReason = "end_turn"
)

type HTTPTransport ΒΆ added in v0.36.0

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

HTTPTransport implements Transport using HTTP clients. It connects to an opencode serve instance and provides SSE event streaming with automatic reconnection using exponential backoff. Uses separate HTTP clients for REST (with timeout) and SSE (no timeout).

func NewHTTPTransport ΒΆ added in v0.36.0

func NewHTTPTransport(cfg HTTPTransportConfig) *HTTPTransport

NewHTTPTransport creates a new HTTPTransport.

func (*HTTPTransport) Close ΒΆ added in v0.36.0

func (t *HTTPTransport) Close() error

Close stops the SSE goroutine and closes all subscriber channels. It is idempotent and goroutine-safe.

func (*HTTPTransport) Connect ΒΆ added in v0.36.0

func (t *HTTPTransport) Connect(ctx context.Context, cfg TransportConfig) error

Connect establishes the SSE connection to the server. It starts a background goroutine that reads SSE events and reconnects on failure.

func (*HTTPTransport) CreateSession ΒΆ added in v0.36.0

func (t *HTTPTransport) CreateSession(ctx context.Context, title string) (string, error)

CreateSession creates a new session on the server via POST /session.

func (*HTTPTransport) DeleteSession ΒΆ added in v0.36.0

func (t *HTTPTransport) DeleteSession(ctx context.Context, sessionID string) error

DeleteSession terminates a session via DELETE /session/:id.

func (*HTTPTransport) Events ΒΆ added in v0.36.0

func (t *HTTPTransport) Events() <-chan string

Events returns the SSE event channel (deprecated: use Subscribe for fan-out). Kept for backwards compatibility with single-session use cases.

func (*HTTPTransport) Health ΒΆ added in v0.36.0

func (t *HTTPTransport) Health(ctx context.Context) error

Health checks if the server is reachable via GET /global/health.

func (*HTTPTransport) RespondPermission ΒΆ added in v0.36.0

func (t *HTTPTransport) RespondPermission(ctx context.Context, sessionID, permissionID, response string) error

RespondPermission sends a permission response via POST /session/:id/permissions/:permID.

func (*HTTPTransport) Send ΒΆ added in v0.36.0

func (t *HTTPTransport) Send(ctx context.Context, sessionID string, message map[string]any) error

Send delivers a message to an existing session via POST /session/:id/prompt_async. This is an async endpoint that returns 204 immediately. Responses are delivered via SSE /event stream (see HTTPTransport.Events).

func (*HTTPTransport) Subscribe ΒΆ added in v0.36.0

func (t *HTTPTransport) Subscribe() <-chan string

Subscribe returns a new channel for receiving SSE events. Each subscriber gets its own channel, enabling fan-out to multiple sessions. The caller must call Unsubscribe when done to prevent memory leaks.

func (*HTTPTransport) Unsubscribe ΒΆ added in v0.36.0

func (t *HTTPTransport) Unsubscribe(ch <-chan string)

Unsubscribe removes a subscriber and closes its channel. Must be called when done with a subscription to prevent memory leaks.

type HTTPTransportConfig ΒΆ added in v0.36.0

type HTTPTransportConfig struct {
	Endpoint string
	Password string
	Logger   *slog.Logger
	Timeout  time.Duration
	WorkDir  string // Working directory for OpenCode Server context
}

TransportConfig for HTTPTransport.

type ModelUsageStats ΒΆ added in v0.17.0

type ModelUsageStats struct {
	InputTokens              int32   `json:"inputTokens"`
	OutputTokens             int32   `json:"outputTokens"`
	CacheReadInputTokens     int32   `json:"cacheReadInputTokens"`
	CacheCreationInputTokens int32   `json:"cacheCreationInputTokens"`
	WebSearchRequests        int32   `json:"webSearchRequests"` // WebSearchRequests is the count of tool-level web search operations.
	CostUSD                  float64 `json:"costUSD"`
	ContextWindow            int32   `json:"contextWindow"`   // ContextWindow is the model's total context capacity (tokens). Used for utilization calculation.
	MaxOutputTokens          int32   `json:"maxOutputTokens"` // MaxOutputTokens is the model's maximum output limit.
}

ModelUsageStats represents the token consumption per model.

type OCAssistantMessage ΒΆ added in v0.36.0

type OCAssistantMessage struct {
	ID        string   `json:"id,omitempty"`
	SessionID string   `json:"sessionID,omitempty"`
	Role      string   `json:"role,omitempty"`
	ModelID   string   `json:"modelID,omitempty"`
	Cost      float64  `json:"cost,omitempty"`
	Tokens    OCUsage  `json:"tokens"`
	Finish    string   `json:"finish,omitempty"`
	Error     *OCError `json:"error,omitempty"`
}

OCAssistantMessage is the info payload for message.updated events.

type OCCacheUsage ΒΆ added in v0.36.0

type OCCacheUsage struct {
	Read  int32 `json:"read,omitempty"`
	Write int32 `json:"write,omitempty"`
}

OCCacheUsage represents cache token usage.

type OCError ΒΆ added in v0.36.0

type OCError struct {
	Name    string         `json:"name"`
	Message string         `json:"message,omitempty"`
	Data    map[string]any `json:"data,omitempty"`
}

OCError represents an error from the OpenCode server.

type OCMessage ΒΆ added in v0.36.0

type OCMessage struct {
	ID      string   `json:"id,omitempty"`
	Role    string   `json:"role,omitempty"`
	Parts   []OCPart `json:"parts,omitempty"`
	Content string   `json:"content,omitempty"`
	Status  string   `json:"status,omitempty"`
	Error   string   `json:"error,omitempty"`
}

OCMessage represents the output message structure from OpenCode.

type OCPart ΒΆ added in v0.36.0

type OCPart struct {
	ID   string `json:"id,omitempty"`
	Type string `json:"type"`

	// text / reasoning
	Text string `json:"text,omitempty"`

	// tool (v1.3.2+ uses nested state and tool field)
	Tool   string         `json:"tool,omitempty"`
	Input  map[string]any `json:"input,omitempty"`
	Output string         `json:"output,omitempty"`
	Error  string         `json:"error,omitempty"`
	State  *OCPartState   `json:"state,omitempty"`

	// step
	StepNumber int    `json:"step_number,omitempty"`
	TotalSteps int    `json:"total_steps,omitempty"`
	Reason     string `json:"reason,omitempty"`

	// token usage
	Usage *OCUsage `json:"usage,omitempty"`

	// Legacy compatibility (fallback for older versions)
	Status string `json:"status,omitempty"`
	Name   string `json:"name,omitempty"`
}

OCPart represents a single part in an OpenCode message.

func (*OCPart) GetStatus ΒΆ added in v0.36.0

func (p *OCPart) GetStatus() string

GetStatus returns the effective status from nested state (v1.3.2+) or legacy field. Supports backward compatibility with older OpenCode versions.

func (*OCPart) GetToolName ΒΆ added in v0.36.0

func (p *OCPart) GetToolName() string

GetToolName returns the effective tool name from Tool field (v1.3.2+) or legacy Name field. Supports backward compatibility with older OpenCode versions.

type OCPartState ΒΆ added in v0.36.0

type OCPartState struct {
	Status string `json:"status,omitempty"` // pending, running, completed, error
}

OCPartState represents the nested state structure in v1.3.2+ tool parts.

type OCPartUpdateProps ΒΆ added in v0.36.0

type OCPartUpdateProps struct {
	Part  OCPart `json:"part"`
	Delta string `json:"delta,omitempty"`
}

OCPartUpdateProps is the properties payload for message.part.updated events.

type OCPermissionProps ΒΆ added in v0.36.0

type OCPermissionProps struct {
	ID        string `json:"id"`
	Type      string `json:"type"`
	SessionID string `json:"sessionID,omitempty"`
	Title     string `json:"title,omitempty"`
}

OCPermissionProps is the properties payload for permission.updated events.

type OCSSEEvent ΒΆ added in v0.36.0

type OCSSEEvent struct {
	Type       string          `json:"type"`
	Properties json.RawMessage `json:"properties,omitempty"`
}

OCSSEEvent is the SSE "data:" payload from opencode serve. Wraps a JSON object with a "type" field and "properties" field.

type OCSession ΒΆ added in v0.36.0

type OCSession struct {
	ID        string `json:"id"`
	ProjectID string `json:"projectID,omitempty"`
	Directory string `json:"directory,omitempty"`
	Title     string `json:"title"`
	Version   string `json:"version,omitempty"`
}

OCSession represents an OpenCode server-side session.

type OCSessionErrorProps ΒΆ added in v0.36.0

type OCSessionErrorProps struct {
	Error OCError `json:"error"`
}

OCSessionErrorProps is the properties payload for session.error events.

type OCSessionState ΒΆ added in v0.36.0

type OCSessionState struct {
	Type    string `json:"type"`
	Attempt int    `json:"attempt,omitempty"`
}

OCSessionState describes the current state of an OpenCode session.

type OCSessionStatusProps ΒΆ added in v0.36.0

type OCSessionStatusProps struct {
	Status OCSessionState `json:"status"`
}

OCSessionStatusProps is the properties payload for session.status events.

type OCTimeStamp ΒΆ added in v0.36.0

type OCTimeStamp struct {
	Created int64 `json:"created,omitempty"`
	Updated int64 `json:"updated,omitempty"`
}

OCTimeStamp represents a timestamp from OpenCode.

type OCUsage ΒΆ added in v0.36.0

type OCUsage struct {
	InputTokens  int32         `json:"input_tokens,omitempty"`
	OutputTokens int32         `json:"output_tokens,omitempty"`
	Cache        *OCCacheUsage `json:"cache,omitempty"`
}

OCUsage represents token usage information.

type OpenCodeConfig ΒΆ

type OpenCodeConfig struct {
	// UseHTTPAPI enables HTTP API mode instead of CLI mode
	UseHTTPAPI bool `json:"use_http_api,omitempty" koanf:"use_http_api"`

	// Port for HTTP API server
	Port int `json:"port,omitempty" koanf:"port"`

	// PlanMode enables planning mode
	PlanMode bool `json:"plan_mode,omitempty" koanf:"plan_mode"`

	// Provider is the LLM provider to use
	Provider string `json:"provider,omitempty" koanf:"provider"`

	// Model is the model ID
	Model string `json:"model,omitempty" koanf:"model"`

	// ServerURL is the HTTP endpoint for opencode serve (e.g., "http://127.0.0.1:4096").
	// Used by OpenCodeServerProvider (opencode-server type).
	ServerURL string `json:"server_url,omitempty" koanf:"server_url"`

	// Agent is the opencode serve --agent flag (e.g., "build", "plan").
	Agent string `json:"agent,omitempty" koanf:"agent"`

	// Password is the Basic Auth password for opencode serve.
	Password string `json:"password,omitempty" koanf:"password"`

	// WorkDir is the working directory for OpenCode Server context.
	// This is passed via x-opencode-directory HTTP header to specify project directory.
	WorkDir string `json:"work_dir,omitempty" koanf:"work_dir"`
}

OpenCodeConfig contains OpenCode-specific configuration.

type OpenCodeServerProvider ΒΆ added in v0.36.0

type OpenCodeServerProvider struct {
	ProviderBase
	// contains filtered or unexported fields
}

OpenCodeServerProvider implements the Provider interface for OpenCode HTTP API mode. Unlike the CLI-based OpenCodeProvider, this uses HTTP transport to communicate with a running opencode serve instance.

func NewOpenCodeServerProvider ΒΆ added in v0.36.0

func NewOpenCodeServerProvider(cfg ProviderConfig, logger *slog.Logger) (*OpenCodeServerProvider, error)

NewOpenCodeServerProvider creates a new HTTP-based OpenCode provider.

func (*OpenCodeServerProvider) BuildCLIArgs ΒΆ added in v0.36.0

BuildCLIArgs returns nil for server mode (no CLI subprocess).

func (*OpenCodeServerProvider) BuildInputMessage ΒΆ added in v0.36.0

func (p *OpenCodeServerProvider) BuildInputMessage(prompt, taskInstructions string) (map[string]any, error)

BuildInputMessage creates an input message for the OpenCode API.

func (*OpenCodeServerProvider) CleanupSession ΒΆ added in v0.36.0

func (p *OpenCodeServerProvider) CleanupSession(_ string, _ string) error

CleanupSession removes session resources.

func (*OpenCodeServerProvider) Close ΒΆ added in v0.36.0

func (p *OpenCodeServerProvider) Close() error

Close closes the provider and releases resources.

func (*OpenCodeServerProvider) Connect ΒΆ added in v0.36.0

Connect establishes connection to the OpenCode server.

func (*OpenCodeServerProvider) DetectTurnEnd ΒΆ added in v0.36.0

func (p *OpenCodeServerProvider) DetectTurnEnd(e *ProviderEvent) bool

DetectTurnEnd detects if an event indicates the end of a turn.

func (*OpenCodeServerProvider) Events ΒΆ added in v0.36.0

func (p *OpenCodeServerProvider) Events() <-chan string

Events returns the SSE event channel (deprecated: use Subscribe for fan-out).

func (*OpenCodeServerProvider) GetHTTPTransport ΒΆ added in v0.36.0

func (p *OpenCodeServerProvider) GetHTTPTransport() Transport

GetHTTPTransport returns the underlying HTTP transport for HTTPSessionStarter. This method is used by the engine to create HTTP-based sessions.

func (*OpenCodeServerProvider) ParseEvent ΒΆ added in v0.36.0

func (p *OpenCodeServerProvider) ParseEvent(line string) ([]*ProviderEvent, error)

ParseEvent parses an SSE event line into provider events.

func (*OpenCodeServerProvider) ValidateBinary ΒΆ added in v0.36.0

func (p *OpenCodeServerProvider) ValidateBinary() (string, error)

ValidateBinary checks if the OpenCode server is reachable.

type PendingPermissionRequest ΒΆ added in v0.12.0

type PendingPermissionRequest struct {
	ID             string // Unique ID (messageID or generated)
	SessionID      string // Claude Code session ID
	Request        *PermissionRequest
	ChannelID      string           // Slack channel ID
	MessageTS      string           // Slack message timestamp for update
	UserID         string           // Slack user who triggered the request
	CreatedAt      time.Time        // When the request was created
	ExpiresAt      time.Time        // When the request expires (timeout)
	SlackMessageTS string           // TS of the Slack message with buttons
	Status         PermissionStatus // Current status
}

PendingPermissionRequest tracks a pending permission request waiting for user response.

func (*PendingPermissionRequest) IsExpired ΒΆ added in v0.12.0

func (p *PendingPermissionRequest) IsExpired() bool

IsExpired returns true if the pending request has expired.

func (*PendingPermissionRequest) TimeUntilExpiry ΒΆ added in v0.12.0

func (p *PendingPermissionRequest) TimeUntilExpiry() time.Duration

TimeUntilExpiry returns the duration until the request expires.

type PermissionBehavior ΒΆ added in v0.12.0

type PermissionBehavior string

PermissionBehavior is the user's decision for a permission request.

const (
	PermissionBehaviorAllow       PermissionBehavior = "allow"
	PermissionBehaviorDeny        PermissionBehavior = "deny"
	PermissionBehaviorAllowAlways PermissionBehavior = "allow_always"
	PermissionBehaviorDenyAlways  PermissionBehavior = "deny_always"
)

type PermissionDeniedDetail ΒΆ added in v0.31.7

type PermissionDeniedDetail struct {
	ToolName  string         `json:"tool_name"`            // Name of the denied tool (e.g., "Edit", "Bash")
	ToolUseID string         `json:"tool_use_id"`          // Unique ID for correlating with tool_use event
	ToolInput map[string]any `json:"tool_input,omitempty"` // The tool input that was denied
}

PermissionDeniedDetail represents a tool permission denial in Claude Code's result event. This is embedded in the "result" type event's permission_denials array when the user denies a tool execution request (stop_reason="tool_disallowed").

type PermissionDetail ΒΆ added in v0.12.0

type PermissionDetail struct {
	Name  string `json:"name"`            // Tool name (e.g., "bash", "Read", "Edit")
	Input string `json:"input,omitempty"` // Tool input (e.g., command to execute)
}

PermissionDetail contains the permission details (legacy format). Used when Claude Code requests permission for a specific tool/action.

type PermissionRequest ΒΆ added in v0.12.0

type PermissionRequest struct {
	Type       string            `json:"type"`
	SessionID  string            `json:"session_id,omitempty"`
	MessageID  string            `json:"message_id,omitempty"`
	Decision   *DecisionDetail   `json:"decision,omitempty"`
	Permission *PermissionDetail `json:"permission,omitempty"` // Legacy format
}

PermissionRequest represents a permission request from Claude Code. Format as described in GitHub Issue #39. Note: Claude Code has two permission request formats: 1. Legacy format with "permission" object: {"type":"permission_request","permission":{"name":"bash","input":"cmd"}} 2. Current format with "decision" object: {"type":"permission_request","decision":{"type":"ask","options":[...]}}

func ParsePermissionRequest ΒΆ added in v0.12.0

func ParsePermissionRequest(data []byte) (*PermissionRequest, error)

ParsePermissionRequest parses a permission request from JSON. It handles both legacy (permission) and current (decision) formats.

func (*PermissionRequest) GetDescription ΒΆ added in v0.12.0

func (p *PermissionRequest) GetDescription() string

GetDescription returns a human-readable description of the permission request.

func (*PermissionRequest) GetToolAndInput ΒΆ added in v0.12.0

func (p *PermissionRequest) GetToolAndInput() (tool string, input string)

GetToolAndInput extracts the tool name and input from a permission request. Handles both legacy and current formats.

func (*PermissionRequest) IsLegacy ΒΆ added in v0.12.0

func (p *PermissionRequest) IsLegacy() bool

IsLegacy returns true if this is a legacy format permission request.

type PermissionResponse ΒΆ added in v0.12.0

type PermissionResponse struct {
	Behavior string `json:"behavior"`
	Message  string `json:"message,omitempty"`
}

PermissionResponse represents the response sent to Claude Code stdin. Format: {"behavior": "allow"} or {"behavior": "deny", "message": "User rejected"}

type PermissionStatus ΒΆ added in v0.12.0

type PermissionStatus string

PermissionStatus is the status of a pending permission request.

const (
	PermissionStatusPending  PermissionStatus = "pending"
	PermissionStatusAllowed  PermissionStatus = "allowed"
	PermissionStatusDenied   PermissionStatus = "denied"
	PermissionStatusExpired  PermissionStatus = "expired"
	PermissionStatusTimedOut PermissionStatus = "timed_out"
)

type PermissionTool ΒΆ added in v0.12.0

type PermissionTool string

PermissionTool is the type of tool requesting permission.

const (
	PermissionToolBash      PermissionTool = "Bash"
	PermissionToolRead      PermissionTool = "Read"
	PermissionToolEdit      PermissionTool = "Edit"
	PermissionToolWrite     PermissionTool = "Write"
	PermissionToolMultiEdit PermissionTool = "MultiEdit"
)

type PiAgentEvent ΒΆ added in v0.21.0

type PiAgentEvent struct {
	Type     string           `json:"type"`
	Messages []PiAgentMessage `json:"messages,omitempty"`
	Message  *PiAgentMessage  `json:"message,omitempty"`
}

PiAgentEvent represents agent lifecycle events.

type PiAgentMessage ΒΆ added in v0.21.0

type PiAgentMessage struct {
	Role         string           `json:"role"`
	Content      []PiContentBlock `json:"content,omitempty"`
	Timestamp    int64            `json:"timestamp,omitempty"`
	Provider     string           `json:"provider,omitempty"`
	Model        string           `json:"model,omitempty"`
	API          string           `json:"api,omitempty"`
	Usage        *PiUsage         `json:"usage,omitempty"`
	StopReason   string           `json:"stopReason,omitempty"`
	ErrorMessage string           `json:"errorMessage,omitempty"`
}

PiAgentMessage represents a message in the pi event stream.

type PiAssistantMessageEvent ΒΆ added in v0.21.0

type PiAssistantMessageEvent struct {
	Type  string `json:"type"`
	Delta string `json:"delta,omitempty"`
}

PiAssistantMessageEvent represents message update events.

type PiConfig ΒΆ added in v0.21.0

type PiConfig struct {
	// Provider is the LLM provider to use (anthropic, openai, google, etc.)
	Provider string `json:"provider,omitempty" koanf:"provider"`

	// Model is the model ID or pattern (supports provider/id format)
	Model string `json:"model,omitempty" koanf:"model"`

	// Thinking level: off, minimal, low, medium, high, xhigh
	Thinking string `json:"thinking,omitempty" koanf:"thinking"`

	// UseRPC enables RPC mode for process integration (stdin/stdout)
	UseRPC bool `json:"use_rpc,omitempty" koanf:"use_rpc"`

	// SessionDir custom session storage directory
	SessionDir string `json:"session_dir,omitempty" koanf:"session_dir"`

	// NoSession enables ephemeral mode (don't save session)
	NoSession bool `json:"no_session,omitempty" koanf:"no_session"`
}

PiConfig contains pi-mono (pi-coding-agent) specific configuration. Pi supports multiple LLM providers through a unified API.

type PiContentBlock ΒΆ added in v0.21.0

type PiContentBlock struct {
	Type      string         `json:"type"`
	Text      string         `json:"text,omitempty"`
	Thinking  string         `json:"thinking,omitempty"`
	ID        string         `json:"id,omitempty"`
	Name      string         `json:"name,omitempty"`
	Arguments map[string]any `json:"arguments,omitempty"`
	Data      string         `json:"data,omitempty"`
	MimeType  string         `json:"mimeType,omitempty"`
}

PiContentBlock represents a content block in a pi message.

type PiMessageUpdateEvent ΒΆ added in v0.21.0

type PiMessageUpdateEvent struct {
	Type                  string                   `json:"type"`
	Message               *PiAgentMessage          `json:"message"`
	AssistantMessageEvent *PiAssistantMessageEvent `json:"assistantMessageEvent,omitempty"`
}

PiMessageUpdateEvent represents a message_update event.

type PiProvider ΒΆ added in v0.21.0

type PiProvider struct {
	ProviderBase
	// contains filtered or unexported fields
}

PiProvider implements the Provider interface for pi-coding-agent CLI. Pi is a minimal terminal coding harness that supports 15+ LLM providers through a unified API. It outputs events in JSON Lines format.

Key features:

  • Multi-provider support (Anthropic, OpenAI, Google, etc.)
  • JSON mode for structured output
  • RPC mode for process integration
  • Session management with JSONL storage

func NewPiProvider ΒΆ added in v0.21.0

func NewPiProvider(cfg ProviderConfig, logger *slog.Logger) (*PiProvider, error)

NewPiProvider creates a new pi provider instance.

func (*PiProvider) BuildCLIArgs ΒΆ added in v0.21.0

func (p *PiProvider) BuildCLIArgs(providerSessionID string, opts *ProviderSessionOptions) []string

BuildCLIArgs constructs pi CLI arguments.

func (*PiProvider) BuildInputMessage ΒΆ added in v0.21.0

func (p *PiProvider) BuildInputMessage(prompt string, taskInstructions string) (map[string]any, error)

BuildInputMessage constructs the input for pi. Note: Pi typically takes the prompt as a CLI argument, not stdin.

func (*PiProvider) CleanupSession ΒΆ added in v0.21.0

func (p *PiProvider) CleanupSession(providerSessionID string, workDir string) error

CleanupSession cleans up pi session files. Pi stores sessions in ~/.pi/agent/sessions/ as JSONL files.

func (*PiProvider) DetectTurnEnd ΒΆ added in v0.21.0

func (p *PiProvider) DetectTurnEnd(event *ProviderEvent) bool

DetectTurnEnd checks if the event signals turn completion.

func (*PiProvider) ParseEvent ΒΆ added in v0.21.0

func (p *PiProvider) ParseEvent(line string) ([]*ProviderEvent, error)

ParseEvent parses a pi JSON output line into one or more ProviderEvents.

func (*PiProvider) ValidateBinary ΒΆ added in v0.21.0

func (p *PiProvider) ValidateBinary() (string, error)

ValidateBinary checks if the pi CLI is available.

type PiSessionEvent ΒΆ added in v0.21.0

type PiSessionEvent struct {
	Type      string `json:"type"`
	Version   int    `json:"version"`
	ID        string `json:"id"`
	Timestamp string `json:"timestamp"`
	Cwd       string `json:"cwd"`
}

PiSessionEvent represents the session header event.

type PiToolExecutionEvent ΒΆ added in v0.21.0

type PiToolExecutionEvent struct {
	Type          string         `json:"type"`
	ToolCallID    string         `json:"toolCallId"`
	ToolName      string         `json:"toolName"`
	Args          map[string]any `json:"args,omitempty"`
	Result        any            `json:"result,omitempty"`
	PartialResult any            `json:"partialResult,omitempty"`
	IsError       bool           `json:"isError,omitempty"`
}

PiToolExecutionEvent represents tool execution events.

type PiUsage ΒΆ added in v0.21.0

type PiUsage struct {
	InputTokens  int32 `json:"input_tokens"`
	OutputTokens int32 `json:"output_tokens"`
}

PiUsage represents token usage information.

type PluginMetadataError ΒΆ added in v0.22.0

type PluginMetadataError struct {
	Type    ProviderType
	Message string
}

PluginMetadataError is returned when plugin metadata validation fails.

func (*PluginMetadataError) Error ΒΆ added in v0.22.0

func (e *PluginMetadataError) Error() string

type PromptBuilder ΒΆ added in v0.22.0

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

PromptBuilder helps construct prompts with task instructions. It provides a consistent format across all providers.

func NewPromptBuilder ΒΆ added in v0.22.0

func NewPromptBuilder(useCDATA bool) *PromptBuilder

NewPromptBuilder creates a new PromptBuilder.

func (*PromptBuilder) Build ΒΆ added in v0.22.0

func (b *PromptBuilder) Build(prompt, taskInstructions string) string

Build constructs the final prompt with task instructions. If taskInstructions is empty, returns the prompt unchanged.

func (*PromptBuilder) BuildInputMessage ΒΆ added in v0.22.0

func (b *PromptBuilder) BuildInputMessage(prompt, taskInstructions string) map[string]any

BuildInputMessage creates a standard input message map for JSON serialization.

type Provider ΒΆ

type Provider interface {
	// Metadata returns the provider's identity and capabilities.
	Metadata() ProviderMeta

	// BuildCLIArgs constructs the command-line arguments for starting the CLI process.
	// The sessionID is the internal SDK identifier, providerSessionID is the
	// provider-specific persistent session identifier (e.g., Claude's --session-id).
	BuildCLIArgs(providerSessionID string, opts *ProviderSessionOptions) []string

	// BuildInputMessage constructs the stdin message payload for sending user input.
	// This handles provider-specific input formatting (e.g., stream-json for Claude).
	BuildInputMessage(prompt string, taskInstructions string) (map[string]any, error)

	// ParseEvent parses a raw output line into one or more normalized ProviderEvents.
	// Returns empty slice if the line should be ignored (e.g., system messages).
	// Returns an error if parsing fails critically.
	ParseEvent(line string) ([]*ProviderEvent, error)

	// DetectTurnEnd checks if the event indicates the end of a turn.
	// Different providers signal turn completion differently:
	// - Claude Code: type="result"
	// - OpenCode: step-finish or specific completion marker
	DetectTurnEnd(event *ProviderEvent) bool

	// ValidateBinary checks if the CLI binary is available and returns its path.
	ValidateBinary() (string, error)

	// CleanupSession cleans up provider-specific session files from disk (e.g. for /reset).
	CleanupSession(providerSessionID string, workDir string) error

	// VerifySession checks if a session can be resumed (i.e., session data exists on disk).
	// This is called before attempting to resume a session to avoid "No conversation found" errors.
	// Returns true if the session exists and can be resumed, false otherwise.
	VerifySession(providerSessionID string, workDir string) bool

	// Name returns the provider name for logging and identification.
	Name() string
}

Provider defines the interface for AI CLI agent providers. Each provider (Claude Code, OpenCode) implements this interface to handle its specific CLI protocol, argument construction, and event parsing.

The interface follows the Strategy Pattern, allowing HotPlex Engine to switch between different AI CLI tools without modifying core logic.

func CreateDefaultProvider ΒΆ

func CreateDefaultProvider(t ProviderType) (Provider, error)

CreateDefaultProvider is a convenience function for creating providers with defaults.

func CreateProvider ΒΆ

func CreateProvider(cfg ProviderConfig) (Provider, error)

CreateProvider is a convenience function that uses the global factory.

type ProviderBase ΒΆ

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

ProviderBase provides common functionality for provider implementations. Embed this struct to reduce boilerplate in concrete providers.

func (*ProviderBase) CleanupSession ΒΆ added in v0.17.0

func (p *ProviderBase) CleanupSession(providerSessionID string, workDir string) error

CleanupSession provides a default no-op implementation for cleaning up session files. Providers that store session files on disk (like Claude Code) should override this.

func (*ProviderBase) Metadata ΒΆ

func (p *ProviderBase) Metadata() ProviderMeta

Metadata returns the provider metadata.

func (*ProviderBase) Name ΒΆ

func (p *ProviderBase) Name() string

Name returns the provider name.

func (*ProviderBase) ValidateBinary ΒΆ

func (p *ProviderBase) ValidateBinary() (string, error)

ValidateBinary checks if the CLI binary exists and returns its path. It uses BinaryPath from config if set, otherwise looks up the binary in PATH. Returns a helpful error message with install hint if the binary is not found.

func (*ProviderBase) VerifySession ΒΆ added in v0.29.0

func (p *ProviderBase) VerifySession(providerSessionID string, workDir string) bool

VerifySession provides a default implementation that always returns true. Providers that support session resumption should override this to check if session data exists.

type ProviderConfig ΒΆ

type ProviderConfig struct {
	// Type identifies the provider (required)
	Type ProviderType `json:"type" yaml:"type" koanf:"type"`

	// Enabled controls whether this provider is available
	Enabled *bool `json:"enabled" yaml:"enabled" koanf:"enabled"`

	// ExplicitDisable explicitly disables the provider, overriding base config's Enabled=true.
	// This is needed because bool zero value (false) cannot be distinguished from "not set"
	// in config merging. Use this when you want to disable a provider in overlay config.
	ExplicitDisable bool `json:"explicit_disable,omitempty" yaml:"explicit_disable,omitempty" koanf:"explicit_disable"`

	// BinaryPath overrides the default binary lookup path
	BinaryPath string `json:"binary_path,omitempty" yaml:"binary_path,omitempty" koanf:"binary_path"`

	// DefaultModel is the default model to use
	DefaultModel string `json:"default_model,omitempty" yaml:"default_model,omitempty" koanf:"default_model"`

	// DefaultPermissionMode is the default permission mode
	DefaultPermissionMode string `json:"default_permission_mode,omitempty" yaml:"default_permission_mode,omitempty" koanf:"default_permission_mode"`

	// DangerouslySkipPermissions bypasses all permission checks.
	// Equivalent to --permission-mode bypassPermissions but skips permission prompts entirely.
	// Recommended only for sandboxes with no internet access.
	DangerouslySkipPermissions *bool `` /* 128-byte string literal not displayed */

	// AllowedTools restricts available tools (provider-level override)
	AllowedTools []string `json:"allowed_tools,omitempty" yaml:"allowed_tools,omitempty" koanf:"allowed_tools"`

	// DisallowedTools blocks specific tools (provider-level override)
	DisallowedTools []string `json:"disallowed_tools,omitempty" yaml:"disallowed_tools,omitempty" koanf:"disallowed_tools"`

	// ExtraArgs are additional CLI arguments
	ExtraArgs []string `json:"extra_args,omitempty" yaml:"extra_args,omitempty" koanf:"extra_args"`

	// ExtraEnv are additional environment variables
	ExtraEnv map[string]string `json:"extra_env,omitempty" yaml:"extra_env,omitempty" koanf:"extra_env"`

	// Timeout overrides the default execution timeout
	Timeout time.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty" koanf:"timeout"`

	// OpenCode-specific options
	OpenCode *OpenCodeConfig `json:"opencode,omitempty" yaml:"opencode,omitempty" koanf:"opencode"`

	// Pi-specific options
	Pi *PiConfig `json:"pi,omitempty" yaml:"pi,omitempty" koanf:"pi"`
}

ProviderConfig defines the configuration for a specific provider instance. This is used in the layered configuration system.

func MergeProviderConfigs ΒΆ

func MergeProviderConfigs(base, overlay ProviderConfig) ProviderConfig

MergeProviderConfigs merges multiple provider configurations with precedence. Later configurations override earlier ones for non-zero values.

Note: For boolean fields like Enabled, false cannot override true because false is the zero value. Use ExplicitDisable field in ProviderConfig if you need to explicitly disable a provider in an overlay config.

func (*ProviderConfig) Validate ΒΆ

func (c *ProviderConfig) Validate() error

Validate validates the provider configuration. Returns an error if required fields are missing or invalid.

type ProviderContentBlock ΒΆ

type ProviderContentBlock struct {
	Type      string         `json:"type"`
	Text      string         `json:"text,omitempty"`
	Name      string         `json:"name,omitempty"`
	ID        string         `json:"id,omitempty"`
	ToolUseID string         `json:"tool_use_id,omitempty"`
	Input     map[string]any `json:"input,omitempty"`
	Content   string         `json:"content,omitempty"`
	IsError   bool           `json:"is_error,omitempty"`
}

ProviderContentBlock represents a structured content block within an event.

type ProviderCreator ΒΆ

type ProviderCreator func(cfg ProviderConfig, logger *slog.Logger) (Provider, error)

ProviderCreator is a function that creates a new Provider instance.

type ProviderEvent ΒΆ

type ProviderEvent struct {
	// Type is the normalized event type
	Type ProviderEventType `json:"type"`

	// RawType is the original type string from the provider (for debugging)
	RawType string `json:"raw_type,omitempty"`

	// Timestamp of the event
	Timestamp time.Time `json:"timestamp,omitempty"`

	// SessionID is the provider-specific session identifier
	SessionID string `json:"session_id,omitempty"`

	// Content contains the main event payload
	Content string `json:"content,omitempty"`

	// Blocks contains structured content blocks (if applicable)
	Blocks []ProviderContentBlock `json:"blocks,omitempty"`

	// Tool information (for tool_use and tool_result events)
	ToolName  string         `json:"tool_name,omitempty"`
	ToolID    string         `json:"tool_id,omitempty"`
	ToolInput map[string]any `json:"tool_input,omitempty"`

	// Status indicates operation status ("running", "success", "error")
	Status string `json:"status,omitempty"`

	// Error contains error message if applicable
	Error   string `json:"error,omitempty"`
	IsError bool   `json:"is_error,omitempty"`

	// Metadata contains additional provider-specific information
	Metadata *ProviderEventMeta `json:"metadata,omitempty"`

	// RawLine preserves the original JSON line for debugging
	RawLine string `json:"-"`
}

ProviderEvent represents a normalized event from any AI CLI provider. This unified model allows the HotPlex Engine to handle events consistently regardless of the underlying provider.

func ParseProviderEvent ΒΆ

func ParseProviderEvent(line string) (*ProviderEvent, error)

ParseProviderEvent parses a JSON line into a ProviderEvent. This is a generic parser; providers should implement custom parsing for their specific event formats.

func (*ProviderEvent) GetFirstTextBlock ΒΆ

func (e *ProviderEvent) GetFirstTextBlock() string

GetFirstTextBlock extracts the first text block content from the event.

func (*ProviderEvent) HasToolInfo ΒΆ

func (e *ProviderEvent) HasToolInfo() bool

HasToolInfo returns true if this event contains tool information.

func (*ProviderEvent) IsTerminalEvent ΒΆ

func (e *ProviderEvent) IsTerminalEvent() bool

IsTerminalEvent returns true if this event indicates the turn is complete.

func (*ProviderEvent) ToEventWithMeta ΒΆ

func (e *ProviderEvent) ToEventWithMeta() *EventWithMeta

ToEventWithMeta converts ProviderEvent to the existing EventWithMeta type. This provides backward compatibility with the existing event system.

func (*ProviderEvent) ToJSON ΒΆ

func (e *ProviderEvent) ToJSON() (string, error)

ToJSON returns the JSON representation of the event.

type ProviderEventMeta ΒΆ

type ProviderEventMeta struct {
	// Timing information
	DurationMs      int64 `json:"duration_ms,omitempty"`
	TotalDurationMs int64 `json:"total_duration_ms,omitempty"`

	// Token usage
	InputTokens      int32 `json:"input_tokens,omitempty"`
	OutputTokens     int32 `json:"output_tokens,omitempty"`
	CacheWriteTokens int32 `json:"cache_write_tokens,omitempty"`
	CacheReadTokens  int32 `json:"cache_read_tokens,omitempty"`

	// Cost information
	TotalCostUSD float64 `json:"total_cost_usd,omitempty"`

	// Model information
	Model           string `json:"model,omitempty"`
	ModelName       string `json:"model_name,omitempty"`        // ModelName is the primary model name from modelUsage (the most active model).
	ContextWindow   int32  `json:"context_window,omitempty"`    // ContextWindow is the model's total context capacity (tokens).
	MaxOutputTokens int32  `json:"max_output_tokens,omitempty"` // MaxOutputTokens is the model's maximum output limit.

	// Context usage percentage (priority over manual calculation)
	ContextUsedPercent *float64 `json:"context_used_percent,omitempty"` // ContextUsedPercent is the direct percentage from provider (e.g., Claude Code provides this).

	// Progress tracking
	Progress    int32 `json:"progress,omitempty"`
	TotalSteps  int32 `json:"total_steps,omitempty"`
	CurrentStep int32 `json:"current_step,omitempty"`
}

ProviderEventMeta contains additional metadata for observability.

type ProviderEventParser ΒΆ

type ProviderEventParser interface {
	// Parse converts a raw output line to a normalized ProviderEvent.
	Parse(line string) (*ProviderEvent, error)

	// IsTurnEnd returns true if the event signals turn completion.
	IsTurnEnd(event *ProviderEvent) bool

	// ExtractSessionID extracts the provider session ID from an event.
	ExtractSessionID(event *ProviderEvent) string
}

ProviderEventParser defines the interface for parsing provider-specific events. Each provider implements this to convert their raw output to normalized events.

type ProviderEventType ΒΆ

type ProviderEventType string

ProviderEventType defines the normalized event types across all providers. These types abstract away provider-specific event names to provide a unified event model for the HotPlex Engine and downstream consumers.

const (
	// EventTypeThinking indicates the AI is reasoning or thinking.
	// Claude Code: type="thinking" or type="status"
	// OpenCode: Part.Type="reasoning"
	EventTypeThinking ProviderEventType = "thinking"

	// EventTypeAnswer indicates text output from the AI.
	// Claude Code: type="assistant" with text blocks
	// OpenCode: Part.Type="text"
	EventTypeAnswer ProviderEventType = "answer"

	// EventTypeToolUse indicates a tool invocation is starting.
	// Claude Code: type="tool_use"
	// OpenCode: Part.Type="tool"
	EventTypeToolUse ProviderEventType = "tool_use"

	// EventTypeToolResult indicates a tool execution result.
	// Claude Code: type="tool_result"
	// OpenCode: Part.Type="tool" with result content
	EventTypeToolResult ProviderEventType = "tool_result"

	// EventTypeError indicates an error occurred.
	EventTypeError ProviderEventType = "error"

	// EventTypeResult indicates the turn has completed with final result.
	// Claude Code: type="result"
	// OpenCode: step-finish or completion marker
	EventTypeResult ProviderEventType = "result"

	// EventTypeSystem indicates a system-level message (often filtered).
	EventTypeSystem ProviderEventType = "system"

	// EventTypeUser indicates a user message reflection (often filtered).
	EventTypeUser ProviderEventType = "user"

	// EventTypeStepStart indicates a new step/milestone (OpenCode specific).
	EventTypeStepStart ProviderEventType = "step_start"

	// EventTypeStepFinish indicates a step/milestone completed (OpenCode specific).
	EventTypeStepFinish ProviderEventType = "step_finish"

	// EventTypeRaw indicates unparsed raw output (fallback).
	EventTypeRaw ProviderEventType = "raw"

	// EventTypePermissionRequest indicates a permission request from Claude Code.
	// This event type is used when Claude Code requests user approval for tool execution.
	// Format: {"type":"permission_request","session_id":"...","permission":{"name":"Bash","input":"..."}}
	EventTypePermissionRequest ProviderEventType = "permission_request"

	// EventTypePlanMode indicates Claude is in Plan Mode and generating a plan.
	// Claude Code: type="thinking" with subtype="plan_generation"
	// In this mode, Claude analyzes and plans but does not execute any tools.
	EventTypePlanMode ProviderEventType = "plan_mode"

	// EventTypeExitPlanMode indicates Claude has completed planning and requests user approval.
	// Claude Code: type="tool_use" with name="ExitPlanMode"
	// The plan content is in input.plan field.
	EventTypeExitPlanMode ProviderEventType = "exit_plan_mode"

	// EventTypeAskUserQuestion indicates Claude is asking a clarifying question.
	// Claude Code: type="tool_use" with name="AskUserQuestion"
	// Note: This feature is primarily for interactive mode; headless mode may not support stdin responses.
	// HotPlex handles this as a degraded text prompt (user replies via message).
	EventTypeAskUserQuestion ProviderEventType = "ask_user_question"

	// EventTypeCommandProgress indicates a slash command is executing with progress updates.
	// Used by /reset, /dc, and future slash commands to show step-by-step execution status.
	EventTypeCommandProgress ProviderEventType = "command_progress"

	// EventTypeCommandComplete indicates a slash command has completed successfully.
	EventTypeCommandComplete ProviderEventType = "command_complete"

	// EventTypeSessionStart indicates a new session is starting (cold start).
	// Sent when user sends first message or CLI needs cold start.
	EventTypeSessionStart ProviderEventType = "session_start"

	// EventTypeEngineStarting indicates the engine is starting up.
	// Sent during CLI cold start when engine is being initialized.
	EventTypeEngineStarting ProviderEventType = "engine_starting"

	// EventTypeUserMessageReceived indicates user message has been received.
	// Sent immediately after user message is received to acknowledge receipt.
	EventTypeUserMessageReceived ProviderEventType = "user_message_received"

	// EventTypePermissionDenied indicates a tool permission was denied by user.
	// Claude Code: type="result" with permission_denials array (stop_reason="tool_disallowed")
	// This event is emitted when Claude Code tries to execute a tool but the user denies permission.
	EventTypePermissionDenied ProviderEventType = "permission_denied"
)

type ProviderFactory ΒΆ

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

ProviderFactory creates Provider instances based on configuration. It implements the Factory Pattern to decouple provider creation from usage.

var GlobalProviderFactory *ProviderFactory

GlobalProviderFactory is the default factory instance. It comes pre-registered with built-in providers.

func NewProviderFactory ΒΆ

func NewProviderFactory(logger *slog.Logger) *ProviderFactory

NewProviderFactory creates a new provider factory with default providers registered.

func (*ProviderFactory) Create ΒΆ

func (f *ProviderFactory) Create(cfg ProviderConfig) (Provider, error)

Create creates a new Provider instance based on the configuration.

func (*ProviderFactory) CreateDefault ΒΆ

func (f *ProviderFactory) CreateDefault(t ProviderType) (Provider, error)

CreateDefault creates a Provider with default configuration.

func (*ProviderFactory) GetPlugin ΒΆ added in v0.22.0

GetPlugin retrieves a registered plugin by type. Returns nil if the plugin is not registered.

func (*ProviderFactory) IsRegistered ΒΆ

func (f *ProviderFactory) IsRegistered(t ProviderType) bool

IsRegistered checks if a provider type is registered.

func (*ProviderFactory) ListRegistered ΒΆ

func (f *ProviderFactory) ListRegistered() []ProviderType

ListRegistered returns a list of registered provider types.

func (*ProviderFactory) Register ΒΆ

func (f *ProviderFactory) Register(t ProviderType, creator ProviderCreator)

Register adds a new provider creator to the factory. If a creator for the given type already exists, it will be replaced.

type ProviderFeatures ΒΆ

type ProviderFeatures struct {
	SupportsResume             bool // Can resume existing sessions (e.g., --resume)
	SupportsStreamJSON         bool // Supports stream-json input/output format
	SupportsSSE                bool // Supports Server-Sent Events output
	SupportsHTTPAPI            bool // Has HTTP API mode
	SupportsSessionID          bool // Supports explicit session ID assignment
	SupportsPermissions        bool // Supports permission modes
	MultiTurnReady             bool // Can handle multiple turns in one session
	RequiresInitialPromptAsArg bool // Requires first prompt to be passed via CLI args instead of stdin
}

ProviderFeatures describes the capabilities of a provider.

type ProviderMeta ΒΆ

type ProviderMeta struct {
	Type        ProviderType // Provider type identifier
	DisplayName string       // Human-readable name (e.g., "Claude Code")
	BinaryName  string       // CLI binary name (e.g., "claude", "opencode")
	InstallHint string       // Installation hint (e.g., "npm install -g @anthropic-ai/claude-code")
	Version     string       // CLI version (if available)
	Features    ProviderFeatures
}

ProviderMeta contains metadata about a provider.

type ProviderPlugin ΒΆ added in v0.22.0

type ProviderPlugin interface {
	// Type returns the unique provider type identifier.
	Type() ProviderType

	// New creates a new Provider instance with the given configuration.
	// The logger may be nil, in which case the default logger should be used.
	New(cfg ProviderConfig, logger *slog.Logger) (Provider, error)

	// Meta returns the provider's metadata including capabilities.
	Meta() ProviderMeta
}

ProviderPlugin defines the interface for provider plugins. Third-party extensions implement this interface to register custom providers without modifying HotPlex core code.

Usage:

// external/aider/provider.go
type aiderPlugin struct{}

func (p *aiderPlugin) Type() ProviderType { return "aider" }
func (p *aiderPlugin) New(cfg ProviderConfig, logger *slog.Logger) (Provider, error) { ... }
func (p *aiderPlugin) Meta() ProviderMeta { ... }

func init() {
    provider.RegisterPlugin(&aiderPlugin{})
}

func GetPlugin ΒΆ added in v0.22.0

func GetPlugin(t ProviderType) ProviderPlugin

GetPlugin retrieves a registered plugin by type. Returns nil if the plugin is not registered.

type ProviderRegistry ΒΆ

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

ProviderRegistry maintains a cache of initialized providers. This is useful for reusing provider instances across sessions.

func NewProviderRegistry ΒΆ

func NewProviderRegistry(factory *ProviderFactory, logger *slog.Logger) *ProviderRegistry

NewProviderRegistry creates a new provider registry.

func (*ProviderRegistry) Clear ΒΆ

func (r *ProviderRegistry) Clear()

Clear removes all providers from the cache.

func (*ProviderRegistry) Get ΒΆ

Get retrieves a cached provider or creates a new one.

func (*ProviderRegistry) GetOrCreate ΒΆ

func (r *ProviderRegistry) GetOrCreate(t ProviderType) (Provider, error)

GetOrCreate retrieves a cached provider or creates one with default config.

func (*ProviderRegistry) List ΒΆ

func (r *ProviderRegistry) List() []ProviderType

List returns all cached provider types.

func (*ProviderRegistry) Remove ΒΆ

func (r *ProviderRegistry) Remove(t ProviderType)

Remove removes a provider from the cache.

type ProviderSessionOptions ΒΆ

type ProviderSessionOptions struct {
	// Working directory for the CLI process
	WorkDir string

	// Permission mode (e.g., "bypassPermissions", "acceptEdits", "default")
	PermissionMode string

	// DangerouslySkipPermissions bypasses all permission checks
	DangerouslySkipPermissions bool

	// Tool restrictions
	AllowedTools    []string
	DisallowedTools []string

	// System prompts
	BaseSystemPrompt string // Engine-level foundational prompt
	TaskInstructions string // Per-task instructions (persisted per session)
	InitialPrompt    string // First prompt for cold start (sent as CLI arg if needed)

	// Session management
	SessionID         string // Internal SDK session ID
	ProviderSessionID string // Provider-specific persistent session ID
	ResumeSession     bool   // Whether to resume an existing session

	// Provider-specific flags
	// Claude Code specific
	Model string // Model override (e.g., "claude-3-5-sonnet")

	// OpenCode specific
	PlanMode bool // Use planning mode instead of build mode
	Port     int  // Port for HTTP API mode (if applicable)
}

ProviderSessionOptions configures a provider session. This is the provider-specific subset of session configuration, extracted from the global EngineOptions and per-request Config.

type ProviderType ΒΆ

type ProviderType string

ProviderType defines the type of AI CLI provider.

const (
	ProviderTypeClaudeCode     ProviderType = "claude-code"
	ProviderTypeOpenCode       ProviderType = "opencode"
	ProviderTypePi             ProviderType = "pi"
	ProviderTypeOpenCodeServer ProviderType = "opencode-server"
)

func ListPlugins ΒΆ added in v0.22.0

func ListPlugins() []ProviderType

ListPlugins returns all registered plugin types.

func (ProviderType) IsRegistered ΒΆ added in v0.22.0

func (t ProviderType) IsRegistered() bool

IsRegistered checks if the provider type is registered in the global factory. This is the preferred method for checking provider availability. It checks both built-in providers and registered plugins.

func (ProviderType) Valid ΒΆ

func (t ProviderType) Valid() bool

Valid checks if the provider type is registered in the global factory. This method delegates to IsRegistered for consistency with the plugin system. Deprecated: Use IsRegistered() instead for clearer semantics.

type StreamMessage ΒΆ

type StreamMessage struct {
	Message      *AssistantMessage `json:"message,omitempty"`
	Input        map[string]any    `json:"input,omitempty"`
	Type         string            `json:"type"`
	Timestamp    string            `json:"timestamp,omitempty"`
	SessionID    string            `json:"session_id,omitempty"`
	MessageID    string            `json:"message_id,omitempty"`
	Role         string            `json:"role,omitempty"`
	Name         string            `json:"name,omitempty"`
	Output       string            `json:"output,omitempty"`
	Status       string            `json:"status,omitempty"`
	Error        string            `json:"error,omitempty"`
	Content      []ContentBlock    `json:"content,omitempty"`
	Duration     int               `json:"duration,omitempty"`    // Claude Code often uses "duration"
	DurationMs   int               `json:"duration_ms,omitempty"` // Some versions/providers use "duration_ms"
	Subtype      string            `json:"subtype,omitempty"`
	IsError      bool              `json:"is_error,omitempty"`
	TotalCostUSD float64           `json:"total_cost_usd,omitempty"`
	Usage        *UsageStats       `json:"usage,omitempty"`
	Result       string            `json:"result,omitempty"`
	// Permission request fields (Issue #39)
	Permission *PermissionDetail          `json:"permission,omitempty"`
	Decision   *DecisionDetail            `json:"decision,omitempty"`
	ModelUsage map[string]ModelUsageStats `json:"modelUsage,omitempty"`
	// PermissionDenials contains tools that were denied by user (Claude Code stop_reason="tool_disallowed")
	PermissionDenials []PermissionDeniedDetail `json:"permission_denials,omitempty"`
}

StreamMessage represents a single event in the stream-json format emitted by CLI tools. This is a minimal copy for the provider package to avoid circular dependencies.

func (*StreamMessage) GetContentBlocks ΒΆ

func (m *StreamMessage) GetContentBlocks() []ContentBlock

GetContentBlocks returns the primary content blocks of the message.

type Transport ΒΆ added in v0.36.0

type Transport interface {
	// Connect establishes the connection to the agent server.
	// It is called once during transport initialization.
	Connect(ctx context.Context, cfg TransportConfig) error

	// Send delivers a message to an existing session.
	Send(ctx context.Context, sessionID string, message map[string]any) error

	// Events returns a channel that emits raw SSE event payloads (JSON strings).
	// The channel is closed when the transport is closed.
	// Multiple goroutines may read from the channel concurrently.
	Events() <-chan string

	// CreateSession creates a new session on the server and returns its ID.
	CreateSession(ctx context.Context, title string) (string, error)

	// DeleteSession terminates a session on the server.
	DeleteSession(ctx context.Context, sessionID string) error

	// RespondPermission sends a permission response to the server.
	RespondPermission(ctx context.Context, sessionID, permissionID, response string) error

	// Health checks if the server is reachable.
	Health(ctx context.Context) error

	// Close releases all resources held by the transport.
	// Close is idempotent and goroutine-safe.
	Close() error
}

Transport defines the low-level communication interface with an agent backend. It is responsible for HTTP or other network protocol details, exposing a simple request/response interface and an SSE event stream to its callers.

Implementations must be safe for concurrent use by multiple goroutines.

type TransportConfig ΒΆ added in v0.36.0

type TransportConfig struct {
	// Endpoint is the base URL of the agent server (e.g., "http://127.0.0.1:4096").
	Endpoint string

	// Env contains environment variables to pass to the server.
	Env map[string]string

	// WorkDir is the working directory for the session.
	WorkDir string

	// Password is the Basic Auth password for the server.
	Password string
}

TransportConfig contains configuration for establishing a transport connection.

type UsageStats ΒΆ

type UsageStats struct {
	InputTokens           int32 `json:"input_tokens"`
	OutputTokens          int32 `json:"output_tokens"`
	CacheWriteInputTokens int32 `json:"cache_creation_input_tokens,omitempty"`
	CacheReadInputTokens  int32 `json:"cache_read_input_tokens,omitempty"`
}

UsageStats represents the token consumption breakdown.

Jump to

Keyboard shortcuts

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