core

package
v0.31.0 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2026 License: MIT Imports: 28 Imported by: 0

Documentation ¶

Index ¶

Constants ¶

View Source
const (
	AgentEventStart          = "agent_start"
	AgentEventEnd            = "agent_end"
	AgentEventError          = "agent_error"
	AgentEventTurnStart      = "turn_start"
	AgentEventTurnEnd        = "turn_end"
	AgentEventMessageStart   = "message_start"
	AgentEventMessageUpdate  = "message_update"
	AgentEventMessageEnd     = "message_end"
	AgentEventToolExecStart  = "tool_execution_start"
	AgentEventToolExecUpdate = "tool_execution_update"
	AgentEventToolExecEnd    = "tool_execution_end"

	AgentEventSteer = "steer" // a steering message was injected mid-run
	// AgentEventUserMessage reports a user prompt that just entered the
	// conversation as the first message of a new run, emitted at the append
	// point (under the state lock) so the fact is already true in history when
	// subscribers see it. Mid-run injections keep using AgentEventSteer.
	AgentEventUserMessage = "user_message"
	// AgentEventSteersCanceled reports queued steers dropped after a failed run.
	AgentEventSteersCanceled = "steers_canceled"

	AgentEventCompactionStart = "compaction_start"
	AgentEventCompactionEnd   = "compaction_end"
)

Agent event type constants.

View Source
const (
	ProviderEventStart         = "start"
	ProviderEventTextStart     = "text_start"
	ProviderEventTextDelta     = "text_delta"
	ProviderEventTextEnd       = "text_end"
	ProviderEventThinkingStart = "thinking_start"
	ProviderEventThinkingDelta = "thinking_delta"
	ProviderEventThinkingEnd   = "thinking_end"
	ProviderEventToolCallStart = "toolcall_start"
	ProviderEventToolCallDelta = "toolcall_delta"
	ProviderEventToolCallEnd   = "toolcall_end"
	ProviderEventRateLimit     = "ratelimit"
	ProviderEventDone          = "done"
	ProviderEventError         = "error"
)

Provider event type constants.

View Source
const (
	// ToolCallDecisionKindPermission marks user-facing permission denials.
	ToolCallDecisionKindPermission = "permission"
	// ToolCallDecisionKindPolicy marks non-permission policy/plan blocks.
	ToolCallDecisionKindPolicy = "policy"
)
View Source
const DefaultMaxOutputTokens = 32_000

DefaultMaxOutputTokens bounds a single model response when the caller has not selected a smaller cap. It leaves room for reasoning without allowing one request to consume a model's entire output allowance.

View Source
const DefaultSTTModel = "gpt-transcribe"

DefaultSTTModel is the speech-to-text model used when none is configured.

gpt-transcribe replaced whisper-1 as the default: on our own Spanish dictation it was faster, 25% cheaper per minute, and noticeably better at technical names (whisper turned "MCP" into "MSP" and "goreleaser" into "Gore Leaser").

View Source
const MaxImageDimension = 8000

MaxImageDimension is the largest side, in pixels, accepted for an inline image. It is Anthropic's limit: it rejects anything above 8000 px per side with a hard 400, and because history is replayed on every turn a single oversized image makes the whole conversation unsendable until the block is removed.

OpenAI has no equivalent hard limit — it caps the request payload (512 MB) and the image count (1500), and oversized images are resized server-side to a patch/pixel budget rather than rejected. The one exception is GPT-5.6 with detail "original"/"auto" (what convertUserContent sends), which preserves the input dimensions and bills every patch: a 395x8239 screenshot is not an error there, just expensive. So the limit is enforced where images enter history (the read tool and attachments), which keeps a session portable across providers — a history built under OpenAI must not become unsendable the moment the user switches to Anthropic mid-session.

Variables ¶

View Source
var DefaultCompactionSettings = CompactionSettings{
	Enabled:       true,
	ReserveTokens: 16384,
	KeepRecent:    20000,
}

DefaultCompactionSettings provides sensible defaults.

View Source
var ErrEmptyResponse = &EmptyResponseError{}

ErrEmptyResponse is a sentinel for errors.Is(err, core.ErrEmptyResponse).

View Source
var ErrQuotaExceeded = &QuotaExceededError{}

ErrQuotaExceeded is a sentinel for errors.Is(err, core.ErrQuotaExceeded).

View Source
var ErrWaitInterruptedBySteer = errors.New("wait interrupted by user steer")

ErrWaitInterruptedBySteer marks a wait tool whose parent agent received a user steer. The job being observed continues in the background; only the parent's blocking wait is interrupted so it can process the new message.

View Source
var ThinkingLevels = []string{"off", "low", "medium", "high", "xhigh"}

ThinkingLevels is the canonical list of valid thinking levels. All validation and UI should reference this slice — not hardcoded strings.

Functions ¶

func AcquireFileLock ¶ added in v0.25.0

func AcquireFileLock(path string) (io.Closer, error)

AcquireFileLock takes an exclusive advisory lock on path, creating it if needed, and blocks until it is free. Close releases it.

It is the same lock the project state uses for its read-modify-write, hoisted so other packages can serialize their own multi-process critical sections instead of inventing a second locking scheme. The memory migration is one: two sessions starting at once in two worktrees of a repository would otherwise both try to fold the old per-path stores into the shared one.

func AddProjectAllowPattern ¶ added in v0.24.0

func AddProjectAllowPattern(workspaceRoot, pattern string) error

AddProjectAllowPattern records an "always allow" approval for a workspace.

func AgentIDFromContext ¶

func AgentIDFromContext(ctx context.Context) string

AgentIDFromContext returns the agent id set by WithAgentID, or "" if none.

func AllowedModelAliases ¶ added in v0.30.0

func AllowedModelAliases(allowedIDs []string) []string

AllowedModelAliases returns the aliases of the models whose IDs appear in allowedIDs, in the same curated order as ModelAliases.

An empty (or nil) allowedIDs means "no restriction" and yields every alias: the allowlist is opt-in, so an unset one must never shrink what callers may offer. IDs matching no known model are skipped rather than reported — the registry changes over time and a stale saved entry must not invalidate the rest of the list.

func BuildSTTPrompt ¶

func BuildSTTPrompt(vocabulary []string) string

BuildSTTPrompt turns a configured vocabulary into the provider's prompt hint.

The prompt is a hint, not a substitution: it biases spelling toward these words without forcing them. We send it as a plain comma-separated list, the shape OpenAI documents for "a list of correct spellings".

It deliberately uses "prompt" rather than the newer "keywords" field: whisper-1 rejects keywords outright, and the model is user-configurable, so a vocabulary that only works on some models would be a trap. Both produce the same result in practice.

Terms are trimmed, blank ones dropped, and duplicates removed case-insensitively (keeping the first spelling, which is the one the user cared to write).

func CacheTTLDuration ¶

func CacheTTLDuration(cfg MoaConfig) time.Duration

CacheTTLDuration maps the configured cache retention to a concrete window. Anthropic's default ephemeral cache lives 5 minutes; the extended window ("1h") lives an hour. Each request refreshes the timer, so the cache stays warm until the last run + this duration.

func CanonicalOrRaw ¶

func CanonicalOrRaw(path string) string

CanonicalOrRaw is the exported form of canonicalOrRaw: a clean, absolute, symlink-resolved path, falling back to the input on failure. Used to compare session working directories when fanning out project-scoped preferences.

func CanonicalizePath ¶

func CanonicalizePath(path string) (string, error)

CanonicalizePath returns a clean, absolute, symlink-resolved path. Falls back to Abs+Clean if EvalSymlinks fails (e.g., broken symlinks).

func CloneArgs ¶ added in v0.23.0

func CloneArgs(m map[string]any) map[string]any

CloneArgs deep-copies a tool call's JSON-shaped arguments. Exported for callers outside core that hold on to an args map the agent keeps mutating (pkg/bus's live-tool registry), where a shallow copy would still share the nested maps and slices.

func CodebaseKey ¶ added in v0.25.0

func CodebaseKey(dir string) string

CodebaseKey identifies the repository a directory belongs to, so that every git worktree of the same repo answers with the same key.

