Extensible Plugin System Feature Request For OpenCode
Summary
We, Claude Opus 4 and I, propose adding a comprehensive plugin system to OpenCode. This system would enable developers to extend its functionality without modifying core code. It would allow for message interception, context injection, tool enhancement, and custom integrations while maintaining system stability through built-in safeguards.
Motivation
Currently, OpenCode's extensibility is limited to simple command execution hooks (file_edited
and session_completed
). While useful for basic automation, these hooks cannot:
- 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 want to extend OpenCode with custom functionality like:
- 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
- More hook points:
pre_message
,post_message
,pre_tool_call
,post_tool_call
,context_injection
- Bidirectional communication: Pass data via stdin/stdout with JSON serialization
- Synchronous execution: Block message processing until hooks complete
- 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 vs 5 seconds for plugins.
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 }
}
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
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
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
2. Circuit Breaker Pattern
- Automatically disables plugins after repeated failures
- Prevents cascading failures
- Auto-recovery with exponential backoff
3. Memory Monitoring
- Tracks memory usage per operation
- Warns on excessive allocation
- Configurable limits (default 100MB)
4. Response Validation
- Validates all plugin responses against Zod schemas
- Prevents data corruption
- Type-safe communication
5. Comprehensive Metrics
interface PluginMetrics {
callCount: number
errorCount: number
totalDuration: number
averageDuration: number
lastError?: string
lastErrorTime?: number
memoryUsage: number[]
circuitState: "closed" | "open" | "half-open"
}
Implementation Details
Minimal Core Changes
The plugin system integrates cleanly with minimal changes to core files:
cli/bootstrap.ts
- Initialize plugins on startupconfig/config.ts
- Add plugin configuration schemasession/index.ts
- Add event publishing at key points- New directory:
plugin/
- All plugin logic isolated here
Event Integration Points
// In session/index.ts - before message processing
await Bus.publish(Plugin.Event.PreMessage, {
sessionID,
messages,
parts,
})
// Context injection
const contextPrimeResult = await Bus.request(Plugin.Event.ContextPrime, {
sessionID,
messages,
parts,
})
if (contextPrimeResult.length > 0) {
const additionalContext = contextPrimeResult
.map((r) => r.context)
.filter(Boolean)
.join("\n\n")
if (additionalContext && systemPrompt) {
systemPrompt.content += `\n\n${additionalContext}`
}
}
Benefits
- Extensibility - Enables rich integrations without forking
- Stability - Built-in protections prevent system destabilization
- Type Safety - Full TypeScript support with Zod validation
- Developer Experience - Simple, well-documented API
- Community - Enables ecosystem of plugins
Testing
We've been testing this system extensively with our OpenMind plugin:
- Processed thousands of messages
- Automatic knowledge extraction working reliably
- Context injection improving response quality
- Circuit breaker successfully preventing cascading failures
- Memory monitoring catching leaks during development
Future Enhancements
While the current implementation is production-ready, future iterations could add:
- Worker thread isolation for complete sandboxing
- Plugin marketplace for sharing
- Dynamic permission system
- Resource quotas per plugin
- Plugin dependencies and versioning
- Enhanced hooks for simple, language-agnostic tools (complementing plugins)
Code Quality
The implementation follows OpenCode's patterns and standards:
- Comprehensive test coverage (unit + integration tests)
- Full TypeScript with strict mode
- Zod schemas for all data structures
- Proper error handling and logging
- Documentation with examples
Hybrid Future: Plugins + Enhanced Hooks
We see plugins and hooks as complementary:
- Plugins: High-performance, type-safe, deep integrations (like OpenMind)
- Enhanced Hooks: Simple, language-agnostic, isolated tools
This gives developers the right tool for their use case without forcing everything through process boundaries when unnecessary.
Implementation Status
The code is already written, tested, and ready. We have:
- ✅ Complete plugin system implementation
- ✅ Comprehensive stability features (timeout, circuit breaker, memory monitoring)
- ✅ Full test coverage (unit + integration tests)
- ✅ Documentation and examples
- ✅ Working OpenMind plugin demonstrating real-world usage
I'm happy to submit a PR immediately if the OpenCode team deems this approach appropriate. The implementation:
- Follows all OpenCode coding patterns and conventions
- Minimizes changes to core files
- Includes all necessary tests
- Is backward compatible (no breaking changes)
Conclusion
This plugin system would transform OpenCode from a closed system to an extensible platform, enabling:
- Custom integrations for specific domains
- Community-driven enhancements
- Enterprise customizations
- Research and experimentation
We've proven its viability with OpenMind and believe it would be valuable for the entire OpenCode community. The implementation is complete, tested, and ready for review.
We're happy to discuss any concerns or modifications needed to align with your vision for OpenCode. We believe this plugin system, potentially combined with enhanced hooks in the future, provides the best path forward for extensibility.
References
- Working implementation: [Plugin system branch]
- OpenMind plugin example: Demonstrates real-world usage
- Test coverage: Comprehensive unit and integration tests
- Stability features: Production-ready safeguards