Documentation
¶
Index ¶
- Constants
- Variables
- func AcquireFileLock(path string) (io.Closer, error)
- func AddProjectAllowPattern(workspaceRoot, pattern string) error
- func AgentIDFromContext(ctx context.Context) string
- func AllowedModelAliases(allowedIDs []string) []string
- func BuildSTTPrompt(vocabulary []string) string
- func CacheTTLDuration(cfg MoaConfig) time.Duration
- func CanonicalOrRaw(path string) string
- func CanonicalizePath(path string) (string, error)
- func CloneArgs(m map[string]any) map[string]any
- func CodebaseKey(dir string) string
- func ConfigDir() string
- func ConfigSubdir(parts ...string) string
- func CorrectImageMime(b64, declared string) string
- func EffectiveThinkingLevel(model Model, level string) (string, error)
- func EstimateOutputTokens(m Message) int
- func EstimateTokens(m Message) int
- func ExtractAssistantText(msg AgentMessage) string
- func ExtractFinalAssistantText(msgs []AgentMessage) string
- func GetCacheTTL(cfg MoaConfig) string
- func GetMaxRunDuration(cfg MoaConfig) time.Duration
- func GetSTTLanguage(cfg MoaConfig) string
- func GetSTTModel(cfg MoaConfig) string
- func GetSubagentMaxRunDuration(cfg MoaConfig) time.Duration
- func ImageDimensions(data []byte) (width, height int)
- func ImageExceedsMaxDimension(b64 string) (width, height int, exceeds bool)
- func ImageMimeFromBytes(data []byte) string
- func ImportLegacyProjectVeto(cwd string) error
- func IsAutoVerifyEnabled(cfg MoaConfig) bool
- func IsMCPPathTrusted(cfg MoaConfig, path string) bool
- func IsMemoryEnabled(cfg MoaConfig) bool
- func IsModelAllowed(model Model, allowedIDs []string) bool
- func IsPersistentShellEnabled(cfg MoaConfig) bool
- func IsProjectPathTrusted(cfg MoaConfig, path string) bool
- func IsUpdateCheckEnabled(cfg MoaConfig) bool
- func IsValidThinkingLevel(level string) bool
- func LoadMCPFile(path string) (map[string]MCPServer, error)
- func MergeMCPServers(maps ...map[string]MCPServer) map[string]MCPServer
- func ModelAliases() []string
- func NativeDocBytes(content []Content) int64
- func NewMsgID() string
- func NewSteerID() string
- func ProjectHash(workspaceRoot string) string
- func ProjectStateDir(workspaceRoot string) string
- func ProviderSupportsDocuments(p Provider) bool
- func RegisterOrLog(reg *Registry, t Tool)
- func RepoCodebaseKey(dir string) (string, bool)
- func ReservedToolName(name string) bool
- func ResolveMaxOutputTokens(model Model, requested *int) int
- func ResolvePathScope(pathScope string, disableSandbox bool, permMode string) string
- func SameModelIdentity(requested, effective string) bool
- func SaveGlobalConfig(update func(*MoaConfig)) error
- func SaveProjectConfig(cwd string, update func(*MoaConfig)) error
- func SetMCPServerDisabled(cfg *MoaConfig, name string, disabled bool)
- func SetProjectMCPServerDisabled(workspaceRoot, server string, disabled bool) error
- func ShouldCompact(contextTokens, contextWindow int, settings CompactionSettings) bool
- func SuggestAliasFrom(spec string, aliases []string) string
- func SuggestModelAlias(spec string) string
- func ThinkingLevelOptions() string
- func ThinkingLevelsForModel(model Model) []string
- func ToolCallIDFromContext(ctx context.Context) string
- func UpdatePinnedModels(models []string, id string, pinned bool) []string
- func UpdateProjectState(workspaceRoot string, update func(*ProjectState)) error
- func ValidateAuxiliaryModelConfig(cfg MoaConfig) error
- func ValidateAuxiliaryModelSpec(spec string) error
- func ValidateMCPServers(servers map[string]MCPServer) error
- func ValidateModelSpec(spec string) error
- func WithAgentID(ctx context.Context, id string) context.Context
- func WithToolCallID(ctx context.Context, id string) context.Context
- type AgentEvent
- type AgentMessage
- type AssistantEvent
- type AuxiliaryModelAvailable
- type CompactionPayload
- type CompactionSettings
- type Content
- type ContextEstimate
- type DocumentCapableProvider
- type EmptyResponseError
- type ExecuteFunc
- type LoadedMoaConfig
- type LockKeyFunc
- type MCPDisablePolicy
- type MCPDisableResolution
- type MCPDisableScope
- type MCPDisableSources
- type MCPServer
- type Message
- type MoaConfig
- type Model
- type ModelEntry
- type PermissionsConfig
- type Pricing
- type PricingTier
- type ProjectState
- type Provider
- type ProviderUnwrapper
- type QuotaExceededError
- type RateLimit
- type Registry
- func (r *Registry) All() []Tool
- func (r *Registry) Count() int
- func (r *Registry) Get(name string) (Tool, bool)
- func (r *Registry) Register(t Tool) error
- func (r *Registry) Specs() []ToolSpec
- func (r *Registry) Unregister(name string)
- func (r *Registry) WithInternalTools(internal ...Tool) (*Registry, error)
- type Request
- type Result
- type SteerItem
- type StreamOptions
- type Tool
- type ToolCallDecision
- type ToolEffect
- type ToolSpec
- type TranscribeOptions
- type Transcriber
- type Usage
Constants ¶
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.
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.
const ( // ToolCallDecisionKindPermission marks user-facing permission denials. ToolCallDecisionKindPermission = "permission" // ToolCallDecisionKindPolicy marks non-permission policy/plan blocks. ToolCallDecisionKindPolicy = "policy" )
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.
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").
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 ¶
var DefaultCompactionSettings = CompactionSettings{ Enabled: true, ReserveTokens: 16384, KeepRecent: 20000, }
DefaultCompactionSettings provides sensible defaults.
var ErrEmptyResponse = &EmptyResponseError{}
ErrEmptyResponse is a sentinel for errors.Is(err, core.ErrEmptyResponse).
var ErrQuotaExceeded = &QuotaExceededError{}
ErrQuotaExceeded is a sentinel for errors.Is(err, core.ErrQuotaExceeded).
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.
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
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
AddProjectAllowPattern records an "always allow" approval for a workspace.
func AgentIDFromContext ¶
AgentIDFromContext returns the agent id set by WithAgentID, or "" if none.
func AllowedModelAliases ¶ added in v0.30.0
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 ¶
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 ¶
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 ¶
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 ¶
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
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
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
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
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
EffectiveThinkingLevel resolves a persisted level for a model. xAI requires reasoning and only accepts low/medium/high; other providers keep the value.
func EstimateOutputTokens ¶
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 ¶
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 ¶
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 ¶
GetMaxRunDuration parses MaxRunDurationStr into a time.Duration. Returns 0 (unlimited) if empty or invalid.
func GetSTTLanguage ¶
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 ¶
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 ¶
GetSubagentMaxRunDuration parses SubagentMaxRunDuration into a time.Duration. Returns 0 (use package default) if empty or invalid.
func ImageDimensions ¶
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 ¶
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
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
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 ¶
IsAutoVerifyEnabled returns whether auto-verify is enabled. Default is false when AutoVerify is nil (not configured).
func IsMCPPathTrusted ¶
IsMCPPathTrusted reports whether path is in the config's trusted MCP paths.
func IsMemoryEnabled ¶
IsMemoryEnabled returns whether cross-session memory is enabled. Default is true when MemoryEnabled is nil (not configured).
func IsModelAllowed ¶ added in v0.30.0
IsModelAllowed reports whether model may be used under allowedIDs. An empty list means unrestricted, so existing installs keep working untouched.
func IsPersistentShellEnabled ¶
IsPersistentShellEnabled returns whether the bash tool persists cwd and exported env across calls. Default is true when PersistentShell is nil.
func IsProjectPathTrusted ¶
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 ¶
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 ¶
IsValidThinkingLevel reports whether level is a recognized thinking level.
func LoadMCPFile ¶
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 ¶
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 ¶
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
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
ProjectStateDir returns where this user's state for a workspace lives, or "" when the config directory cannot be resolved.
func ProviderSupportsDocuments ¶
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 ¶
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
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 ¶
ReservedToolName reports whether name is reserved for an internal tool which must never be supplied by scripts, extensions, or MCP servers.
func ResolveMaxOutputTokens ¶
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 ¶
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:
- Explicit pathScope ("workspace" or "unrestricted") — use as-is
- Legacy disableSandbox: true → "unrestricted"
- Derive from permission mode: - "yolo" or "ask" → "unrestricted" - "auto" → "workspace" - "" (unresolved) → "workspace" (conservative fallback; see note above)
func SameModelIdentity ¶ added in v0.29.0
SameModelIdentity compares model IDs after resolving known aliases. Unknown provider-returned IDs remain comparable without pretending they are aliases.
func SaveGlobalConfig ¶
SaveGlobalConfig reads the current global config, applies update, and writes it back atomically. Creates ~/.config/moa/ if it doesn't exist.
func SaveProjectConfig ¶
SaveProjectConfig reads the current project config, applies update, and writes it back atomically. Creates <cwd>/.moa/ if it doesn't exist.
func SetMCPServerDisabled ¶
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
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
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
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
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 ¶
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
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
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
ValidateAuxiliaryModelSpec validates a config value without requiring credentials. It accepts auto, off, and every normal model spec.
func ValidateMCPServers ¶ added in v0.20.0
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 ¶
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 ¶
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).
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 ¶
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
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 ¶
CloneContent returns a deep copy of a content slice (see Content.Clone). A nil input yields a nil output.
func DocumentContent ¶
func ImageContent ¶
func ThinkingContent ¶
func (Content) Clone ¶
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 ¶
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.
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 ¶
NewToolResultMessage creates a tool_result message.
func NewUserMessage ¶
NewUserMessage creates a user message with text content.
func NewUserMessageWithContent ¶
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 ¶
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 ¶
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 ¶
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.
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.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds registered tools. Thread-safe.
func (*Registry) All ¶
All returns all registered tools (snapshot), sorted by name for deterministic order.
func (*Registry) Register ¶
Register adds or replaces a tool. Returns error if a WritePath tool is missing its LockKey function.
func (*Registry) Specs ¶
Specs returns ToolSpecs for all registered tools (for sending to LLM), sorted by name.
func (*Registry) Unregister ¶
Unregister removes a tool.
func (*Registry) WithInternalTools ¶
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 ¶
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 ¶
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.
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.
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.