mirror of
https://github.com/j3ssie/osmedeus.git
synced 2026-08-20 22:52:32 +02:00
- Update Next.js generated chunk hashes and build IDs reflecting latest dashboard build - Update CSS stylesheet references in workflow upload page metadata - Add comprehensive cloud setup E2E test suite (cloud_setup_test.go) with SSH password/key auth, post-command variable expansion, and Ansible integration - Fix API priority levels to include 'medium' priority in test coverage - Add agent-sdk test workflows (minimal, config, codex, multi-agent, session variants) - Update E2E test utilities with runCLIInBase helper for multi-step cloud config tests - Fix stderr/stdout capture in dependencies_target_types_test assertions
264 lines
6.8 KiB
Go
264 lines
6.8 KiB
Go
package executor
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
agnostic "github.com/j3ssie/go-agent-agnostic"
|
|
"github.com/j3ssie/go-agent-agnostic/sdk/claude"
|
|
"github.com/j3ssie/go-agent-agnostic/sdk/codex"
|
|
"github.com/j3ssie/go-agent-agnostic/sdk/opencode"
|
|
"github.com/j3ssie/osmedeus/v5/internal/core"
|
|
oslogger "github.com/j3ssie/osmedeus/v5/internal/logger"
|
|
"github.com/j3ssie/osmedeus/v5/internal/template"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// supportedSDKAgents lists agent names supported by the go-agent-agnostic SDK.
|
|
var supportedSDKAgents = []string{"claude-code", "codex", "opencode"}
|
|
|
|
// SDKExecutor implements StepExecutorPlugin for agent-sdk steps.
|
|
// It uses the go-agent-agnostic library to run coding agents.
|
|
type SDKExecutor struct {
|
|
templateEngine template.TemplateEngine
|
|
}
|
|
|
|
// NewSDKExecutor creates a new SDK executor.
|
|
func NewSDKExecutor(engine template.TemplateEngine) *SDKExecutor {
|
|
return &SDKExecutor{
|
|
templateEngine: engine,
|
|
}
|
|
}
|
|
|
|
// Name returns the executor name for logging/debugging.
|
|
func (e *SDKExecutor) Name() string {
|
|
return "agent-sdk"
|
|
}
|
|
|
|
// StepTypes returns the step types this executor handles.
|
|
func (e *SDKExecutor) StepTypes() []core.StepType {
|
|
return []core.StepType{core.StepTypeAgentSDK}
|
|
}
|
|
|
|
// Execute runs an agent-sdk step.
|
|
func (e *SDKExecutor) Execute(ctx context.Context, step *core.Step, execCtx *core.ExecutionContext) (*core.StepResult, error) {
|
|
result := &core.StepResult{
|
|
StepName: step.Name,
|
|
Status: core.StepStatusRunning,
|
|
StartTime: time.Now(),
|
|
Exports: make(map[string]interface{}),
|
|
}
|
|
|
|
prompt := BuildPrompt(step)
|
|
if prompt == "" {
|
|
err := fmt.Errorf("agent-sdk step has no prompt (messages with content required)")
|
|
e.fillResult(result, "", "", err)
|
|
return result, err
|
|
}
|
|
|
|
agentName := step.Agent
|
|
if agentName == "" {
|
|
agentName = core.DefaultSDKAgent
|
|
}
|
|
|
|
opts := e.buildOptions(step, execCtx)
|
|
|
|
strategy := ""
|
|
var agentNames []string
|
|
if step.SDKConfig != nil && step.SDKConfig.Strategy != "" && len(step.SDKConfig.Agents) > 0 {
|
|
strategy = step.SDKConfig.Strategy
|
|
agentNames = step.SDKConfig.Agents
|
|
}
|
|
|
|
log := oslogger.Get()
|
|
|
|
if strategy != "" {
|
|
// Multi-agent mode
|
|
log.Debug("running agent-sdk in multi-agent mode",
|
|
zap.String("strategy", strategy),
|
|
zap.Strings("agents", agentNames))
|
|
|
|
output, agentUsed, err := e.runMultiAgent(ctx, prompt, strategy, agentNames, opts)
|
|
e.fillResult(result, output, agentUsed, err)
|
|
return result, err
|
|
}
|
|
|
|
log.Debug("running agent-sdk",
|
|
zap.String("agent", agentName),
|
|
zap.Int("promptLength", len(prompt)))
|
|
|
|
agent, err := e.createAgent(agentName, opts)
|
|
if err != nil {
|
|
e.fillResult(result, "", "", err)
|
|
return result, err
|
|
}
|
|
defer func() { _ = agent.Close() }()
|
|
|
|
output, err := agent.Run(ctx, prompt, nil)
|
|
|
|
// Capture session ID if available
|
|
sessionID := ""
|
|
if sp, ok := agent.(agnostic.SessionProvider); ok {
|
|
sessionID = sp.LastSessionID()
|
|
}
|
|
|
|
e.fillResult(result, output, agentName, err)
|
|
result.Exports["sdk_session_id"] = sessionID
|
|
|
|
return result, err
|
|
}
|
|
|
|
// buildOptions constructs agnostic.Options from step configuration.
|
|
func (e *SDKExecutor) buildOptions(step *core.Step, execCtx *core.ExecutionContext) *agnostic.Options {
|
|
opts := &agnostic.Options{
|
|
Cwd: step.Cwd,
|
|
}
|
|
if opts.Cwd == "" {
|
|
opts.Cwd = execCtx.WorkspacePath
|
|
}
|
|
|
|
// Extract system prompt from messages
|
|
for _, msg := range step.Messages {
|
|
if msg.Role == "system" {
|
|
if content, ok := msg.Content.(string); ok {
|
|
opts.SystemPrompt = content
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
cfg := step.SDKConfig
|
|
if cfg == nil {
|
|
return opts
|
|
}
|
|
|
|
opts.Model = cfg.Model
|
|
opts.Env = cfg.Env
|
|
|
|
// Apply agent-specific options
|
|
agentName := step.Agent
|
|
if agentName == "" {
|
|
agentName = core.DefaultSDKAgent
|
|
}
|
|
|
|
switch agentName {
|
|
case "claude-code":
|
|
cc := &agnostic.ClaudeCodeOptions{}
|
|
if cfg.MaxTurns > 0 {
|
|
cc.MaxTurns = cfg.MaxTurns
|
|
}
|
|
if cfg.PermissionMode != "" {
|
|
cc.PermissionMode = cfg.PermissionMode
|
|
}
|
|
if cfg.SessionResume != "" {
|
|
cc.Resume = cfg.SessionResume
|
|
}
|
|
if len(step.AllowedPaths) > 0 {
|
|
cc.AdditionalDirs = step.AllowedPaths
|
|
}
|
|
opts.ClaudeCode = cc
|
|
|
|
case "codex":
|
|
cx := &agnostic.CodexOptions{}
|
|
if cfg.Sandbox != "" {
|
|
cx.Sandbox = cfg.Sandbox
|
|
}
|
|
opts.Codex = cx
|
|
|
|
case "opencode":
|
|
opts.OpenCode = &agnostic.OpenCodeOptions{}
|
|
}
|
|
|
|
return opts
|
|
}
|
|
|
|
// createAgent creates an agnostic.Agent for the given agent name.
|
|
func (e *SDKExecutor) createAgent(name string, opts *agnostic.Options) (agnostic.Agent, error) {
|
|
switch name {
|
|
case "claude-code":
|
|
return claude.New(opts), nil
|
|
case "codex":
|
|
return codex.New(opts), nil
|
|
case "opencode":
|
|
return opencode.New(opts), nil
|
|
default:
|
|
return nil, fmt.Errorf("unknown agent-sdk agent: %q (available: %s)", name, availableSDKAgentNames())
|
|
}
|
|
}
|
|
|
|
// runMultiAgent runs multiple agents using the specified strategy.
|
|
func (e *SDKExecutor) runMultiAgent(ctx context.Context, prompt, strategy string, agentNames []string, baseOpts *agnostic.Options) (string, string, error) {
|
|
agents := make([]agnostic.Agent, 0, len(agentNames))
|
|
for _, name := range agentNames {
|
|
a, err := e.createAgent(name, baseOpts)
|
|
if err != nil {
|
|
// Close already-created agents
|
|
for _, created := range agents {
|
|
_ = created.Close()
|
|
}
|
|
return "", "", fmt.Errorf("failed to create agent %q: %w", name, err)
|
|
}
|
|
agents = append(agents, a)
|
|
}
|
|
defer func() {
|
|
for _, a := range agents {
|
|
_ = a.Close()
|
|
}
|
|
}()
|
|
|
|
switch strategy {
|
|
case "first":
|
|
result, err := agnostic.RunFirst(ctx, prompt, agents...)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
return result.Output, string(result.Agent), nil
|
|
|
|
case "all":
|
|
results := agnostic.RunAll(ctx, prompt, agents...)
|
|
var outputs []string
|
|
var agentsUsed []string
|
|
for _, r := range results {
|
|
if r.Err == nil {
|
|
outputs = append(outputs, r.Output)
|
|
agentsUsed = append(agentsUsed, string(r.Agent))
|
|
}
|
|
}
|
|
if len(outputs) == 0 {
|
|
return "", "", fmt.Errorf("all %d agents failed", len(results))
|
|
}
|
|
return strings.Join(outputs, "\n---\n"), strings.Join(agentsUsed, ","), nil
|
|
|
|
default:
|
|
return "", "", fmt.Errorf("unknown multi-agent strategy: %q (use \"first\" or \"all\")", strategy)
|
|
}
|
|
}
|
|
|
|
// fillResult populates a StepResult with agent output.
|
|
func (e *SDKExecutor) fillResult(result *core.StepResult, output, agentName string, err error) {
|
|
result.EndTime = time.Now()
|
|
result.Duration = result.EndTime.Sub(result.StartTime)
|
|
result.Output = output
|
|
result.Exports["sdk_output"] = output
|
|
result.Exports["sdk_agent"] = agentName
|
|
|
|
if err != nil {
|
|
result.Status = core.StepStatusFailed
|
|
result.Error = err
|
|
} else {
|
|
result.Status = core.StepStatusSuccess
|
|
}
|
|
}
|
|
|
|
// ListSDKAgentNames returns the names of all supported SDK agents.
|
|
func ListSDKAgentNames() []string {
|
|
return supportedSDKAgents
|
|
}
|
|
|
|
// availableSDKAgentNames returns a comma-separated list of supported SDK agent names.
|
|
func availableSDKAgentNames() string {
|
|
return strings.Join(ListSDKAgentNames(), ", ")
|
|
}
|