agui

package
v0.2.0 Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
const DiscoveryPath = "/agui/agents.json"

DiscoveryPath is the unauthenticated discovery endpoint listing the AG-UI workloads this daemon exposes (docs/ag-ui-design.md open question 8).

View Source
const ProtocolVersion = "0.1"

ProtocolVersion is the AG-UI protocol line this server implements. It is advertised in the discovery descriptor and pinned per release (docs/ag-ui-design.md).

Variables

View Source
var ErrNotResumable = errors.New("agui: session is not awaiting input (nothing to resume)")

ErrNotResumable marks a resume request (RunAgentInput.Resume present) whose target session is not awaiting input — no such session, or it is not paused on any interrupt the client named. When a Backend returns it before emitting any frame, the server reports HTTP 409 (Conflict): the client's answer does not correspond to an open interrupt, so driving a fresh turn would silently discard it. Refusing before the SSE upgrade keeps this a clean HTTP status rather than a fabricated stream.

View Source
var ErrUnavailable = errors.New("agui: backend temporarily unavailable")

ErrUnavailable marks a transiently-unavailable backend — e.g. a daemon draining for shutdown that refuses new work. When a Backend returns it before emitting any frame, the server reports HTTP 503 (retryable) rather than opening a stream it cannot fill.

Functions

This section is empty.

Types

type AgentDescriptor

type AgentDescriptor struct {
	Name            string         `json:"name"`
	Endpoint        string         `json:"endpoint"`
	Description     string         `json:"description,omitempty"`
	InputSchema     map[string]any `json:"input_schema,omitempty"`
	ProtocolVersion string         `json:"protocol_version"`
	Auth            DescriptorAuth `json:"auth"`
}

AgentDescriptor is one workload's entry in the discovery document. Field names follow the AG-UI discovery convention (snake_case for the compound keys, matching the bundle's agui: config surface).

type Backend

type Backend interface {
	RunAgent(ctx context.Context, in RunInput, emit func(any)) (RunResult, error)
}

Backend drives an AG-UI run against the mast runtime. The daemon implements it over runTurnPre (cmd/mast/agui.go); this package never imports the runtime. emit is called synchronously and in order on the calling goroutine — the SSE handler writes each frame to the wire — so implementations need no locking around it. The backend emits the opening frames (RunStarted, then StateSnapshot) and all interior frames; the server emits the terminal frame from the returned RunResult. A backend that cannot start the turn (draining) must return ErrUnavailable BEFORE any emit, so the server can report a clean HTTP error instead of a truncated stream. A backend must not emit after returning.

type Config

type Config struct {
	// Listen is the bind address, e.g. ":7781". Used by ListenAndServe.
	Listen string

	// Exposed are the workloads served, each on its own EndpointPath. An
	// empty slice serves only the discovery endpoint; the daemon starts the
	// server only when at least one workload opts in.
	Exposed []ExposedWorkload

	// Validator authenticates every run request. Nil disables auth (dev
	// only). When set, a request without a valid bearer is refused 401 before
	// any dispatch.
	Validator serverauth.TokenValidator

	// Limiter, when non-nil, admits or refuses each run request before
	// dispatch — see serverauth.RateLimiter. Nil disables rate limiting.
	Limiter serverauth.RateLimiter

	// Backend is required.
	Backend Backend

	// Metric, when non-nil, records run outcomes.
	Metric RunMetric

	// Logger defaults to slog.Default().
	Logger *slog.Logger

	// BaseContext, when non-nil, is the context every request derives from
	// (the daemon passes its turn lifetime).
	BaseContext context.Context
}

Config configures the AG-UI server.

type Context

type Context struct {
	Description string `json:"description,omitempty"`
	Value       string `json:"value,omitempty"`
}

Context is one supplementary context entry a client attaches to a run (docs/ag-ui-design.md RunAgentInput). Carried through opaquely.

type DescriptorAuth

type DescriptorAuth struct {
	Required bool     `json:"required"`
	Scopes   []string `json:"scopes,omitempty"`
}

