Extensible Plugin System For OpenCode Enhancing Platform Functionality

by Jeany 71 views
Iklan Headers

Summary

We propose adding a comprehensive plugin system to OpenCode. This system will empower developers to extend OpenCode's functionality without modifying its core code. This approach allows for message interception, context injection, tool enhancement, and custom integrations. By incorporating built-in safeguards, the system's stability is maintained, ensuring a robust and reliable platform.

Motivation

Currently, OpenCode's extensibility is limited to simple command execution hooks (file_edited and session_completed). While these hooks serve basic automation needs, they lack the capacity to:

  • Modify or enhance messages before processing.
  • Inject contextual information into conversations.
  • Capture and analyze conversation data.
  • Integrate with external knowledge systems.
  • Provide bidirectional communication with OpenCode.

Many users are eager to extend OpenCode with custom functionality, such as:

  • Knowledge management systems.
  • Custom context providers.
  • Specialized tool integrations.
  • Conversation analytics.
  • Domain-specific enhancements.

Alternative Considered: Enhanced Hooks

We carefully considered whether enhancing the existing hooks system could meet our needs. While theoretically possible, it would require significant changes and introduce performance/complexity issues:

What Enhanced Hooks Would Need

  1. More hook points: pre_message, post_message, pre_tool_call, post_tool_call, context_injection
  2. Bidirectional communication: Pass data via stdin/stdout with JSON serialization
  3. Synchronous execution: Block message processing until hooks complete
  4. Error handling: Timeouts, retries, fallback behavior

Why Plugins Are Better Than Enhanced Hooks

Aspect Enhanced Hooks Plugin System
Performance ❌ 60-150ms overhead per message (process spawn + JSON) ✅ <1ms (in-process)
Type Safety ❌ JSON serialization loses types ✅ Full TypeScript
Debugging ❌ Separate process, hard to debug ✅ Integrated debugging
Complexity ❌ IPC, serialization, process management ✅ Simple async functions
Error Handling ⚠️ Basic process exit codes ✅ Try/catch with full context
State Management ❌ Stateless between calls ✅ Persistent plugin state

Performance Impact Example

For a typical OpenMind operation (context injection):

  • Hook approach: ~100ms (50ms spawn + 30ms JSON + 20ms IPC)
  • Plugin approach: ~5ms (direct function call)

At 1000 messages/day, hooks add 100 seconds of latency versus 5 seconds for plugins. This stark contrast highlights the performance benefits of a plugin system. The plugin system significantly reduces latency, making it a more efficient choice for high-volume operations.

When Hooks Make Sense

Enhanced hooks would be valuable for:

  • Language diversity: Python, Rust, Go integrations
  • Security isolation: Untrusted code
  • Simple operations: Logging, notifications

But for performance-critical, deep integrations like OpenMind, plugins are the clear choice.

Real-World Use Case: OpenMind Integration

We've been developing and testing a plugin called OpenMind - an AI knowledge graph system that:

  • Automatically captures conversations and extracts knowledge
  • Builds a semantic graph of concepts, entities, and relationships
  • Provides contextual information retrieval for enhanced responses
  • Learns from tool usage patterns

This integration requires capabilities far beyond current hooks:

// Example: Context injection based on conversation analysis
async onContextPrime(request: ContextPrimeRequest): Promise<ContextPrimeResponse> {
  const relevantKnowledge = await this.queryKnowledgeGraph(request.messages)
  return { context: relevantKnowledge }
}

Integrating the OpenMind Plugin for Enhanced Contextual Understanding

The OpenMind plugin exemplifies the powerful capabilities a robust plugin system can bring to OpenCode. This system serves as an AI knowledge graph, capturing conversations and extracting valuable knowledge. It builds a semantic graph of concepts, entities, and relationships, which can be queried to provide contextual information, thus enhancing the quality and relevance of responses. The OpenMind plugin also learns from tool usage patterns, further refining its ability to provide insightful context. This level of integration, which requires deep access to OpenCode's inner workings, goes far beyond the capabilities of current hooks. The ability to inject context based on conversation analysis, as demonstrated in the code snippet above, illustrates the nuanced and sophisticated enhancements that a plugin system makes possible. This approach ensures that OpenCode can evolve to meet the needs of complex applications and use cases, maintaining its position as a versatile and adaptable platform.

Proposed Solution

Plugin Architecture

The plugin system follows OpenCode's existing patterns:

  • Namespace pattern for organization
  • Bus events for communication
  • App state for lifecycle management
  • Zod schemas for type safety

Core Events

The core events within the plugin system are designed to provide comprehensive hooks into OpenCode's message processing and tool execution flows. This structured approach ensures that plugins can interact with the system in a predictable and type-safe manner. By leveraging bus events, the system facilitates seamless communication between OpenCode's core components and the installed plugins. This not only enhances the extensibility of the platform but also ensures that all interactions adhere to a well-defined schema, promoting stability and reducing the likelihood of errors. The meticulous design of these core events allows for the creation of powerful and reliable plugins, significantly expanding OpenCode's capabilities. The use of Zod schemas further reinforces this commitment to type safety, making development easier and more robust. This system-level approach to plugin integration is key to maintaining the integrity and performance of OpenCode while unlocking a wide range of potential enhancements and customizations.