ProjectHash cannot do this: it hashes the canonical path, and a worktree is by definition a different path. Anything scoped by ProjectHash is therefore scoped per worktree, and deleting a worktree orphans whatever was stored under it — with a dozen worktrees on one repo, that is a dozen unrelated islands and a permanent leak every time one is removed.

A directory inside a submodule keys on the submodule, not on the superproject: a submodule is a repository with its own history and its own remote, usually maintained by other people, and what is learned about it belongs to it rather than to whoever vendored it. The corollary is that the same submodule checked out under two worktrees of the superproject gets two keys, because git gives each one its own git dir (super/.git/modules/... vs super/.git/worktrees/<wt>/...) and nothing ties them back together without resolving the superproject, which would defeat the decision above.

Outside a repository, or when git cannot answer, the key is the hash of the canonical path: the current behaviour. It never fails and never returns "".

func ConfigDir ¶ added in v0.24.0

func ConfigDir() string

ConfigDir returns the directory holding moa's own state: config.json, credentials, sessions, skills, prompts, memory and attachments.

MOA_CONFIG_DIR overrides it. That knob existed before this function but was only honored by some of the call sites, so setting it produced a half-moved instance: credentials in the new directory, config and history still in the home one. Everything that resolves moa state must go through here, so the override either moves all of it or none of it.

It deliberately does not cover paths that merely happen to live under the home directory, such as expanding "~" in a path the user typed.

func ConfigSubdir ¶ added in v0.24.0

func ConfigSubdir(parts ...string) string

ConfigSubdir returns a path inside ConfigDir, or "" if it cannot be resolved — callers already treat an empty path as "this feature is unavailable" rather than writing to a relative directory.

func CorrectImageMime ¶ added in v0.22.0

func CorrectImageMime(b64, declared string) string

CorrectImageMime returns the media type to declare for a base64 image payload: the one the bytes actually are, falling back to the declared one when the format isn't recognized.

A mislabeled image (e.g. a GIF saved as .png, which the read tool types from the extension) is rejected by Anthropic with a hard 400 naming the mismatch. History is replayed every turn, so one such block makes the whole conversation unsendable — hence the correction sits at the provider edge too, where it also un-poisons sessions that already recorded the wrong type.

func EffectiveThinkingLevel ¶ added in v0.26.0

func EffectiveThinkingLevel(model Model, level string) (string, error)

EffectiveThinkingLevel resolves a persisted level for a model. xAI requires reasoning and only accepts low/medium/high; other providers keep the value.

func EstimateOutputTokens ¶

func EstimateOutputTokens(m Message) int

EstimateOutputTokens estimates the logical output generated by an assistant message. It intentionally excludes thinking and counts only text and tool calls, which are the content returned to the user or sent to tools.

func EstimateTokens ¶

func EstimateTokens(m Message) int

EstimateTokens estimates the token count of a single message using a chars/4 heuristic. Conservative (overestimates slightly).

func ExtractAssistantText ¶ added in v0.30.0

func ExtractAssistantText(msg AgentMessage) string

ExtractAssistantText returns the concatenated non-empty text blocks of an assistant message. Keeping this rule separate lets streaming transcript readers reuse the exact outcome extraction semantics without materializing every preceding message.

func ExtractFinalAssistantText ¶

func ExtractFinalAssistantText(msgs []AgentMessage) string

ExtractFinalAssistantText returns the concatenated text content of the last assistant message in the conversation. Returns "" if none found.

func GetCacheTTL ¶

func GetCacheTTL(cfg MoaConfig) string

GetCacheTTL returns the prompt-cache TTL for the interactive agent. Only "1h" is honored; anything else (including empty or a typo) yields "" — the Anthropic default of 5 minutes. Subagents and one-shot calls never use this.

func GetMaxRunDuration ¶

func GetMaxRunDuration(cfg MoaConfig) time.Duration

GetMaxRunDuration parses MaxRunDurationStr into a time.Duration. Returns 0 (unlimited) if empty or invalid.

func GetSTTLanguage ¶

func GetSTTLanguage(cfg MoaConfig) string

GetSTTLanguage returns the ISO-639-1 language hint for speech-to-text. Default is "en" (English) when unset — a safe international default that also avoids Whisper mis-detecting short/ambiguous clips. Set "stt_language" in config (e.g. "es") to override; "auto" (any case) yields "" so the model auto-detects.

The value is normalized to a lowercase two-letter code. Anything that isn't a plausible ISO-639-1 code (wrong length, non-letters) falls back to "en" so a typo can't turn every transcription into an HTTP 400 from the provider.

func GetSTTModel ¶

func GetSTTModel(cfg MoaConfig) string

GetSTTModel returns the speech-to-text model id to send to the provider.

Set "stt_model" in config to try another one (e.g. "whisper-1" to go back, or "gpt-4o-mini-transcribe" for half the price) without needing a new build: these models appear and change price faster than moa releases.

func GetSubagentMaxRunDuration ¶

func GetSubagentMaxRunDuration(cfg MoaConfig) time.Duration

GetSubagentMaxRunDuration parses SubagentMaxRunDuration into a time.Duration. Returns 0 (use package default) if empty or invalid.

func ImageDimensions ¶

func ImageDimensions(data []byte) (width, height int)

ImageDimensions reports the pixel size from an image header. Returns 0,0 when the format is unsupported or the header is unreadable — an unknown size is never treated as oversized, so callers fall through to normal handling.

func ImageExceedsMaxDimension ¶

func ImageExceedsMaxDimension(b64 string) (width, height int, exceeds bool)

ImageExceedsMaxDimension reports whether a base64 image payload has a side above MaxImageDimension. Only the header is decoded, so the cost is bounded regardless of image size.

func ImageMimeFromBytes ¶ added in v0.22.0

func ImageMimeFromBytes(data []byte) string

ImageMimeFromBytes reports the media type implied by an image's magic bytes, or "" when the format isn't one providers accept inline. Only the formats in that set are recognized, so an unknown payload is left for the caller to handle rather than guessed at.

func ImportLegacyProjectVeto ¶ added in v0.24.0

func ImportLegacyProjectVeto(cwd string) error

ImportLegacyProjectVeto carries vetoes an older moa wrote into a trusted project's own config over to this user's state, once.

Merging them on every read instead would leave the MCP panel in a state the user cannot get out of: the veto would show under the project scope, but switching the server back on writes to the state, and the next session would read the project file again and turn it off. Importing makes the toggle mean what it says, and the flag stops the import from undoing that later.

func IsAutoVerifyEnabled ¶

func IsAutoVerifyEnabled(cfg MoaConfig) bool

IsAutoVerifyEnabled returns whether auto-verify is enabled. Default is false when AutoVerify is nil (not configured).

func IsMCPPathTrusted ¶

func IsMCPPathTrusted(cfg MoaConfig, path string) bool

IsMCPPathTrusted reports whether path is in the config's trusted MCP paths.

func IsMemoryEnabled ¶

func IsMemoryEnabled(cfg MoaConfig) bool

IsMemoryEnabled returns whether cross-session memory is enabled. Default is true when MemoryEnabled is nil (not configured).

func IsModelAllowed ¶ added in v0.30.0

func IsModelAllowed(model Model, allowedIDs []string) bool

IsModelAllowed reports whether model may be used under allowedIDs. An empty list means unrestricted, so existing installs keep working untouched.

func IsPersistentShellEnabled ¶

func IsPersistentShellEnabled(cfg MoaConfig) bool

IsPersistentShellEnabled returns whether the bash tool persists cwd and exported env across calls. Default is true when PersistentShell is nil.

func IsProjectPathTrusted ¶

func IsProjectPathTrusted(cfg MoaConfig, path string) bool