DescriptorAuth describes the auth a workload's endpoint requires. Required is true whenever the server has a validator configured; Scopes lists the per-workload scopes a caller additionally needs.

type EventType

type EventType string

EventType is the discriminator on every AG-UI SSE event's "type" field.

const (
	// Lifecycle.
	EventRunStarted   EventType = "RUN_STARTED"
	EventRunFinished  EventType = "RUN_FINISHED"
	EventRunError     EventType = "RUN_ERROR"
	EventStepStarted  EventType = "STEP_STARTED"
	EventStepFinished EventType = "STEP_FINISHED"

	// Assistant text (streamed as start → one-or-more content deltas → end).
	EventTextMessageStart   EventType = "TEXT_MESSAGE_START"
	EventTextMessageContent EventType = "TEXT_MESSAGE_CONTENT"
	EventTextMessageEnd     EventType = "TEXT_MESSAGE_END"

	// Tool calls (start → args deltas → end, then a result once available).
	EventToolCallStart  EventType = "TOOL_CALL_START"
	EventToolCallArgs   EventType = "TOOL_CALL_ARGS"
	EventToolCallEnd    EventType = "TOOL_CALL_END"
	EventToolCallResult EventType = "TOOL_CALL_RESULT"

	// Shared state.
	EventStateSnapshot EventType = "STATE_SNAPSHOT"
	EventStateDelta    EventType = "STATE_DELTA"
)

The AG-UI event vocabulary this server emits (docs/ag-ui-design.md emission map). Stage 1 ships the lifecycle, text-message triad, tool-call quartet, and state families; activity/reasoning/raw/custom families are deferred.

type ExposedWorkload

type ExposedWorkload struct {
	// WorkloadName is the mast workload backing this endpoint; the backend
	// resolves it to a session and drives the turn.
	WorkloadName string

	// EndpointPath is the HTTP path the workload is served at, e.g.
	// "/agui/triage". Must start with "/". Each exposed workload owns a
	// distinct path.
	EndpointPath string

	// Description is surfaced in the discovery descriptor.
	Description string

	// InputSchema is an optional JSON-Schema-shaped hint surfaced in the
	// discovery descriptor so a client can render an input form.
	InputSchema map[string]any

	// Scopes are required to invoke this workload. Empty means the endpoint
	// needs authentication only (a valid token, no specific scope) when a
	// validator is configured, or open access when it is not.
	Scopes []string
}

ExposedWorkload is one workload's AG-UI exposure, projected by the daemon from the bundle's agui: section (this package does not import pkg/workload).

type Interrupt

type Interrupt struct {
	ID             string          `json:"id"`
	Message        string          `json:"message,omitempty"`
	ResponseSchema json.RawMessage `json:"responseSchema,omitempty"`
	ExpiresAt      *int64          `json:"expiresAt,omitempty"`
}

Interrupt describes one open interrupt a run paused on. ID is the resume correlation key (the client echoes it in a ResumeEntry.InterruptID); Message is the human-readable prompt; ResponseSchema, when present, is the JSON schema the answer payload should conform to (a client can render a form from it). ExpiresAt (epoch ms) is modeled for spec completeness but unset — mast HITL interrupts carry no wall-clock expiry today.

type Message

type Message struct {
	ID      string `json:"id,omitempty"`
	Role    string `json:"role"`
	Content string `json:"content,omitempty"`
	Name    string `json:"name,omitempty"`
}

Message is one conversational message in a RunAgentInput. Role is the AG-UI role vocabulary ("user", "assistant", "system", "tool"); Content is the text body. Unmodeled per-role fields (tool calls on assistant messages, toolCallId on tool messages) are not consumed by the Stage 1 server.

type ResumeEntry

type ResumeEntry struct {
	InterruptID string          `json:"interruptId"`
	Status      ResumeStatus    `json:"status"`
	Payload     json.RawMessage `json:"payload,omitempty"`
}

ResumeEntry answers one prior interrupt when a client resumes a run. The InterruptID must match an interrupt the server reported in a prior RunFinished{outcome: interrupt}; Payload is the client's answer.

