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
- Variables
- func CheckMinCLIVersion(detected string) error
- func CheckMinVersion(agentType, detectedVersion string) error
- func DetectVersion(ctx context.Context, executablePath string) (string, error)
- func HandoffSupported(cliVersion string) bool
- func IsKnownThinkingValue(providerType, value string) bool
- func IsSupportedType(agentType string) bool
- func LaunchHeader(agentType string) string
- func ModelKnownIncompatibleWithProvider(providerType, model string) bool
- func ModelSelectionSupported(providerType string) bool
- func PiSessionDir() (string, error)
- func ValidateThinkingLevel(ctx context.Context, providerType, executablePath, model, value string) (bool, error)
- type Backend
- type Config
- type ExecOptions
- type Message
- type MessageType
- type Model
- type ModelThinking
- type Result
- type Session
- type ThinkingLevel
- type TokenUsage
Constants ¶
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.
const CodexSemanticInactivityMarker = "codex semantic inactivity timeout"
CodexSemanticInactivityMarker prefixes timeout errors emitted when Codex stops making semantic progress while the process is still alive.
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.
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 ¶
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.
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.
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 ¶
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 ¶
CheckMinVersion validates that detectedVersion meets the minimum for agentType. Returns nil if the version is acceptable or no minimum is defined.
func DetectVersion ¶
DetectVersion runs the agent CLI with --version and returns the output.
func HandoffSupported ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
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 ¶
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).
Source Files
¶
- agent.go
- antigravity.go
- claude.go
- codebuddy.go
- codex.go
- copilot.go
- copilot_invocation.go
- copilot_invocation_other.go
- cursor.go
- cursor_invocation.go
- cursor_invocation_other.go
- hermes.go
- kimi.go
- kiro.go
- models.go
- openclaw.go
- opencode.go
- opencode_mcp.go
- pi.go
- pi_invocation.go
- pi_invocation_other.go
- proc_other.go
- qoder.go
- stderr_tail.go
- thinking.go
- traecli.go
- version.go