IsProjectPathTrusted reports whether path is trusted to auto-load its repo-local .moa/config.json and .moa/tools/*. Repo-local config can escalate permissions and register shell-executing tools, so — like .mcp.json — it is only honored for directories the user has explicitly trusted.

Paths are compared after canonicalization (abs + symlink-resolved) so a dir trusted via one spelling still matches when a caller later canonicalizes cwd (e.g. the serve path resolves /var → /private/var on macOS).

func IsUpdateCheckEnabled ¶

func IsUpdateCheckEnabled(cfg MoaConfig) bool

IsUpdateCheckEnabled returns whether release update checks are enabled. They are enabled by default; MOA_NO_UPDATE_CHECK=1 is handled by pkg/release.

func IsValidThinkingLevel ¶

func IsValidThinkingLevel(level string) bool

IsValidThinkingLevel reports whether level is a recognized thinking level.

func LoadMCPFile ¶

func LoadMCPFile(path string) (map[string]MCPServer, error)

LoadMCPFile reads a .mcp.json file. Returns nil map if file doesn't exist. Returns error for parse failures — and for entries that don't describe exactly one valid transport — so callers can warn the user instead of silently starting a half-defined server.

func MergeMCPServers ¶

func MergeMCPServers(maps ...map[string]MCPServer) map[string]MCPServer

MergeMCPServers merges server maps. Later maps override earlier ones by name (full replacement, not field-level merge).

func ModelAliases ¶ added in v0.30.0

func ModelAliases() []string

ModelAliases returns every alias that resolves to a known model, in the curated display order. Callers that need to *tell* a model which names are valid must derive the list from here rather than writing one by hand: a hand-kept copy silently drifts from modelAliases as models come and go.

func NativeDocBytes ¶

func NativeDocBytes(content []Content) int64

NativeDocBytes sums the decoded size of the native document/image blocks in content — the base64 payloads that count against a session's native-content budget. Text/thinking/tool blocks contribute nothing.

func NewMsgID ¶

func NewMsgID() string

NewMsgID mints a stable message identifier, using the same mechanism as Message.EnsureMsgID. Used when a caller needs a message's ID before the message is built (e.g. to correlate a later event with it).

func NewSteerID ¶

func NewSteerID() string

NewSteerID mints a random identifier for a steer item, using the same crypto/rand mechanism as Message.EnsureMsgID.

func ProjectHash ¶ added in v0.24.0

func ProjectHash(workspaceRoot string) string

ProjectHash identifies a workspace by its canonical path. Memory already scopes per project this way; sharing the function keeps a project from hashing to two different directories depending on who asks.

func ProjectStateDir ¶ added in v0.24.0

func ProjectStateDir(workspaceRoot string) string

ProjectStateDir returns where this user's state for a workspace lives, or "" when the config directory cannot be resolved.

func ProviderSupportsDocuments ¶

func ProviderSupportsDocuments(p Provider) bool

ProviderSupportsDocuments reports whether p accepts native document blocks. Conservative: an unknown provider (not implementing DocumentCapableProvider) returns false, so callers fall back to disk rather than silently dropping a PDF the provider can't handle.

func RegisterOrLog ¶

func RegisterOrLog(reg *Registry, t Tool)

RegisterOrLog registers a tool and logs a warning on failure. Use for dynamic tool sources (MCP, extensions, plan mode) where a registration error shouldn't abort the caller.

func RepoCodebaseKey ¶ added in v0.25.0

func RepoCodebaseKey(dir string) (string, bool)

RepoCodebaseKey is CodebaseKey restricted to directories git recognizes as part of a repository: it reports no key at all instead of falling back to the path hash.

The distinction matters to anything that uses a directory as *evidence* about who owns something rather than as the workspace it was asked about. The fallback is the right answer for "which key does this workspace use" — it always answers, and unrelated paths get unrelated keys — but as evidence it is worthless: it would name an owner for every path on the filesystem, including ones no workspace will ever open.

func ReservedToolName ¶

func ReservedToolName(name string) bool

ReservedToolName reports whether name is reserved for an internal tool which must never be supplied by scripts, extensions, or MCP servers.

func ResolveMaxOutputTokens ¶

func ResolveMaxOutputTokens(model Model, requested *int) int

ResolveMaxOutputTokens returns the effective output cap for a request. An explicit caller value wins, subject to the model's advertised capability; otherwise the shared operational default is used.

func ResolvePathScope ¶

func ResolvePathScope(pathScope string, disableSandbox bool, permMode string) string

ResolvePathScope determines the effective path scope from config values.

permMode is expected to be already resolved by the caller: bootstrap defaults an unset permission mode to "yolo" (moa's out-of-the-box posture for a single-user local tool) BEFORE calling this, so in normal operation the empty-mode branch below is never hit and the effective default scope is "unrestricted". The empty-mode → "workspace" branch is only a conservative fallback for direct callers that pass an unresolved mode; it does NOT reflect the CLI default.

Priority:

  1. Explicit pathScope ("workspace" or "unrestricted") — use as-is
  2. Legacy disableSandbox: true → "unrestricted"
  3. Derive from permission mode: - "yolo" or "ask" → "unrestricted" - "auto" → "workspace" - "" (unresolved) → "workspace" (conservative fallback; see note above)

func SameModelIdentity ¶ added in v0.29.0

func SameModelIdentity(requested, effective string) bool

SameModelIdentity compares model IDs after resolving known aliases. Unknown provider-returned IDs remain comparable without pretending they are aliases.

func SaveGlobalConfig ¶

func SaveGlobalConfig(update func(*MoaConfig)) error

SaveGlobalConfig reads the current global config, applies update, and writes it back atomically. Creates ~/.config/moa/ if it doesn't exist.

func SaveProjectConfig ¶

func SaveProjectConfig(cwd string, update func(*MoaConfig)) error

SaveProjectConfig reads the current project config, applies update, and writes it back atomically. Creates <cwd>/.moa/ if it doesn't exist.

func SetMCPServerDisabled ¶

func SetMCPServerDisabled(cfg *MoaConfig, name string, disabled bool)

SetMCPServerDisabled adds or removes a server name from a config's disabled list, keeping it sorted and deduplicated. Removing the last entry sets the slice to nil so omitempty drops the field. It is the read-modify-write body callers pass to SaveGlobalConfig / SaveProjectConfig.

func SetProjectMCPServerDisabled ¶ added in v0.24.0

func SetProjectMCPServerDisabled(workspaceRoot, server string, disabled bool) error

SetProjectMCPServerDisabled records this user's veto for a workspace.

func ShouldCompact ¶

func ShouldCompact(contextTokens, contextWindow int, settings CompactionSettings) bool

ShouldCompact returns true if context tokens exceed the safe threshold. Returns false for disabled settings, zero/negative context windows, or degenerate settings where reserve >= window.

func SuggestAliasFrom ¶ added in v0.30.0

func SuggestAliasFrom(spec string, aliases []string) string

SuggestAliasFrom is SuggestModelAlias over an explicit alias set, so a caller that may only offer some models (a subagent allowlist) never teaches a name it would then refuse.

func SuggestModelAlias ¶ added in v0.30.0

func SuggestModelAlias(spec string) string

SuggestModelAlias returns the alias a misspelled spec most likely meant, or "" when nothing is close enough. It exists so an unknown-model error can teach the correct name instead of only rejecting the wrong one: agents write these names from memory and a near miss ("sonet", "grok/4") is far more common than an unknown model.

Only unambiguous matches are offered: a suggestion that is wrong is worse than none, because it invites a second failed attempt.

func ThinkingLevelOptions ¶

func ThinkingLevelOptions() string

ThinkingLevelOptions returns a human-readable list for error messages.

func ThinkingLevelsForModel ¶ added in v0.26.0

func ThinkingLevelsForModel(model Model) []string

ThinkingLevelsForModel returns the levels the selected model accepts. Unknown models retain the common vocabulary; only verified constrained models narrow it. This keeps model switching and persisted session state deterministic.

func ToolCallIDFromContext ¶

func ToolCallIDFromContext(ctx context.Context) string

ToolCallIDFromContext returns the tool call id set by WithToolCallID, or "" if the context does not represent a tool call.

func UpdatePinnedModels ¶ added in v0.26.0

func UpdatePinnedModels(models []string, id string, pinned bool) []string

UpdatePinnedModels returns models with id added or removed. Adding preserves the existing order and appends new IDs; removing preserves the order of IDs that remain.

func UpdateProjectState ¶ added in v0.24.0

func UpdateProjectState(workspaceRoot string, update func(*ProjectState)) error

UpdateProjectState applies update to the stored state and writes it back.

update must not call back into UpdateProjectState or the lock deadlocks, and it should not read the state either: it receives the current one, and a LoadProjectState from inside would return the version before its own edits.

The whole read-modify-write is under an advisory lock, and the write goes through a uniquely named temporary file. Both matter: the project config writer this replaces used a fixed temp name and no lock, so two moa processes could clobber each other — reachable with one user running two sessions in the same project, which would silently drop an approval or re-enable a server they had switched off.

func ValidateAuxiliaryModelConfig ¶ added in v0.27.0

func ValidateAuxiliaryModelConfig(cfg MoaConfig) error

ValidateAuxiliaryModelConfig validates the two background-model settings without requiring credentials. Credential availability is intentionally a startup concern: a valid config remains valid when a user logs out.

func ValidateAuxiliaryModelSpec ¶ added in v0.27.0

func ValidateAuxiliaryModelSpec(spec string) error

ValidateAuxiliaryModelSpec validates a config value without requiring credentials. It accepts auto, off, and every normal model spec.

func ValidateMCPServers ¶ added in v0.20.0

func ValidateMCPServers(servers map[string]MCPServer) error

ValidateMCPServers checks every entry of a server map, reporting the first offending name (in name order, so the message is stable) so the user can fix the file.

func ValidateModelSpec ¶

func ValidateModelSpec(spec string) error

ValidateModelSpec reports whether spec can possibly be used to build a provider, without needing pricing/context metadata for it. It rejects two cases ResolveModel alone can't distinguish by its return value:

  • a bare (no "provider/" prefix) spec that isn't a known alias, model ID, or display name
  • a "provider/model" spec whose model portion IS a known model but registered under a *different* provider (almost certainly a typo, e.g. "openai/sonnet" — sonnet is an Anthropic model)

A "provider/model" spec whose model portion is simply absent from the registry is accepted (nil error): it's treated as a legitimate custom model, just without pricing/context-window metadata.

func WithAgentID ¶

func WithAgentID(ctx context.Context, id string) context.Context

WithAgentID tags ctx with an agent identifier used to isolate per-agent shell state (see pkg/tool.BashState). The root/parent agent uses "" (no tag).

func WithToolCallID ¶

func WithToolCallID(ctx context.Context, id string) context.Context

WithToolCallID tags ctx with the identifier of the tool call being executed.

Types ¶

type AgentEvent ¶

type AgentEvent struct {
	Type string

	// Populated per type:
	Message        AgentMessage       // message_start, message_end, user_message
	AssistantEvent *AssistantEvent    // message_update (streaming deltas)
	Text           string             // steer, user_message (plain-text prompt)
	SteerID        string             // steer
	MsgID          string             // steer, user_message (MsgID of the user message, for client dedup)
	AttachmentIDs  []string           // steers_canceled
	ToolCallID     string             // tool_execution_*
	ToolName       string             // tool_execution_*
	Args           map[string]any     // tool_execution_start
	Result         *Result            // tool_execution_end/update
	IsError        bool               // tool_execution_end
	Rejected       bool               // tool_execution_end (true only for permission denial)
	Messages       []AgentMessage     // agent_end (full conversation)
	Compaction     *CompactionPayload // compaction_end
	Error          error              // agent_error, compaction_end (non-fatal)
}

AgentEvent is emitted by the agent loop for UI/extension consumption.

type AgentMessage ¶

type AgentMessage struct {
	Message
	Custom map[string]any `json:"custom,omitempty"`
}

AgentMessage wraps Message with extension-custom data. Custom messages (role not user/assistant/tool_result) are filtered before LLM calls.

func WrapMessage ¶

func WrapMessage(m Message) AgentMessage

WrapMessage converts a Message to an AgentMessage.

func (AgentMessage) IsLLMMessage ¶

func (m AgentMessage) IsLLMMessage() bool

IsLLMMessage returns true if this message should be sent to the LLM.

type AssistantEvent ¶

type AssistantEvent struct {
	Type         string   `json:"type"`
	ContentIndex int      `json:"content_index,omitempty"`
	Delta        string   `json:"delta,omitempty"`
	Partial      *Message `json:"partial,omitempty"`
	Message      *Message `json:"message,omitempty"`
	Error        error    `json:"-"`

	// Tool call metadata — populated for toolcall_start, toolcall_delta, toolcall_end events.
	ToolCallID  string         `json:"tool_call_id,omitempty"`
	ToolName    string         `json:"tool_name,omitempty"`
	PartialArgs map[string]any `json:"partial_args,omitempty"`

	// RateLimit — populated for the "ratelimit" event, emitted once at stream
	// start from the response headers (independent of message success).
	RateLimit *RateLimit `json:"rate_limit,omitempty"`
}

AssistantEvent is emitted by providers during streaming.

Terminal events: "done" (success) or "error" (failure). Every stream ends with exactly one terminal event, then channel close.

func (AssistantEvent) IsTerminal ¶

func (e AssistantEvent) IsTerminal() bool

IsTerminal returns true for "done" or "error" events.

type AuxiliaryModelAvailable ¶ added in v0.27.0

type AuxiliaryModelAvailable func(provider string) bool

AuxiliaryModelAvailable reports whether normal completion credentials are available for a provider. Transcription-only credentials must not satisfy it.

type CompactionPayload ¶

type CompactionPayload struct {
	Summary        string   `json:"summary"`
	TokensBefore   int      `json:"tokens_before"`
	TokensAfter    int      `json:"tokens_after"`
	ReadFiles      []string `json:"read_files,omitempty"`
	ModifiedFiles  []string `json:"modified_files,omitempty"`
	SummaryMsgID   string   `json:"summary_msg_id,omitempty"`
	FirstKeptMsgID string   `json:"first_kept_msg_id,omitempty"`
	Usage          *Usage   `json:"usage,omitempty"`
}

CompactionPayload is the typed result of a compaction event.

type CompactionSettings ¶

type CompactionSettings struct {
	Enabled       bool `json:"enabled"`
	ReserveTokens int  `json:"reserve_tokens"`       // keep free for model output + thinking
	KeepRecent    int  `json:"keep_recent"`          // tokens of recent context to keep verbatim
	CompactAt     int  `json:"compact_at,omitempty"` // soft threshold in tokens; 0 = use the model window
}

CompactionSettings controls automatic context compaction.

func (CompactionSettings) EffectiveWindow ¶

func (s CompactionSettings) EffectiveWindow(maxInput int) int

EffectiveWindow returns the context window to use for compaction decisions. When CompactAt is set (>0) it caps the model's real window so compaction fires earlier; it is clamped to maxInput, so an over-large value harmlessly degrades to plain overflow protection rather than disabling compaction. It is also floored so a too-low CompactAt can't cause per-turn compaction thrash.

func (CompactionSettings) MinCompactAt ¶

func (s CompactionSettings) MinCompactAt() int

MinCompactAt is the lowest CompactAt that still behaves as asked: below it EffectiveWindow silently raises the threshold to avoid per-turn thrash. A UI offering a threshold has to read this rather than assume, since it moves with ReserveTokens and KeepRecent — a control that let you pick below it would be promising a compaction point the engine will not honor.

type Content ¶

type Content struct {
	Type string `json:"type"`

	// text
	Text string `json:"text,omitempty"`
	// TextSignature carries provider round-trip metadata for a text/message
	// block so the exact item can be replayed on the next request. For the
	// OpenAI Responses API this is a small JSON blob {id, phase} — the model's
	// output message id and its phase ("commentary"/"final_answer"). OpenAI
	// warns that dropping the phase when replaying manually causes "early
	// stopping and other misbehavior", which manifests as empty/stalled turns.
	// Opaque to everything except the provider/model pair that produced it.
	// Replay adapters must discard it when Message.Provider or Message.Model
	// differs from their target.
	TextSignature string `json:"text_signature,omitempty"`

	// thinking
	Thinking          string `json:"thinking,omitempty"`
	ThinkingSignature string `json:"thinking_signature,omitempty"`
	Redacted          bool   `json:"redacted,omitempty"`

	// image/document
	Data     string `json:"data,omitempty"`
	MimeType string `json:"mime_type,omitempty"`
	Filename string `json:"filename,omitempty"`
	// attachment reference (image/document stored out-of-line in the blob store).
	// When AttachmentID is set, Data is empty in persisted/in-memory history and is
	// rehydrated only at request time by the materializer. Size is the decoded byte
	// size, kept so budgeting/sizing works without reading the blob.
	AttachmentID   string `json:"attachment_id,omitempty"`
	AttachmentSize int64  `json:"attachment_size,omitempty"`

	// tool_call
	ToolCallID string         `json:"tool_call_id,omitempty"`
	ToolName   string         `json:"tool_name,omitempty"`
	Arguments  map[string]any `json:"arguments,omitempty"`
	// ToolCallItemID is the provider's output-item id for a tool_call (OpenAI
	// Responses: the "fc_..." id, distinct from ToolCallID which is the
	// "call_id" that pairs the call with its function_call_output). Preserved
	// so the function_call item can be replayed with its original id, matching
	// how the reasoning item that preceded it was paired. Empty for providers
	// that don't use a separate item id.
	ToolCallItemID string `json:"tool_call_item_id,omitempty"`
}

Content is a tagged union. Type determines which fields are populated.

"text"      → Text
"thinking"  → Thinking, ThinkingSignature, Redacted
"image"     → Data, MimeType
"document"  → Data, MimeType, Filename
"tool_call" → ToolCallID, ToolName, Arguments

func CloneContent ¶

func CloneContent(in []Content) []Content

CloneContent returns a deep copy of a content slice (see Content.Clone). A nil input yields a nil output.

func DocumentContent ¶

func DocumentContent(data, mime, filename string) Content

func ImageContent ¶

func ImageContent(data, mime string) Content

func TextContent ¶

func TextContent(text string) Content

Constructors for clarity.

func ThinkingContent ¶

func ThinkingContent(text string) Content

func ToolCallContent ¶

func ToolCallContent(id, name string, args map[string]any) Content

func (Content) Clone ¶

func (c Content) Clone() Content

Clone returns a deep copy of the content: every field is copied by value except Arguments (a map[string]any), which is cloned so the copy shares no mutable backing state with the original. Nested values inside Arguments are copied via cloneAny (maps and slices are rebuilt recursively). Used at ownership boundaries (e.g. when the agent takes a caller-supplied content block into its own state) so a later mutation by the caller can't change the stored message or race a concurrent reader.

type ContextEstimate ¶

type ContextEstimate struct {
	Tokens         int // total estimated context tokens
	UsageTokens    int // from provider-reported usage (0 if none valid)
	TrailingTokens int // estimated tokens for messages after last valid usage
	OverheadTokens int // system prompt + tool specs
}

ContextEstimate holds the result of a context size estimation.

func EstimateContextTokens ¶

func EstimateContextTokens(msgs []AgentMessage, systemPrompt string, toolSpecs []ToolSpec, compactionEpoch int) ContextEstimate

EstimateContextTokens estimates total context size including system prompt and tool spec overhead. Uses provider-reported Usage from the last assistant message whose compaction epoch matches the current one. Stale usage from pre-compaction messages is ignored.

type DocumentCapableProvider ¶

type DocumentCapableProvider interface {
	SupportsDocuments() bool
}

DocumentCapableProvider is an optional interface a Provider may implement to declare whether it accepts native "document" content blocks (e.g. PDFs). Providers that don't implement it are treated as NOT document-capable.

type EmptyResponseError ¶

type EmptyResponseError struct {
	// Provider is the provider name (e.g. "openai").
	Provider string
	// Usage carries the token usage the provider reported for the empty
	// response, if any. An empty completed response can still bill input
	// tokens; the loop must account for this before retrying so a stall can't
	// silently bypass the budget. nil when the provider reported no usage.
	Usage *Usage
}

EmptyResponseError is returned by a provider when a turn completed with no substantive content (no text and no tool call) and the backend gave no signal that it intends to continue. It is a distinct, typed condition so the agent loop can re-sample the same request once (a transient empty turn during polling often self-corrects) before surfacing it as a visible error, rather than ending the run in silence or failing on the first occurrence.

func (*EmptyResponseError) Error ¶

func (e *EmptyResponseError) Error() string

func (*EmptyResponseError) Is ¶

func (e *EmptyResponseError) Is(target error) bool

Is enables errors.Is(err, core.ErrEmptyResponse).

type ExecuteFunc ¶

type ExecuteFunc func(ctx context.Context, params map[string]any, onUpdate func(Result)) (Result, error)

ExecuteFunc runs a tool. onUpdate streams partial results (e.g., bash stdout lines).

type LoadedMoaConfig ¶

type LoadedMoaConfig struct {
	Config      MoaConfig
	MCPDisabled MCPDisableSources
}

LoadedMoaConfig is the merged config plus the disabled-server provenance the merge discards. LoadMoaConfig remains the simple entry point (returns .Config); scope-aware callers use LoadMoaConfigResolved.

func LoadMoaConfigResolved ¶

func LoadMoaConfigResolved(cwd string) LoadedMoaConfig

LoadMoaConfigResolved loads and merges config exactly like LoadMoaConfig, but also returns the disabled-server preference split by scope. The project list is included only when cwd is a trusted project path — matching the trust gate that governs whether the project config is merged at all.

type LockKeyFunc ¶

type LockKeyFunc func(args map[string]any) string

LockKeyFunc returns a canonical path used as a lock key for scheduling. Returns empty string on failure, which causes fallback to shell scheduling.

type MCPDisablePolicy ¶

type MCPDisablePolicy struct {
	Global  map[string]struct{}
	Project map[string]struct{}
	Session map[string]struct{}
}

MCPDisablePolicy is the resolved veto sets for one session, across all three scopes. The session set is process-lifetime and never persisted.

func NewMCPDisablePolicy ¶

func NewMCPDisablePolicy(sources MCPDisableSources) MCPDisablePolicy

NewMCPDisablePolicy builds a policy from loaded config sources. The session set starts empty; callers add to it at runtime.

func (MCPDisablePolicy) DisabledSet ¶

func (p MCPDisablePolicy) DisabledSet() map[string]bool

DisabledSet returns the names disabled for a session across all scopes, ready to hand to Manager.Start as initiallyDisabled.

type MCPDisableResolution ¶

type MCPDisableResolution struct {
	// Disabled is true if any scope vetoes the server.
	Disabled bool
	// Scopes lists every scope that vetoes it, in stable order (global,
	// project, session). Empty when the server is enabled.
	Scopes []MCPDisableScope
}

MCPDisableResolution is the outcome of resolving one server against a policy.

func ResolveMCPDisabled ¶

func ResolveMCPDisabled(name string, p MCPDisablePolicy) MCPDisableResolution

ResolveMCPDisabled resolves whether a server is disabled for a session. Vetoes accumulate: disabled = global OR project OR session. No scope can re-enable a veto from another scope, so the result reports every applicable scope, which lets the UI explain why a server stays disabled after one scope is cleared.

type MCPDisableScope ¶

type MCPDisableScope string

MCPDisableScope identifies the configuration level that vetoes an MCP server. The three scopes are the only values ever produced; this is a closed set, not an open enum persisted to disk (the persisted form is just a list of names per config level).

const (
	// MCPScopeGlobal is the user's global moa config (~/.config/moa/config.json).
	MCPScopeGlobal MCPDisableScope = "global"
	// MCPScopeProject is the repo-local moa config (<cwd>/.moa/config.json),
	// honored only for trusted project paths.
	MCPScopeProject MCPDisableScope = "project"
	// MCPScopeSession is a temporary, in-memory veto for one conversation. It is
	// never persisted.
	MCPScopeSession MCPDisableScope = "session"
)

type MCPDisableSources ¶

type MCPDisableSources struct {
	// Global lists names vetoed by the global config.
	Global []string
	// Project lists names this user vetoed for this workspace. It comes from
	// their own project state, not from <cwd>/.moa/config.json: switching a
	// server off is a preference, while the project file declares which servers
	// exist and is meant to be committed.
	Project []string
	// ProjectTrusted reports whether <cwd>/.moa/config.json is trusted, i.e.
	// whether the project's own MCP definitions are loaded at all.
	ProjectTrusted bool
}

MCPDisableSources is the disabled-server preference split by provenance, which the merged MoaConfig loses. Loaded once so startup, the controller, and the UI all agree on which scope vetoes a server.

type MCPServer ¶

type MCPServer struct {
	Command string            `json:"command"`
	Args    []string          `json:"args"`
	Env     map[string]string `json:"env"`
	// URL is the streamable-HTTP endpoint of a remote MCP server. Only http and
	// https are accepted. It is an outbound connection to an endpoint the
	// operator configured, so it carries the same trust as the rest of the file.
	URL string `json:"url"`
	// Headers are extra HTTP headers sent on every request to URL (typically
	// Authorization). Ignored for command-based servers.
	Headers map[string]string `json:"headers"`
}

MCPServer defines an MCP tool server connection. A server is EITHER command-based (stdio: a local subprocess) OR url-based (streamable HTTP: a remote endpoint). Setting both, or neither, is a configuration error.

func (MCPServer) IsRemote ¶ added in v0.20.0

func (s MCPServer) IsRemote() bool

IsRemote reports whether the server is reached over HTTP rather than spawned as a subprocess.

func (MCPServer) Validate ¶ added in v0.20.0

func (s MCPServer) Validate() error

Validate checks that the entry describes exactly one transport, and that a remote one points at an absolute http(s) URL.

type Message ¶

type Message struct {
	MsgID     string    `json:"msg_id,omitempty"`
	Role      string    `json:"role"`
	Content   []Content `json:"content"`
	Timestamp int64     `json:"timestamp"`

	// assistant-only
	Provider string `json:"provider,omitempty"`
	Model    string `json:"model,omitempty"`
	// RequestedModel is the normalized model selected for this response; Model
	// is the effective model reported by the provider.
	RequestedModel string `json:"requested_model,omitempty"`
	Usage          *Usage `json:"usage,omitempty"`
	StopReason     string `json:"stop_reason,omitempty"`
	ErrorMessage   string `json:"error_message,omitempty"`

	// tool_result-only
	ToolCallID string `json:"tool_call_id,omitempty"`
	ToolName   string `json:"tool_name,omitempty"`
	IsError    bool   `json:"is_error,omitempty"`
}

Message is a tagged union. Role determines which fields are relevant.

"user"        → Content
"assistant"   → Content, Provider, Model, Usage, StopReason
"tool_result" → ToolCallID, ToolName, Content, IsError

func NewToolResultMessage ¶

func NewToolResultMessage(toolCallID, toolName string, content []Content, isError bool) Message

NewToolResultMessage creates a tool_result message.

func NewUserMessage ¶

func NewUserMessage(text string) Message

NewUserMessage creates a user message with text content.

func NewUserMessageWithContent ¶

func NewUserMessageWithContent(content []Content) Message

NewUserMessageWithContent creates a user message with arbitrary content blocks.

func (*Message) EnsureMsgID ¶

func (m *Message) EnsureMsgID()

EnsureMsgID assigns a stable identifier when the message does not have one.

type MoaConfig ¶

type MoaConfig struct {
	DisableSandbox         bool                 `json:"disable_sandbox"`                         // Deprecated: use PathScope. YOLO mode: allow any file path
	AllowedPaths           []string             `json:"allowed_paths"`                           // Additional directories accessible outside workspace
	PathScope              string               `json:"path_scope"`                              // "workspace", "unrestricted", or "" (derive from permission mode)
	Permissions            PermissionsConfig    `json:"permissions"`                             // Tool execution permission policy
	PinnedModels           []string             `json:"pinned_models"`                           // Model IDs pinned for Ctrl+P cycling
	BraveAPIKey            string               `json:"brave_api_key"`                           // Brave Search API key for web_search tool
	MCPServers             map[string]MCPServer `json:"mcp_servers"`                             // MCP tool server connections
	DisabledMCPServers     []string             `json:"disabled_mcp_servers,omitempty"`          // MCP server names vetoed at this config level (server stays configured but is not started)
	TrustedMCPPaths        []string             `json:"trusted_mcp_paths"`                       // Project paths trusted for .mcp.json auto-load
	TrustedProjectPaths    []string             `json:"trusted_project_paths"`                   // Project paths trusted for .moa/config.json + .moa/tools/* auto-load
	PlanReviewModel        string               `json:"plan_review_model"`                       // Model for plan reviewer (default: current model)
	PlanReviewThinking     string               `json:"plan_review_thinking"`                    // Thinking level for plan reviewer (default: "low")
	CodeReviewModel        string               `json:"code_review_model,omitempty"`             // Model for code reviewer (default: plan review model)
	CodeReviewThinking     string               `json:"code_review_thinking,omitempty"`          // Thinking level for code reviewer (default: plan review thinking)
	AutoTitleModel         string               `json:"auto_title_model,omitempty"`              // "auto", "off", or model spec for automatic session titles
	SessionBriefModel      string               `json:"session_brief_model,omitempty"`           // "auto", "off", or model spec for web/Pulse session briefs
	MaxBudget              float64              `json:"max_budget"`                              // Max USD per agent run. 0 = unlimited.
	MaxTurns               int                  `json:"max_turns,omitempty"`                     // Max agent turns per run. 0 = unlimited.
	MaxToolCallsPerTurn    int                  `json:"max_tool_calls_per_turn,omitempty"`       // Max tool calls per turn. 0 = unlimited.
	MaxRunDurationStr      string               `json:"max_run_duration,omitempty"`              // Max run duration as Go duration string (e.g. "30m"). Empty = unlimited.
	MemoryEnabled          *bool                `json:"memory_enabled,omitempty"`                // nil = true (enabled by default)
	AutoVerify             *bool                `json:"auto_verify,omitempty"`                   // nil = false (disabled by default)
	PersistentShell        *bool                `json:"persistent_shell,omitempty"`              // nil = true (enabled by default)
	UpdateCheck            *bool                `json:"update_check,omitempty"`                  // nil = true (check stable releases at most every 6h)
	CacheTTL               string               `json:"cache_ttl,omitempty"`                     // Interactive prompt-cache TTL: "5m" (default) or "1h". Only "1h" changes behavior.
	STTLanguage            string               `json:"stt_language,omitempty"`                  // Speech-to-text language as ISO-639-1 (e.g. "es", "en"). Empty = "en"; "auto" lets the model detect.
	STTModel               string               `json:"stt_model,omitempty"`                     // Speech-to-text model id. Empty = "gpt-transcribe".
	STTVocabulary          []string             `json:"stt_vocabulary,omitempty"`                // Words the transcriber tends to get wrong (names, jargon). Keep it short: long lists hurt accuracy.
	SubagentMaxTurns       int                  `json:"subagent_max_turns,omitempty"`            // Max turns per subagent run. 0 = use package default.
	SubagentMaxRunDuration string               `json:"subagent_max_run_duration,omitempty"`     // Max subagent run duration as Go duration string. Empty = use package default.
	SubagentMaxConcurrent  int                  `json:"subagent_max_concurrent_async,omitempty"` // Max concurrent async subagents. 0 = use package default.
	SubagentAllowedModels  []string             `json:"subagent_allowed_models,omitempty"`       // Model IDs a subagent may run under. Empty/absent = no restriction (opt-in).
}

MoaConfig holds sandbox, path, and permission settings. Loaded from config files at three levels: global (~/.config/moa/config.json), project (<cwd>/.moa/config.json), and session (flags). Merged with OR for booleans, concatenation for slices.

func LoadGlobalConfig ¶

func LoadGlobalConfig() MoaConfig

LoadGlobalConfig loads only the user's global moa config (~/.config/moa/config.json), without merging any project config. Callers that need the trusted-project allowlist or global-only settings use this.

func LoadMoaConfig ¶

func LoadMoaConfig(cwd string) MoaConfig

LoadMoaConfig reads and merges config from global and project levels. Global: ~/.config/moa/config.json. Project: <cwd>/.moa/config.json. Project values override/extend global values. Also loads global .mcp.json (always). Project .mcp.json is handled separately in main.go behind a trust gate.

type Model ¶

type Model struct {
	ID        string   `json:"id"`
	Provider  string   `json:"provider"`
	API       string   `json:"api"`
	Name      string   `json:"name"`
	MaxInput  int      `json:"max_input"`
	MaxOutput int      `json:"max_output"`
	Pricing   *Pricing `json:"pricing,omitempty"`
}

Model identifies an LLM model.

func ResolveAuxiliaryModel ¶ added in v0.27.0

func ResolveAuxiliaryModel(spec string, available AuxiliaryModelAvailable) (Model, bool, error)

ResolveAuxiliaryModel resolves an auto-title or session-brief model setting. Empty is equivalent to "auto". Auto deliberately considers only the two inexpensive completion models: Luna first, then Haiku. It never selects Grok merely because xAI credentials happen to be present.

func ResolveModel ¶

func ResolveModel(spec string) (Model, bool)

ResolveModel resolves a model specifier to a fully-populated Model.

Accepted formats:

  • "sonnet" → alias lookup
  • "claude-sonnet-4-6" → direct registry lookup
  • "anthropic/claude-sonnet-4" → provider prefix (strips prefix, looks up rest)
  • "openai/gpt-5.3-codex" → provider prefix

For unknown models, returns a Model with MaxInput=0 and ok=false.

When a "provider/model" spec resolves to a known model whose registered Provider differs from the requested prefix (e.g. "openai/sonnet", where "sonnet" is an Anthropic model), ok is false — a provider/model mismatch on a *known* model name is treated as caller error, not as an intentional custom model. A provider/model pair that resolves to no known model at all is still accepted as a legitimate custom model spec (ok=false, but Provider/ID are populated verbatim so callers can still use it — pricing and context-window metadata will simply be absent). Use ValidateModelSpec to distinguish these two ok=false cases when that matters (e.g. to decide whether to fail fast at config-parse time).

type ModelEntry ¶

type ModelEntry struct {
	Model Model
	Alias string // shortest alias, empty if none
}

ListModels returns all unique known models, deduplicated by ID, sorted by provider then name. Each model also carries its shortest alias.

func ListModels ¶

func ListModels() []ModelEntry

type PermissionsConfig ¶

type PermissionsConfig struct {
	Mode  string   `json:"mode"`  // "yolo", "ask", or "auto" (default: "yolo")
	Allow []string `json:"allow"` // Glob patterns auto-approved in ask mode: "Bash(npm:*)", "edit"
	Deny  []string `json:"deny"`  // Glob patterns always denied (checked before allow)
	Model string   `json:"model"` // Model for auto mode evaluator (e.g. "haiku")
	Rules []string `json:"rules"` // Natural language rules for auto mode
}

PermissionsConfig controls tool execution approval.

type Pricing ¶

type Pricing struct {
	Input      float64 `json:"input"`       // $/M input tokens
	Output     float64 `json:"output"`      // $/M output tokens
	CacheRead  float64 `json:"cache_read"`  // $/M cached input tokens
	CacheWrite float64 `json:"cache_write"` // $/M cache write tokens

	// Tiers holds additional pricing tiers keyed by a context-length
	// threshold, for providers that charge more once the prompt exceeds a
	// given size. Must be sorted ascending by Threshold.
	Tiers []PricingTier `json:"tiers,omitempty"`
}

Pricing holds per-token costs in USD per million tokens.

Some providers (e.g. OpenAI's long-context GPT models) charge a different flat rate once the prompt exceeds a context-length threshold. Tiers lists those higher-context rates in ascending Threshold order; the base Input/Output/CacheRead/CacheWrite fields are the tier that applies below the first threshold ("short context"). Cost picks the tier by the request's total input context (Input+CacheRead tokens count toward the prompt length the provider bills against) and applies it to the *whole* request, matching how these providers actually bill — not a blended rate.

func (*Pricing) Cost ¶

func (p *Pricing) Cost(u Usage) float64

Cost calculates the USD cost for a given Usage, selecting the pricing tier based on the request's total context (Input+CacheRead tokens) and applying that tier's rates to the entire request.

type PricingTier ¶

type PricingTier struct {
	Threshold  int     `json:"threshold"`   // tier applies when Input+CacheRead >= this
	Input      float64 `json:"input"`       // $/M input tokens
	Output     float64 `json:"output"`      // $/M output tokens
	CacheRead  float64 `json:"cache_read"`  // $/M cached input tokens
	CacheWrite float64 `json:"cache_write"` // $/M cache write tokens
}

PricingTier is a pricing tier that applies once the request's context (input + cache-read tokens) reaches Threshold tokens.

type ProjectState ¶ added in v0.24.0

type ProjectState struct {
	// PermissionAllow holds patterns approved with "always allow".
	PermissionAllow []string `json:"permission_allow,omitempty"`
	// DisabledMCPServers holds servers this user switched off for this project.
	DisabledMCPServers []string `json:"disabled_mcp_servers,omitempty"`
	// Config is settings you want in this project but not in the repository —
	// a turn limit you prefer here, a review model, your own budget. It is
	// hand-edited: moa never writes it. Merged after the project's own config
	// and before session flags, and subject to the same rules, so it can
	// tighten a limit the project set but never relax one.
	Config *MoaConfig `json:"config,omitempty"`
	// LegacyVetoImported records that vetoes an older moa wrote into the
	// project's own config were carried over. It has to be remembered: without
	// it the import would run again after the user switches a server back on,
	// and the toggle would appear to do nothing.
	LegacyVetoImported bool `json:"legacy_veto_imported,omitempty"`
}

ProjectState is what moa decides on its own while the user works in a project: approvals they granted, MCP servers they switched off.

It is deliberately not a MoaConfig. <project>/.moa/config.json describes the project — which MCP servers it uses, what its limits are — and is meant to be committed and shared. What one person clicked is not that: it followed the user's machine into the repository, showed up as a diff nobody wanted, and on a shared checkout the first writer's private permissions locked everyone else out of the directory. So it lives with the user instead.

func LoadProjectState ¶ added in v0.24.0

func LoadProjectState(workspaceRoot string) (ProjectState, error)

LoadProjectState reads this user's state for a workspace. A missing file is an empty state; anything else is an error, because silently treating an unreadable file as "no approvals" would re-prompt for everything with no indication why.

type Provider ¶

type Provider interface {
	Stream(ctx context.Context, req Request) (<-chan AssistantEvent, error)
}

Provider streams LLM responses. Each provider (Anthropic, OpenAI, etc.) implements this interface, emitting normalized AssistantEvents.

Error contract:

  • Returns error immediately for pre-stream failures (auth, invalid model, network).
  • If channel is returned, it ALWAYS receives exactly one terminal event ("done" or "error") before being closed.
  • The caller must drain the channel to avoid goroutine leaks.
  • Context cancellation causes an "error" event with ctx.Err().

type ProviderUnwrapper ¶

type ProviderUnwrapper interface {
	Unwrap() Provider
}

ProviderUnwrapper is optionally implemented by Provider decorators to expose the provider they wrap. Capability helpers follow this chain so decorators do not hide optional provider interfaces.

Unwrap must return nil when there is no wrapped provider.

type QuotaExceededError ¶

type QuotaExceededError struct {
	// Provider is the provider name (e.g. "openai", "anthropic").
	Provider string
	// Message is the human-readable message from the provider, if any.
	Message string
	// PlanType is the subscription plan reported by the provider (may be empty).
	PlanType string
	// ResetsIn is the time until the exhausted window resets (0 if unknown).
	ResetsIn time.Duration
	// ResetsAt is the wall-clock reset time (zero if unknown).
	ResetsAt time.Time
	// Window labels which limit was hit ("5h", "weekly", or "" if unknown).
	Window string
}

QuotaExceededError is returned by a provider when the account's usage limit has been reached (e.g. a ChatGPT/Codex subscription 5-hour or weekly window), as opposed to a transient rate limit that a retry would clear. It is NOT a user cancellation: callers must surface it as an actionable "limit reached, resets in X" message rather than a generic error or an interruption marker.

func AsQuotaExceeded ¶

func AsQuotaExceeded(err error) (*QuotaExceededError, bool)

AsQuotaExceeded extracts a *QuotaExceededError from an error chain, if present.

func (*QuotaExceededError) Error ¶

func (e *QuotaExceededError) Error() string

func (*QuotaExceededError) Is ¶

func (e *QuotaExceededError) Is(target error) bool

Is reports whether target is a *QuotaExceededError, enabling errors.Is checks against the ErrQuotaExceeded sentinel.

type RateLimit ¶

type RateLimit struct {
	Status              string  `json:"status,omitempty"`               // allowed / allowed_warning / rejected
	RepresentativeClaim string  `json:"representative_claim,omitempty"` // window that currently binds: five_hour / seven_day / overage / ...
	FiveHourUtil        float64 `json:"five_hour_util"`                 // [0,1], or -1 if unknown
	SevenDayUtil        float64 `json:"seven_day_util"`                 // [0,1], or -1 if unknown
	OverageStatus       string  `json:"overage_status,omitempty"`
	OverageUtil         float64 `json:"overage_util"` // [0,1], or -1 if unknown
}

RateLimit captures the unified rate-limit state a provider reports on each response (Anthropic's anthropic-ratelimit-unified-* headers).

Utilization fields are fractions in [0,1], or -1 when the corresponding header was absent/invalid — callers must treat -1 as "unknown" and NOT overwrite a known value with it (the endpoint is reverse-engineered and may change shape).

It lets callers see, per request, how much of each plan window is used and whether the request was served from pay-as-you-go "extra usage" — instantly, without polling the account-global usage endpoint.

func (RateLimit) OnOverage ¶

func (r RateLimit) OnOverage() bool

OnOverage reports whether the request is currently being served from extra usage — i.e. the binding rate-limit window is the overage bucket.

type Registry ¶

type Registry struct {
	// contains filtered or unexported fields
}

Registry holds registered tools. Thread-safe.

func NewRegistry ¶

func NewRegistry() *Registry

NewRegistry creates an empty tool registry.

func (*Registry) All ¶

func (r *Registry) All() []Tool

All returns all registered tools (snapshot), sorted by name for deterministic order.

func (*Registry) Count ¶

func (r *Registry) Count() int

Count returns the number of registered tools.

func (*Registry) Get ¶

func (r *Registry) Get(name string) (Tool, bool)

Get returns a tool by name.

func (*Registry) Register ¶

func (r *Registry) Register(t Tool) error

Register adds or replaces a tool. Returns error if a WritePath tool is missing its LockKey function.

func (*Registry) Specs ¶

func (r *Registry) Specs() []ToolSpec

Specs returns ToolSpecs for all registered tools (for sending to LLM), sorted by name.

func (*Registry) Unregister ¶

func (r *Registry) Unregister(name string)

Unregister removes a tool.

func (*Registry) WithInternalTools ¶

func (r *Registry) WithInternalTools(internal ...Tool) (*Registry, error)

WithInternalTools returns a new, isolated registry containing this registry's current tools plus internal tools. It never mutates the receiver, including when an internal tool uses a reserved name. Creating an overlay only changes tool visibility; it does not grant permissions to execute any tool in the overlay.

type Request ¶

type Request struct {
	Model    Model
	System   string     // System prompt
	Messages []Message  // Conversation history (user, assistant, tool_result)
	Tools    []ToolSpec // Available tools for tool_use
	Options  StreamOptions
}

Request contains everything needed for an LLM call.

type Result ¶

type Result struct {
	Content []Content `json:"content"`
	IsError bool      `json:"is_error,omitempty"`
	// Custom annotates the recorded tool result with facts the UI needs but the
	// model does not — it is merged into the tool_result message's own Custom
	// map and never reaches the provider. The subagent tool uses it to record
	// which job a call spawned, a link that is otherwise lost on restart.
	Custom map[string]any `json:"custom,omitempty"`
}

Result is what a tool returns to the LLM. Uses the same Content type as messages — no duplication.

func ErrorResult ¶

func ErrorResult(msg string) Result

ErrorResult creates a Result representing an error message. Sets IsError=true so the agent loop can detect tool-level errors even when the tool returns (Result, nil) instead of (Result, error).

func TextResult ¶

func TextResult(text string) Result

TextResult creates a Result with a single text content block.

type SteerItem ¶

type SteerItem struct {
	ID   string `json:"id"`
	Text string `json:"text"`
	// Custom is persisted with the eventual conversation message. It lets
	// internal ingress retain its rendering/source metadata when it has to wait
	// on the queue rail instead of starting a direct run.
	Custom map[string]any `json:"-"`
	// Content, when non-nil, is the full payload of a steer (text plus image or
	// other content blocks). It is injected with NewUserMessageWithContent. A
	// nil Content means a plain-text steer carried in Text.
	Content []Content `json:"content,omitempty"`
	// Command, when non-empty, marks this item as a queued command (a BARRIER):
	// it holds the raw normalized command line (e.g. "/compact", "/model sonnet").
	// A barrier item is never injected as a conversation message — it stops the
	// queue drain, and is executed at the next idle point (RunEnded) by the bus.
	// Invariant: a barrier carries no Content, and an Internal item is never a
	// barrier.
	Command string `json:"command,omitempty"`
	// Internal marks a system-generated steer (e.g. a subagent/bash completion
	// injected into the parent run) as opposed to a user-typed message. Internal
	// steers are delivered to the agent but excluded from the authoritative
	// queue snapshot, since their delivery event is suppressed and they must not
	// surface as user-visible "queued" chips.
	Internal bool `json:"-"`
}

SteerItem is a queued item in the agent's unified queue rail. It is either a steering message (text, optionally with image/content blocks) injected into a run, or a queued command that acts as a turn barrier (see Command). Items are consumed in strict FIFO order, so a command queued between two messages runs exactly in that position.

func (SteerItem) IsBarrier ¶

func (it SteerItem) IsBarrier() bool

IsBarrier reports whether this item is a queued command that stops the run (a turn barrier) rather than a steer injected into the current run.

type StreamOptions ¶

type StreamOptions struct {
	Temperature    *float64 `json:"temperature,omitempty"`
	MaxTokens      *int     `json:"max_tokens,omitempty"`
	APIKey         string   `json:"-"`
	ThinkingLevel  string   `json:"thinking_level,omitempty"`
	CacheRetention string   `json:"cache_retention,omitempty"`
}

StreamOptions configures an LLM request.

type Tool ¶

type Tool struct {
	Name        string          `json:"name"`
	Label       string          `json:"label"`
	Description string          `json:"description"`
	Parameters  json.RawMessage `json:"parameters"`
	SpecFunc    func() ToolSpec `json:"-"` // Lets schemas that reflect live policy be refreshed for each provider request.
	Execute     ExecuteFunc     `json:"-"`
	Effect      ToolEffect      `json:"-"` // scheduling hint for conflict-aware execution
	LockKey     LockKeyFunc     `json:"-"` // required when Effect is EffectWritePath
}

Tool is a callable function with JSON Schema parameters.

func (Tool) Spec ¶

func (t Tool) Spec() ToolSpec

Spec returns a ToolSpec (definition without the execute function).

type ToolCallDecision ¶

type ToolCallDecision struct {
	Block  bool
	Reason string
	Kind   string // optional classification (e.g. permission, policy)
}

ToolCallDecision is returned by tool-call hooks to optionally block execution.

type ToolEffect ¶

type ToolEffect int

ToolEffect classifies a tool's side effects for the conflict-aware scheduler. The zero value (EffectUnknown) is treated as a barrier — safe by default.

const (
	EffectUnknown     ToolEffect = iota // zero value — serialized (conservative)
	EffectReadOnly                      // no side effects — safe to parallelize
	EffectWritePath                     // writes to a specific path via LockKey
	EffectShell                         // may write anywhere — acts as barrier
	EffectInteractive                   // blocks on a human response (e.g. ask_user) — acts as a total barrier
)

type ToolSpec ¶

type ToolSpec struct {
	Name        string          `json:"name"`
	Description string          `json:"description"`
	Parameters  json.RawMessage `json:"parameters"`
}

ToolSpec is a tool definition sent to the LLM (name + description + JSON schema). Separate from the executable Tool to keep the provider layer dependency-free.

type TranscribeOptions ¶

type TranscribeOptions struct {
	// Language is an ISO-639-1 hint (e.g. "es", "en"). Empty lets the provider
	// auto-detect. Setting it avoids mis-detection on short/ambiguous audio.
	Language string
	// Prompt biases the decoder toward specific vocabulary/spelling. Optional.
	Prompt string
	// Model is the provider's model id. Empty lets the provider pick its own
	// default, so callers that do not care keep working.
	Model string
}

TranscribeOptions tunes a speech-to-text request.

type Transcriber ¶

type Transcriber interface {
	Transcribe(ctx context.Context, audio io.Reader, filename string, opts TranscribeOptions) (string, error)
}

Transcriber converts audio to text. Providers that support speech-to-text (e.g. OpenAI) implement this interface.

type Usage ¶

type Usage struct {
	Input       int `json:"input"`
	Output      int `json:"output"`
	CacheRead   int `json:"cache_read"`
	CacheWrite  int `json:"cache_write"`
	TotalTokens int `json:"total_tokens"`
}

Usage tracks token consumption for a single LLM call.

Jump to

Keyboard shortcuts

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