export namespace Plugin {
  export const Event = {
    // Fired before message processing - allows modification
    PreMessage: Bus.event("plugin.pre-message", z.object({
      sessionID: z.string(),
      messages: z.array(MessageV2.Info),
      parts: z.array(MessageV2.Part)
    })),
    
    // Fired after message completion - includes usage stats
    PostMessage: Bus.event("plugin.post-message", z.object({
      sessionID: z.string(),
      messageID: z.string(),
      message: MessageV2.Info,
      usage: LanguageModelUsage
    })),
    
    // Tool interception for enhancement/monitoring
    PreToolCall: Bus.event("plugin.pre-tool-call", z.object({
      toolName: z.string(),
      toolCallId: z.string(),
      args: z.any(),
      sessionID: z.string(),
      messageID: z.string()
    })),
    
    PostToolCall: Bus.event("plugin.post-tool-call", z.object({
      toolName: z.string(),
      toolCallId: z.string(),
      args: z.any(),
      result: z.string().optional(),
      error: z.string().optional(),
      sessionID: z.string(),
      messageID: z.string()
    })),
    
    // Request/response for context injection
    ContextPrime: Bus.request("plugin.context-prime",
      ContextPrimeRequest,
      ContextPrimeResponse
    )
  }
}

Plugin Interface

The plugin interface defines a structured contract that all plugins must adhere to, ensuring a consistent and predictable interaction with OpenCode. This interface includes essential lifecycle methods such as init and shutdown, which allow plugins to initialize their state and gracefully release resources. The interface also specifies event handlers for various core events, such as onPreMessage, onPostMessage, onPreToolCall, onPostToolCall, and onContextPrime. These event handlers enable plugins to intercept and modify messages, enhance tool calls, and inject contextual information, thereby extending OpenCode's functionality in a modular and type-safe manner. By enforcing a well-defined interface, the plugin system promotes maintainability, reduces the risk of conflicts between plugins, and allows developers to focus on implementing their custom logic without needing to understand the intricacies of OpenCode's internal architecture. This structured approach is crucial for building a robust and scalable ecosystem of plugins that can enhance OpenCode's capabilities without compromising its stability.

export interface Plugin.Info {
  name: string
  version: string
  description?: string
  
  // Lifecycle
  init?(config: any): Promise<void>
  shutdown?(): Promise<void>
  
  // Event handlers
  onPreMessage?(event: PreMessageEvent): Promise<void>
  onPostMessage?(event: PostMessageEvent): Promise<void>
  onPreToolCall?(event: PreToolCallEvent): Promise<void>
  onPostToolCall?(event: PostToolCallEvent): Promise<void>
  onContextPrime?(request: ContextPrimeRequest): Promise<ContextPrimeResponse>
}

Configuration

Plugins are configured in opencode.json:

{
  "experimental": {
    "plugins": {
      "my-plugin": {
        "enabled": true,
        "path": "./plugins/my-plugin.js",
        "stability": {
          "timeout": 5000,
          "maxFailures": 3,
          "circuitResetTimeout": 60000,
          "validateResponses": true,
          "maxMemoryIncrease": 100
        },
        // Custom plugin config
        "apiKey": "...",
        "endpoint": "..."
      }
    }
  }
}

Stability & Safety Features

Understanding that plugins run in the same process, we've implemented comprehensive stability protections:

1. Timeout Protection

  • Default 5-second timeout for all plugin operations
  • Prevents hanging operations from blocking the UI
  • Configurable per-plugin

The timeout protection mechanism is a critical component of the plugin system, designed to prevent plugins from causing performance bottlenecks or application freezes. By setting a default timeout of 5 seconds for all plugin operations, the system ensures that no single plugin can monopolize resources or indefinitely delay the processing of user requests. This timeout is configurable on a per-plugin basis, allowing administrators to fine-tune the system's behavior to suit the specific needs and characteristics of each plugin. This level of control is essential for maintaining a responsive and stable user interface, particularly in environments where multiple plugins are running concurrently. The timeout protection effectively safeguards the overall performance and reliability of OpenCode, ensuring a smooth and consistent user experience even in the presence of potentially resource-intensive plugins.

2. Circuit Breaker Pattern

  • Automatically disables plugins after repeated failures
  • Prevents cascading failures
  • Auto-recovery with exponential backoff

Incorporating a circuit breaker pattern is paramount for ensuring the resilience and fault tolerance of the plugin system. This pattern is designed to automatically disable plugins that exhibit repeated failures, thereby preventing cascading failures that could destabilize the entire OpenCode application. The circuit breaker acts as a protective barrier, monitoring plugin behavior and intervening when necessary to maintain system integrity. When a plugin fails repeatedly, the circuit breaker