rdev/internal/domain/code_agent.go
jordan 39df51defd feat: Add multi-provider code agent interface with Claude Code and OpenCode adapters
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>
2026-01-27 09:25:51 -07:00

198 lines
5.6 KiB
Go

// Package domain contains pure domain models with no external dependencies.
package domain
import "time"
// AgentProvider identifies which code agent implementation to use.
type AgentProvider string
const (
// AgentProviderClaudeCode uses Anthropic's Claude Code CLI.
AgentProviderClaudeCode AgentProvider = "claudecode"
// AgentProviderOpenCode uses the open-source OpenCode agent.
AgentProviderOpenCode AgentProvider = "opencode"
)
// ValidAgentProviders returns all valid agent provider values.
func ValidAgentProviders() []AgentProvider {
return []AgentProvider{AgentProviderClaudeCode, AgentProviderOpenCode}
}
// IsValid returns true if the provider is a known valid value.
func (p AgentProvider) IsValid() bool {
switch p {
case AgentProviderClaudeCode, AgentProviderOpenCode:
return true
default:
return false
}
}
// String returns the string representation of the provider.
func (p AgentProvider) String() string {
return string(p)
}
// ParseAgentProvider converts a string to AgentProvider with validation.
// Returns empty provider and error if the string is not a valid provider.
func ParseAgentProvider(s string) (AgentProvider, error) {
p := AgentProvider(s)
if !p.IsValid() {
return "", ErrInvalidAgentProvider
}
return p, nil
}
// AgentRequest contains parameters for executing a code agent command.
type AgentRequest struct {
// Prompt is the user's instruction to the agent.
Prompt string
// ProjectID identifies the project context.
ProjectID ProjectID
// SessionID enables conversation continuation. Empty for new sessions.
SessionID string
// AllowedTools specifies which tools the agent may use without prompting.
// Uses permission rule syntax (e.g., "Bash", "Read", "Bash(git:*)").
AllowedTools []string
// Model specifies which LLM to use. Provider-specific.
// For Claude Code: ignored (uses Claude).
// For OpenCode: e.g., "claude-sonnet-4-20250514", "gpt-4o".
Model string
// WorkingDir is the directory context for the agent. Defaults to /workspace.
WorkingDir string
// Timeout is the maximum execution time. Zero means use default.
Timeout time.Duration
// Metadata contains additional provider-specific options.
Metadata map[string]string
}
// Validate checks the AgentRequest for required fields and valid values.
// Returns an error describing the validation failure, or nil if valid.
func (r *AgentRequest) Validate() error {
if r.Prompt == "" {
return ErrPromptRequired
}
if r.Timeout < 0 {
return ErrInvalidTimeout
}
return nil
}
// AgentEventType categorizes events emitted during agent execution.
type AgentEventType string
const (
// AgentEventOutput is text output from the agent (stdout equivalent).
AgentEventOutput AgentEventType = "output"
// AgentEventToolUse indicates the agent is invoking a tool.
AgentEventToolUse AgentEventType = "tool_use"
// AgentEventToolResult contains the result of a tool invocation.
AgentEventToolResult AgentEventType = "tool_result"
// AgentEventThinking indicates the agent is processing/reasoning.
AgentEventThinking AgentEventType = "thinking"
// AgentEventError indicates an error occurred.
AgentEventError AgentEventType = "error"
// AgentEventComplete indicates execution finished.
AgentEventComplete AgentEventType = "complete"
)
// AgentEvent represents a single event during agent execution.
type AgentEvent struct {
// Type categorizes this event.
Type AgentEventType
// Timestamp when the event occurred.
Timestamp time.Time
// Content is the main payload (text output, tool name, error message).
Content string
// Stream identifies the output stream ("stdout", "stderr", or empty).
Stream string
// ToolName is set for tool_use and tool_result events.
ToolName string
// ToolInput contains the tool invocation arguments (for tool_use).
ToolInput map[string]any
// Metadata contains additional event-specific data.
Metadata map[string]any
}
// AgentEventHandler is a callback for receiving agent events during execution.
type AgentEventHandler func(event AgentEvent)
// AgentResult contains the outcome of agent execution.
type AgentResult struct {
// SessionID identifies this conversation for potential continuation.
SessionID string
// ExitCode is 0 for success, non-zero for failure.
ExitCode int
// DurationMs is the total execution time in milliseconds.
DurationMs int64
// Error contains any execution error (nil on success).
Error error
// TokensUsed tracks token consumption (if available from provider).
TokensUsed *AgentTokenUsage
// FinalOutput contains the agent's final text response (if any).
FinalOutput string
}
// Success returns true if the agent completed successfully.
func (r *AgentResult) Success() bool {
return r.Error == nil && r.ExitCode == 0
}
// AgentTokenUsage tracks token consumption during execution.
type AgentTokenUsage struct {
InputTokens int64
OutputTokens int64
TotalTokens int64
}
// AgentCapabilities describes what a code agent implementation supports.
type AgentCapabilities struct {
// Provider identifies this agent implementation.
Provider AgentProvider
// SupportsSessionContinuation indicates --resume/session support.
SupportsSessionContinuation bool
// SupportsModelSelection indicates the model can be changed.
SupportsModelSelection bool
// SupportsToolControl indicates --allowedTools support.
SupportsToolControl bool
// SupportedModels lists available models (empty if not applicable).
SupportedModels []string
// DefaultModel is used when none is specified.
DefaultModel string
// MaxPromptLength is the maximum prompt size (0 = unlimited).
MaxPromptLength int
// SupportsStreaming indicates real-time output streaming.
SupportsStreaming bool
}