type ResumeStatus

type ResumeStatus string

ResumeStatus is the disposition a client reports when answering an interrupt (docs/ag-ui-design.md Resume): "resolved" (the human answered) or "cancelled" (the human declined). The vocabulary follows the design doc; the shipped Stage 1 placeholder used "accepted"/"rejected", reconciled here now that the lifecycle is live. mast forwards the entry's Payload verbatim as the interrupt answer regardless of status (the runtime resume channel carries the answer value, not a separate disposition — see cmd/mast buildResumeMessage); when a cancelled entry carries no payload, mast synthesizes a minimal {"status":"cancelled"} answer so a workload can branch on a decline.

const (
	ResumeStatusResolved  ResumeStatus = "resolved"
	ResumeStatusCancelled ResumeStatus = "cancelled"
)

type RunAgentInput

type RunAgentInput struct {
	ThreadID       string          `json:"threadId"`
	RunID          string          `json:"runId"`
	ParentRunID    *string         `json:"parentRunId,omitempty"`
	State          json.RawMessage `json:"state,omitempty"`
	Messages       []Message       `json:"messages,omitempty"`
	Tools          []Tool          `json:"tools,omitempty"`
	Context        []Context       `json:"context,omitempty"`
	ForwardedProps json.RawMessage `json:"forwardedProps,omitempty"`
	Resume         []ResumeEntry   `json:"resume,omitempty"`
}

RunAgentInput is the request body a client POSTs to a workload's AG-UI endpoint to drive one turn. Field names follow the AG-UI spec JSON (camelCase). State/ForwardedProps ride along as raw JSON — the server echoes State back as the opening StateSnapshot and does not otherwise interpret it. Resume carries the client's answers when continuing an interrupted run (the HITL lifecycle): a non-empty Resume turns this request into a resume rather than a fresh user turn. Tools are parsed but unused (client-tool acceptance is a follow-on stage); modeled so the input contract stays complete and forward-compatible.

type RunError

type RunError struct {
	Message string       `json:"message"`
	Code    RunErrorCode `json:"code,omitempty"`
	// contains filtered or unexported fields
}

RunError is the terminal event for a run that did not complete normally: aborted or an internal fault. (A HITL pause is NOT a RunError — it is a terminal RunFinished carrying outcome.type == "interrupt"; see RunOutcome.) Message is a short human-readable summary; Code is the machine-readable disposition.

func NewRunError

func NewRunError(msg string, code RunErrorCode) RunError

type RunErrorCode

type RunErrorCode string

RunErrorCode is the machine-readable code on a RUN_ERROR event. The vocabulary is deliberately small: aborted (operator/client cancellation) and internal (any server-side fault, with no detail leaked to the client). A HITL pause is NOT an error — it is a terminal RunFinished{outcome: interrupt} (see RunOutcome), so it carries no error code.

const (
	RunErrorAborted  RunErrorCode = "aborted"
	RunErrorInternal RunErrorCode = "internal"
)

type RunFinished

type RunFinished struct {
	ThreadID string          `json:"threadId"`
	RunID    string          `json:"runId"`
	Outcome  *RunOutcome     `json:"outcome,omitempty"`
	Result   json.RawMessage `json:"result,omitempty"`
	// contains filtered or unexported fields
}

