Documentation
¶
Index ¶
- Constants
- Variables
- func AgentIDFromContext(ctx context.Context) string
- func BuildSTTPrompt(vocabulary []string) string
- func CacheTTLDuration(cfg MoaConfig) time.Duration
- func CanonicalOrRaw(path string) string
- func CanonicalizePath(path string) (string, error)
- func EstimateOutputTokens(m Message) int
- func EstimateTokens(m Message) int
- 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 IsAutoVerifyEnabled(cfg MoaConfig) bool
- func IsMCPPathTrusted(cfg MoaConfig, path string) bool
- func IsMemoryEnabled(cfg MoaConfig) 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 NativeDocBytes(content []Content) int64
- func NewMsgID() string
- func NewSteerID() string
- func ProviderSupportsDocuments(p Provider) bool
- func RegisterOrLog(reg *Registry, t Tool)
- func ReservedToolName(name string) bool
- func ResolveMaxOutputTokens(model Model, requested *int) int
- func ResolvePathScope(pathScope string, disableSandbox bool, permMode string) string
- func SaveGlobalConfig(update func(*MoaConfig)) error
- func SaveProjectConfig(cwd string, update func(*MoaConfig)) error
- func SetMCPServerDisabled(cfg *MoaConfig, name string, disabled bool)
- func ShouldCompact(contextTokens, contextWindow int, settings CompactionSettings) bool
- func ThinkingLevelOptions() string
- func ToolCallIDFromContext(ctx context.Context) string
- 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 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 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 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 AgentIDFromContext ¶
AgentIDFromContext returns the agent id set by WithAgentID, or "" if none.
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 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 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 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 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 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 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 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 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 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 ThinkingLevelOptions ¶
func ThinkingLevelOptions() string
ThinkingLevelOptions returns a human-readable list for error messages.
func ToolCallIDFromContext ¶
ToolCallIDFromContext returns the tool call id set by WithToolCallID, or "" if the context does not represent a tool call.
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 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 that produced it.
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 vetoed by the project config. It is populated only
// when ProjectTrusted is true; an untrusted project's preference is never
// silently applied.
Project []string
// ProjectTrusted reports whether <cwd>/.moa/config.json is trusted, i.e.
// whether the project scope is writable/applicable 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"`
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)
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.
}
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 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 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) 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"`
// 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"`
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.