Implements weeks 1-4 of the multi-provider architecture: Week 1 - Foundation: - Add domain models (AgentProvider, AgentRequest, AgentEvent, AgentResult) - Define CodeAgent port interface with Execute, Cancel, Capabilities - Create thread-safe provider registry with first-registered default Week 2 - Claude Code Adapter: - Extract kubectl exec logic into CodeAgent implementation - Parse stream-json output format (init, message, tool_use, result) - Support session continuation via --resume flag Week 3 - OpenCode Adapter: - HTTP/SSE client for opencode serve API - Session management (create, send message, abort) - Event streaming with documented buffer rationale Week 4 - Quality & Polish: - Fix race condition in OpenCode Cancel method - Add AgentRequest.Validate() with ErrPromptRequired, ErrInvalidTimeout - Document DefaultAvailabilityTimeout constants - Add HTTP error context for debugging Also includes: - Work queue system with PostgreSQL adapter - Credential store for infrastructure secrets - Project templates with Woodpecker CI integration - Comprehensive test coverage Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
123 lines
2.8 KiB
Go
123 lines
2.8 KiB
Go
// Package codeagent provides the code agent registry and common utilities.
|
|
package codeagent
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/orchard9/rdev/internal/domain"
|
|
"github.com/orchard9/rdev/internal/port"
|
|
)
|
|
|
|
// Registry implements port.CodeAgentRegistry with thread-safe agent management.
|
|
type Registry struct {
|
|
mu sync.RWMutex
|
|
agents map[domain.AgentProvider]port.CodeAgent
|
|
defProv domain.AgentProvider
|
|
hasAgent bool
|
|
}
|
|
|
|
// NewRegistry creates a new empty agent registry.
|
|
func NewRegistry() *Registry {
|
|
return &Registry{
|
|
agents: make(map[domain.AgentProvider]port.CodeAgent),
|
|
}
|
|
}
|
|
|
|
// Ensure Registry implements port.CodeAgentRegistry at compile time.
|
|
var _ port.CodeAgentRegistry = (*Registry)(nil)
|
|
|
|
// Register adds an agent implementation for a provider.
|
|
func (r *Registry) Register(agent port.CodeAgent) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
provider := agent.Provider()
|
|
r.agents[provider] = agent
|
|
|
|
// If this is the first agent, make it the default
|
|
if !r.hasAgent {
|
|
r.defProv = provider
|
|
r.hasAgent = true
|
|
}
|
|
}
|
|
|
|
// Get returns the agent for a specific provider.
|
|
func (r *Registry) Get(provider domain.AgentProvider) port.CodeAgent {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
return r.agents[provider]
|
|
}
|
|
|
|
// Default returns the default agent implementation.
|
|
func (r *Registry) Default() port.CodeAgent {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
if !r.hasAgent {
|
|
return nil
|
|
}
|
|
return r.agents[r.defProv]
|
|
}
|
|
|
|
// SetDefault sets which provider should be used as the default.
|
|
func (r *Registry) SetDefault(provider domain.AgentProvider) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
if _, exists := r.agents[provider]; !exists {
|
|
return fmt.Errorf("agent provider %q is not registered", provider)
|
|
}
|
|
|
|
r.defProv = provider
|
|
return nil
|
|
}
|
|
|
|
// Available returns all registered providers.
|
|
func (r *Registry) Available() []domain.AgentProvider {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
providers := make([]domain.AgentProvider, 0, len(r.agents))
|
|
for p := range r.agents {
|
|
providers = append(providers, p)
|
|
}
|
|
return providers
|
|
}
|
|
|
|
// AvailableAgents returns all registered agents that are currently available.
|
|
func (r *Registry) AvailableAgents(ctx context.Context) []port.CodeAgent {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
available := make([]port.CodeAgent, 0, len(r.agents))
|
|
for _, agent := range r.agents {
|
|
if agent.Available(ctx) {
|
|
available = append(available, agent)
|
|
}
|
|
}
|
|
return available
|
|
}
|
|
|
|
// DefaultProvider returns the current default provider.
|
|
// Returns empty string if no agents are registered.
|
|
func (r *Registry) DefaultProvider() domain.AgentProvider {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
if !r.hasAgent {
|
|
return ""
|
|
}
|
|
return r.defProv
|
|
}
|
|
|
|
// Count returns the number of registered agents.
|
|
func (r *Registry) Count() int {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
return len(r.agents)
|
|
}
|