Files

153 lines
4.6 KiB
Go

// Package extensions provides subagent spawning capabilities for Kit extensions.
package extensions
import (
"fmt"
"os"
"sync"
"time"
)
// ---------------------------------------------------------------------------
// Subagent types
// ---------------------------------------------------------------------------
// SubagentConfig configures a subagent spawn.
type SubagentConfig struct {
// Prompt is the task/instruction for the subagent (required).
Prompt string
// Model overrides the parent's model (e.g. "anthropic/claude-haiku-3-5-20241022").
// Empty string uses the parent's current model.
Model string
// SystemPrompt provides domain-specific instructions.
// Empty string uses the default system prompt.
SystemPrompt string
// Timeout limits execution time. Zero means 5 minute default.
Timeout time.Duration
// OnOutput streams stderr output chunks as the subagent runs.
// Called from a goroutine; must be safe for concurrent use.
OnOutput func(chunk string)
// OnEvent receives real-time events from the subagent's execution:
// text chunks, tool calls, tool results, reasoning deltas, etc.
// Called synchronously from the subagent's event loop.
OnEvent func(SubagentEvent)
// OnComplete is called when the subagent finishes (success or error).
// Called from a goroutine; must be safe for concurrent use.
OnComplete func(result SubagentResult)
// Blocking, when true, makes SpawnSubagent wait for completion and
// return the result directly. When false (default), spawns in background
// and returns immediately with a handle.
Blocking bool
// NoSession, when true, runs the subagent without persisting a session
// file. By default (false), subagent sessions are persisted so they can
// be loaded for replay/inspection. Set to true for ephemeral tasks
// where session history is not needed.
NoSession bool
// ParentSessionID links the subagent's session to the parent (optional).
// When set, the subagent's session header includes a parent reference
// so viewers can navigate the session tree.
ParentSessionID string
}
// SubagentEvent carries a real-time event from a running subagent. Extensions
// use the Type field to determine what happened and read the relevant fields.
// This is a concrete struct (not an interface) for Yaegi compatibility.
type SubagentEvent struct {
// Type identifies the event: "text", "reasoning", "tool_call",
// "tool_result", "tool_execution_start", "tool_execution_end",
// "turn_start", "turn_end".
Type string
// Content carries text for "text" and "reasoning" events.
Content string
// ToolCallID is set on tool_call, tool_result, tool_execution_start,
// and tool_execution_end events.
ToolCallID string
// ToolName is set on tool-related events.
ToolName string
// ToolKind is set on tool-related events.
ToolKind string
// ToolArgs is set on tool_call events (JSON-encoded).
ToolArgs string
// ToolResult is set on tool_result events.
ToolResult string
// IsError is set on tool_result events.
IsError bool
}
// SubagentResult contains the outcome of a subagent execution.
type SubagentResult struct {
// Response is the subagent's final text response.
Response string
// Error is set if the subagent failed (nil on success).
Error error
// ExitCode is the subprocess exit code (0 = success).
ExitCode int
// Elapsed is the total execution time.
Elapsed time.Duration
// Usage contains token usage if available.
Usage *SubagentUsage
// SessionID is the subagent's session identifier, if available.
// Populated when the subagent persists its session (requires running
// without --no-session). Empty for ephemeral sessions.
SessionID string
}
// SubagentUsage contains token usage from the subagent's run.
type SubagentUsage struct {
InputTokens int64
OutputTokens int64
}
// SubagentHandle provides control over a running subagent.
type SubagentHandle struct {
// ID is a unique identifier for this subagent instance.
ID string
proc *os.Process
done chan struct{}
result *SubagentResult
mu sync.Mutex
}
// Kill terminates the subagent process.
func (h *SubagentHandle) Kill() error {
h.mu.Lock()
proc := h.proc
h.mu.Unlock()
if proc != nil {
return proc.Kill()
}
return nil
}
// Wait blocks until the subagent completes and returns the result.
func (h *SubagentHandle) Wait() SubagentResult {
<-h.done
h.mu.Lock()
defer h.mu.Unlock()
if h.result != nil {
return *h.result
}
return SubagentResult{Error: fmt.Errorf("subagent completed without result")}
}
// Done returns a channel that closes when the subagent completes.
func (h *SubagentHandle) Done() <-chan struct{} {
return h.done
}