agent

package
v1.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package agent provides a unified interface for executing prompts via coding agents (Claude Code, CodeBuddy, Codex, Copilot, OpenCode, OpenClaw, Hermes, Pi, Cursor, Kimi, Kiro, Antigravity, Qoder). It mirrors the happy-cli AgentBackend pattern, translated to idiomatic Go.

Index

Constants

View Source
const CodexFirstTurnNoProgressMarker = "codex app-server no progress timeout"

CodexFirstTurnNoProgressMarker identifies the app-server failure mode where Codex accepts a turn and then never emits any item, completion, or error.

View Source
const CodexSemanticInactivityMarker = "codex semantic inactivity timeout"

CodexSemanticInactivityMarker prefixes timeout errors emitted when Codex stops making semantic progress while the process is still alive.

View Source
const MinHandoffCLIVersion = "0.3.28"

MinHandoffCLIVersion is the lowest multica CLI version whose daemon renders the assignment handoff note into the run's opening prompt + issue_context.md (MUL-3375). Unlike quick-create this is a SOFT gate: assigning an issue with a note never fails on an old daemon — the assignment still takes effect, the note is simply dropped. The frontend reads HandoffSupported to gray out the note box and warn the user, so they aren't surprised by a silently ignored note. Bump this to the release that actually ships the daemon rendering.

View Source
const MinQuickCreateCLIVersion = "0.2.21"