RunFinished is the terminal event for a run that reached a stopping point, carrying a RunOutcome that says which. A success outcome carries Result (the workload's final answer, also present as the closing TextMessage triad; the duplication is inherent under message-granular streaming). An interrupt outcome carries the open Interrupts the client must answer to resume — a HITL pause is a clean run stop, not a RunError (docs/ag-ui-design.md).

func NewRunFinished

func NewRunFinished(threadID, runID string, result json.RawMessage) RunFinished

NewRunFinished builds the terminal event for a run that completed successfully, carrying a success outcome and the final answer.

func NewRunFinishedInterrupt

func NewRunFinishedInterrupt(threadID, runID string, interrupts []Interrupt) RunFinished

NewRunFinishedInterrupt builds the terminal event for a run that paused for human input, carrying an interrupt outcome and the open interrupts the client must answer to resume. It carries no Result — the run has not produced a final answer yet.

type RunInput

type RunInput struct {
	// WorkloadName is the target workload (the endpoint the request hit).
	WorkloadName string

	// ThreadID / RunID are the client-supplied correlation ids; the daemon
	// derives the (namespaced) mast session id from them and never trusts a
	// client-supplied raw session id.
	ThreadID string
	RunID    string

	// ParentRunID is the run this one continues, when the client sets it. A
	// resume arrives as a NEW run (new RunID) but must reach the session the
	// parent run parked; under a run-keyed session model the daemon keys the
	// resume's session on ParentRunID. Empty when absent.
	ParentRunID string

	// Text is the turn's user input (the last user message's content).
	Text string

	// State is the client-supplied shared-state document, echoed back as the
	// opening StateSnapshot; nil when absent.
	State json.RawMessage

	// Resume, when non-empty, makes this a resume of an interrupted run: each
	// entry answers one open interrupt (by id) rather than driving a fresh user
	// turn. The daemon translates the answers into the runtime's resume input.
	Resume []ResumeEntry
}

RunInput is a RunAgentInput projected onto the runtime seam. The server extracts it from the decoded request; the daemon converts Text into a user turn and runs it through the turn chokepoint. State rides along as raw JSON for the backend to echo as the opening StateSnapshot.

type RunMetric

type RunMetric interface {
	AGUIRun(workload, outcome string)
}

RunMetric records AG-UI run outcomes. The daemon backs it with observability.Registry.AGUIRun; nil disables. The outcome is one of a fixed vocabulary (see observability.Prime): success, interrupted, error, aborted, rejected.

type RunOutcome

type RunOutcome struct {
	Type       RunOutcomeType `json:"type"`
	Interrupts []Interrupt    `json:"interrupts,omitempty"`
}

RunOutcome is the structured disposition on a RunFinished. Type is "success" (the run completed; the answer is in RunFinished.Result) or "interrupt" (the run paused for human input; Interrupts lists what to answer). The interrupt lifecycle maps directly onto mast's durable pause/resume: each Interrupt.ID is a mast pending-interrupt id, and a client resumes by POSTing a new run whose RunAgentInput.Resume answers it.

type RunOutcomeType

type RunOutcomeType string

RunOutcomeType discriminates a RunFinished's disposition.

const (
	RunOutcomeSuccess   RunOutcomeType = "success"
	RunOutcomeInterrupt RunOutcomeType = "interrupt"
)

type RunResult

type RunResult struct {
	Text        string
	Aborted     bool
	Interrupted bool
	Interrupts  []Interrupt
}

RunResult is the terminal outcome of a run, returned by the Backend after the turn completes. Exactly one disposition holds: Aborted (operator/client cancellation), Interrupted (the turn paused for human input), or neither (success, with Text the final answer surfaced in RunFinished.result). When Interrupted, Interrupts lists the open interrupts the client must answer to resume; the server emits them in the terminal RunFinished{outcome: interrupt}.

type RunStarted

type RunStarted struct {
	ThreadID string `json:"threadId"`
	RunID    string `json:"runId"`
	// contains filtered or unexported fields
}

RunStarted opens a run's event stream (docs/ag-ui-design.md: session start).

func NewRunStarted

func NewRunStarted(threadID, runID string) RunStarted

type Server

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

Server is the AG-UI HTTP server. Construct with New; serve with ListenAndServe or Serve.

func New

func New(cfg Config) (*Server, error)

New constructs a Server. It does not start listening.

func (*Server) Close

func (s *Server) Close() error

Close stops the server immediately.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler exposes the server's routes for mounting on an external mux or for tests (httptest.NewServer).

func (*Server) ListenAndServe

func (s *Server) ListenAndServe() error

ListenAndServe blocks serving requests; returns http.ErrServerClosed on graceful shutdown.

func (*Server) Serve

func (s *Server) Serve(ln net.Listener) error

Serve serves on an already-bound listener; returns http.ErrServerClosed on graceful shutdown. The daemon binds eagerly so a bad bind address fails startup rather than a background goroutine (mirrors buildAttach / a2a).

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

Shutdown attempts a graceful stop.

type StateDelta

type StateDelta struct {
	Delta json.RawMessage `json:"delta"`
	// contains filtered or unexported fields
}

type StateSnapshot

type StateSnapshot struct {
	Snapshot json.RawMessage `json:"snapshot"`
	// contains filtered or unexported fields
}

StateSnapshot carries the full shared-state document; StateDelta carries an RFC-6902 JSON Patch against the last snapshot. Stage 1 emits one opening StateSnapshot (echoing the input State); per-key StateDelta emission is deferred (it needs a state-projection allowlist), but the type ships so the state vocabulary is complete.

func NewStateSnapshot

func NewStateSnapshot(snapshot json.RawMessage) StateSnapshot

type StepFinished

type StepFinished struct {
	StepName string `json:"stepName"`
	// contains filtered or unexported fields
}

type StepStarted

type StepStarted struct {
	StepName string `json:"stepName"`
	// contains filtered or unexported fields
}

StepStarted / StepFinished bracket a named step within a run (reserved for multi-step workloads; modeled so consumers can rely on the vocabulary).

type TextMessageContent

type TextMessageContent struct {
	MessageID string `json:"messageId"`
	Delta     string `json:"delta"`
	// contains filtered or unexported fields
}

func NewTextMessageContent

func NewTextMessageContent(messageID, delta string) TextMessageContent

type TextMessageEnd

type TextMessageEnd struct {
	MessageID string `json:"messageId"`
	// contains filtered or unexported fields
}

func NewTextMessageEnd

func NewTextMessageEnd(messageID string) TextMessageEnd

type TextMessageStart

type TextMessageStart struct {
	MessageID string `json:"messageId"`
	Role      string `json:"role"`
	// contains filtered or unexported fields
}

TextMessageStart / TextMessageContent / TextMessageEnd stream one assistant message. mast runs StreamingModeNone (message-granular), so a message is emitted as start + a single content frame carrying the whole text + end; the triad shape keeps token-level streaming a forward-compatible upgrade. Role is present only on the start frame ("assistant").

func NewTextMessageStart

func NewTextMessageStart(messageID string) TextMessageStart

type Tool

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

Tool is a client-declared tool offered to the agent for this run. Parsed but ignored (accepting client tools is a follow-on); modeled so the input contract is complete.

type ToolCallArgs

type ToolCallArgs struct {
	ToolCallID string `json:"toolCallId"`
	Delta      string `json:"delta"`
	// contains filtered or unexported fields
}

func NewToolCallArgs

func NewToolCallArgs(toolCallID, delta string) ToolCallArgs

type ToolCallEnd

type ToolCallEnd struct {
	ToolCallID string `json:"toolCallId"`
	// contains filtered or unexported fields
}

func NewToolCallEnd

func NewToolCallEnd(toolCallID string) ToolCallEnd

type ToolCallResult

type ToolCallResult struct {
	ToolCallID string `json:"toolCallId"`
	MessageID  string `json:"messageId,omitempty"`
	Content    string `json:"content"`
	// contains filtered or unexported fields
}

func NewToolCallResult

func NewToolCallResult(toolCallID, content string) ToolCallResult

type ToolCallStart

type ToolCallStart struct {
	ToolCallID      string `json:"toolCallId"`
	ToolCallName    string `json:"toolCallName"`
	ParentMessageID string `json:"parentMessageId,omitempty"`
	// contains filtered or unexported fields
}

ToolCallStart / ToolCallArgs / ToolCallEnd stream one tool invocation, and ToolCallResult reports its outcome once the runtime has run it. Args carries the JSON-encoded call arguments; Content carries the JSON-encoded response. ParentMessageID links the call to the assistant message that issued it.

func NewToolCallStart

func NewToolCallStart(toolCallID, name, parentMessageID string) ToolCallStart

Jump to

Keyboard shortcuts

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