MinQuickCreateCLIVersion gates the agent-create (quick-create) flow against the multica CLI version reported by the daemon at registration time. The quick-create prompt that the agent runs depends on CLI behavior introduced after this version (attachment URL handling, quick-create attachment binding, no-retry semantics on `multica issue create` failure — see PR #1851); older daemons would either double-create issues or mishandle pasted screenshot URLs. Treated as a hard requirement: missing / unparsable / below this threshold all fail closed.

Variables

View Source
var (
	ErrCLIVersionMissing = errors.New("multica CLI version not reported by daemon")
	ErrCLIVersionTooOld  = errors.New("multica CLI version is below required minimum")
)

Errors returned by CheckMinCLIVersion. Callers branch on these to surface "needs upgrade" vs "version not reported" with the right user message.

View Source
var MinVersions = map[string]string{
	"claude":  "2.0.0",
	"codex":   "0.100.0",
	"copilot": "1.0.0",
}

MinVersions defines the minimum required CLI version for each agent type. Versions below these will be rejected during daemon registration.

View Source
var SupportedTypes = []string{
	"claude",
	"codebuddy",
	"codex",
	"copilot",
	"opencode",
	"openclaw",
	"hermes",
	"pi",
	"cursor",
	"kimi",
	"kiro",
	"antigravity",
}

New creates a Backend for the given agent type. Supported types: "claude", "codebuddy", "codex", "copilot", "opencode", "openclaw", "hermes", "pi", "cursor", "kimi", "kiro", "antigravity", "qoder", "traecli".

SupportedTypes is the canonical whitelist of agent types eligible to back a custom runtime profile. It MUST stay in lockstep with the runtime_profile.protocol_family CHECK constraint (migration 120): a custom runtime profile may only be based on a backend Multica officially supports. (qoder is a built-in provider New can construct, but it is not offered as a custom-profile base, so it is intentionally absent from this list.)

Functions

func CheckMinCLIVersion

func CheckMinCLIVersion(detected string) error

CheckMinCLIVersion returns nil when `detected` parses as ≥ minimum. Returns ErrCLIVersionMissing for empty or unparsable input, and ErrCLIVersionTooOld when parsable but below the minimum. The caller can check for these sentinel errors with errors.Is to drive the response shape.

Dev-built daemons (git-describe shape) always pass — the version string itself is the shared signal, so the modal pre-check and this server gate agree by construction without needing to compare separate env flags.

func CheckMinVersion

func CheckMinVersion(agentType, detectedVersion string) error

CheckMinVersion validates that detectedVersion meets the minimum for agentType. Returns nil if the version is acceptable or no minimum is defined.

func DetectVersion

func DetectVersion(ctx context.Context, executablePath string) (string, error)

DetectVersion runs the agent CLI with --version and returns the output.

func HandoffSupported

func HandoffSupported(cliVersion string) bool

HandoffSupported reports whether a daemon reporting cliVersion is new enough to render handoff notes. Reuses the CheckMinCLIVersion parsing (including the git-describe dev-build exemption) but never errors — a missing/old/unparsable version simply means "not supported", which the soft gate degrades gracefully.

func IsKnownThinkingValue

func IsKnownThinkingValue(providerType, value string) bool

IsKnownThinkingValue reports whether `value` is a recognised effort token for the given provider. Empty string is always accepted (means "use runtime default"). Unknown providers (no thinking concept) accept only empty; OpenCode accepts well-formed variant names because its local catalog can be extended by opencode.json.

This is the cheap synchronous gate the server uses on CreateAgent / UpdateAgent. Unlike ValidateThinkingLevel it does NOT consult the live catalog or per-model subset.

func IsSupportedType

func IsSupportedType(agentType string) bool

IsSupportedType reports whether agentType is in the SupportedTypes whitelist. Used to validate a custom runtime profile's protocol_family before it is persisted or registered.

func LaunchHeader

func LaunchHeader(agentType string) string

LaunchHeader returns the user-visible launch skeleton for agentType, or an empty string if the type is unknown. Callers render this as a preview so users understand which command their custom_args get appended to.

func ModelKnownIncompatibleWithProvider

func ModelKnownIncompatibleWithProvider(providerType, model string) bool

ModelKnownIncompatibleWithProvider reports whether a saved model is a known mismatch for a target runtime provider. For first-party providers with maintained static catalogs, compatibility is exact: the model must be one of the IDs that runtime advertises. Unknown/custom model strings still return false because the UI and CLI allow manual entries and the server should not erase values it cannot confidently classify.

func ModelSelectionSupported

func ModelSelectionSupported(providerType string) bool

ModelSelectionSupported reports whether setting `agent.model` has any effect for the given provider. Every built-in provider now honours `opts.Model` end-to-end — Hermes routes it through the ACP `session/set_model` RPC before each prompt; Claude / Codex / Cursor / Gemini / Copilot / Kimi / Kiro / OpenCode / OpenClaw / Pi / Antigravity pass it via flag or session config (Antigravity gained `--model` in agy 1.0.6 — MUL-3125).

The hook is retained — rather than inlining `true` at the call sites — so a future model-less runtime can opt out in one place, which makes the UI render a disabled "Managed by runtime" picker instead of an empty dropdown plus a silently-ignored manual-entry field.

func PiSessionDir

func PiSessionDir() (string, error)

PiSessionDir exposes piSessionDir to other packages in this module.

func ValidateThinkingLevel

func ValidateThinkingLevel(ctx context.Context, providerType, executablePath, model, value string) (bool, error)

ValidateThinkingLevel reports whether `value` is in the supported catalog for the given (provider, model) pair. Empty value is always valid — it means "use the runtime default".

Empty model is treated as "use the provider's default model"; we resolve it through ListModels so the daemon's pre-execution guard behaves the same whether the agent picked an explicit model or inherited the runtime default. Without this, a default-model task with a valid thinking_level would be rejected on the grounds that the empty string is not in the catalog — exactly the misjudgement Elon flagged in the PR1 review.

The lookup goes through ListModels so it sees the *current* CLI catalog (including dynamic discovery for codex), not just a static map. The function is intentionally pure of HTTP concerns so the daemon's pre-execution guard and the server's UpdateAgent gate can share the same source of truth.

Types

type Backend

type Backend interface {
	// Execute runs a prompt and returns a Session for streaming results.
	// The caller should read from Session.Messages (optional) and wait on
	// Session.Result for the final outcome.
	Execute(ctx context.Context, prompt string, opts ExecOptions) (*Session, error)
}

Backend is the unified interface for executing prompts via coding agents.

func New

func New(agentType string, cfg Config) (Backend, error)

type Config

type Config struct {
	ExecutablePath string            // path to CLI binary (claude, codebuddy, codex, copilot, opencode, openclaw, hermes, pi, cursor, kimi, kiro-cli, agy, qodercli)
	Env            map[string]string // extra environment variables
	Logger         *slog.Logger
}

Config configures a Backend instance.

type ExecOptions

type ExecOptions struct {
	Cwd   string
	Model string
	// SystemPrompt is consumed only by providers that can pass or safely inline
	// developer/system instructions. Hermes ACP intentionally ignores it and
	// relies on cwd-scoped context files such as AGENTS.md instead.
	SystemPrompt              string
	ThreadName                string
	MaxTurns                  int
	Timeout                   time.Duration
	SemanticInactivityTimeout time.Duration
	ResumeSessionID           string          // if non-empty, resume a previous agent session
	ExtraArgs                 []string        // daemon-wide default CLI arguments appended before CustomArgs; currently read by claude and codex backends only
	CustomArgs                []string        // per-agent CLI arguments appended after ExtraArgs
	McpConfig                 json.RawMessage // if non-nil, MCP server config to pass via --mcp-config
	// ThinkingLevel is the runtime-native reasoning/effort value (e.g.
	// Claude's "low|medium|high|xhigh|max", Codex's "none|minimal|low|
	// medium|high|xhigh", OpenCode's model variant names). Empty means
	// "use the runtime/model default" —
	// every backend that consumes this skips its --effort / reasoning_effort
	// injection so the upstream CLI's own default applies. Currently honoured
	// by the claude, codex, and opencode backends; other backends ignore the
	// field rather than fail (so MUL-2339 can grow runtime support
	// incrementally without breaking unrelated agents).
	ThinkingLevel string
	// OpenclawMode chooses between local (embedded) and gateway routing for
	// the openclaw backend. "" or "local" keeps the historical behaviour —
	// the daemon spawns `openclaw agent --local …` and the agent loop runs
	// in-process on the daemon host. "gateway" instructs the daemon to drop
	// the --local flag and let openclaw route the turn through a Gateway (the
	// user's globally-configured one, or an endpoint pinned in the per-task
	// config wrapper that the daemon writes from execenv.OpenclawGatewayPin —
	// see server/internal/daemon/execenv/openclaw_config.go). Other backends
	// ignore this field, mirroring ThinkingLevel's renderer-side fall-through
	// pattern. See issue #3260.
	OpenclawMode string
}

ExecOptions configures a single execution.

type Message

type Message struct {
	Type      MessageType
	Content   string         // text content (Text, Error, Log)
	Tool      string         // tool name (ToolUse, ToolResult)
	CallID    string         // tool call ID (ToolUse, ToolResult)
	Input     map[string]any // tool input (ToolUse)
	Output    string         // tool output (ToolResult)
	Status    string         // agent status string (Status)
	Level     string         // log level (Log)
	SessionID string         // backend session id (Status), for early resume-pointer pinning
}

Message is a unified event emitted by an agent during execution.

type MessageType

type MessageType string

MessageType identifies the kind of Message.

const (
	MessageText       MessageType = "text"
	MessageThinking   MessageType = "thinking"
	MessageToolUse    MessageType = "tool-use"
	MessageToolResult MessageType = "tool-result"
	MessageStatus     MessageType = "status"
	MessageError      MessageType = "error"
	MessageLog        MessageType = "log"
)

type Model

type Model struct {
	ID       string `json:"id"`
	Label    string `json:"label"`
	Provider string `json:"provider,omitempty"`
	Default  bool   `json:"default,omitempty"`
	// Thinking advertises the runtime's reasoning/effort catalog for this
	// model. nil means the runtime/model has no thinking-level control
	// (or the daemon couldn't discover one); the UI hides its picker. The
	// catalog is per-model because Codex's `codex debug models` is itself
	// per-model and Claude's `--effort` superset has known per-model gaps
	// (`xhigh` is Opus-only, `max` is session-only). See MUL-2339.
	Thinking *ModelThinking `json:"thinking,omitempty"`
}

Model describes a single LLM model exposed by an agent provider. The dropdown groups by Provider when the ID uses the `provider/model` form (e.g. "openai/gpt-4o" from opencode). Default is a *display* hint: the UI badges the entry the runtime advertises as its preferred pick (e.g. Claude Code's shipped default, or hermes' currentModelId). It has no effect at execution time — when agent.model is empty the daemon passes "" to the backend so each provider's own CLI resolves its own default, which is always closer to what the user's account / environment actually supports than a static guess here.

func ListModels

func ListModels(ctx context.Context, providerType, executablePath string) ([]Model, error)

ListModels returns the models supported by the given agent provider. For providers with a known static catalog it returns the baked-in list; for providers with a CLI discovery mechanism (opencode, pi, openclaw) it shells out with caching and falls back to the static list on failure.

For claude, codex, and opencode, the catalog is augmented with per-model thinking-level options discovered from the local CLI. Discovery failures silently leave Thinking == nil on each entry, which the UI treats as "no picker for this model" rather than blocking model selection.

executablePath lets the caller point at a non-default binary; pass "" to use the provider's default name on PATH.

type ModelThinking

type ModelThinking struct {
	SupportedLevels []ThinkingLevel `json:"supported_levels"`
	// DefaultLevel is the value the runtime picks when no override is
	// provided. Empty means "the runtime picks, we don't know" — the
	// UI shows "Default" as a generic option.
	DefaultLevel string `json:"default_level,omitempty"`
}

ModelThinking carries the per-model reasoning/effort catalog surfaced by an agent runtime. Values are runtime-native — Codex emits "none|minimal|low|medium|high|xhigh"; Claude emits "low|medium|high|xhigh|max". The frontend renders SupportedLevels as-is so what users see matches each CLI's own UI.

type Result

type Result struct {
	Status     string // "completed", "failed", "aborted", "timeout", "cancelled"
	Output     string // accumulated text output
	Error      string // error message if failed
	DurationMs int64
	SessionID  string
	Usage      map[string]TokenUsage // keyed by model name
}

Result is the final outcome after an agent session completes.

type Session

type Session struct {
	// Messages streams events as the agent works. The channel is closed
	// when the agent finishes (before Result is sent).
	Messages <-chan Message
	// Result receives exactly one value — the final outcome — then closes.
	Result <-chan Result
}

Session represents a running agent execution.

type ThinkingLevel

type ThinkingLevel struct {
	Value       string `json:"value"`
	Label       string `json:"label"`
	Description string `json:"description,omitempty"`
}

ThinkingLevel is one entry in a ModelThinking.SupportedLevels list. Value is the literal token passed to the CLI (Claude `--effort <value>` or Codex `model_reasoning_effort=<value>`); Label is a display string; Description is optional helper copy lifted from the upstream catalog when available (Codex's `description` field).

type TokenUsage

type TokenUsage struct {
	InputTokens      int64
	OutputTokens     int64
	CacheReadTokens  int64
	CacheWriteTokens int64
}

TokenUsage tracks token consumption for a single model.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL