Documentation
¶
Overview ¶
Package agui provides utilities for integrating gains with the AG-UI protocol.
AG-UI (Agent-User Interface) is an open, lightweight, event-based protocol that standardizes how AI agents connect to user-facing applications. This package provides mapping utilities to convert gains events to AG-UI events, enabling easy integration with AG-UI-compatible frontends.
Overview ¶
This package provides:
- Mapper: Stateful event converter that handles AG-UI's Start-Content-End pattern
- Message conversion utilities: ToGainsMessages, FromGainsMessages
- State management: DecodeState, MustDecodeState for typed frontend state access
The package does NOT provide HTTP handlers or transport implementations. Users are responsible for implementing their own server using the AG-UI SDK's SSE writer or their preferred transport mechanism.
Usage ¶
Create a Mapper for each run and use it to convert gains events:
// Create mapper for this run
mapper := agui.NewMapper(threadID, runID)
// Emit run started
writeEvent(mapper.RunStarted())
// Run agent and map events
for event := range myAgent.RunStream(ctx, messages) {
aguiEvent := mapper.MapEvent(event)
if aguiEvent != nil {
writeEvent(aguiEvent)
}
}
// Emit run finished
writeEvent(mapper.RunFinished())
Event Mapping ¶
The Mapper tracks state to properly emit AG-UI's Start-Content-End sequences:
- event.MessageDelta → TEXT_MESSAGE_START (on first delta), TEXT_MESSAGE_CONTENT
- event.StepEnd → TEXT_MESSAGE_END (if message active), STEP_FINISHED
- event.ToolCallStart → TOOL_CALL_START, TOOL_CALL_ARGS
- event.ToolCallResult → TOOL_CALL_END, TOOL_CALL_RESULT
Message Conversion ¶
Use ToGainsMessages to convert AG-UI messages to gains messages for input:
messages := agui.ToGainsMessages(aguiMessages) result := agent.Run(ctx, messages)
Use FromGainsMessages to convert gains messages to AG-UI format for snapshots:
snapshot := events.NewMessagesSnapshotEvent(agui.FromGainsMessages(history))
Shared State ¶
AG-UI supports bidirectional state synchronization between agents and frontends. The frontend can send state with each run via RunAgentInput.State, and agents can emit state updates via STATE_SNAPSHOT and STATE_DELTA events.
Reading frontend state:
type MyState struct {
Progress int `json:"progress"`
Items []string `json:"items"`
}
prepared, _ := input.Prepare()
state, err := agui.DecodeState[MyState](prepared)
// or: state := agui.MustDecodeState[MyState](prepared)
Emitting state to frontend:
// Full state snapshot
writeEvent(mapper.StateSnapshot(map[string]any{
"progress": 50,
"items": []string{"a", "b"},
}))
// Incremental delta (JSON Patch RFC 6902)
writeEvent(mapper.StateDelta(
event.Replace("/progress", 75),
event.Add("/items/-", "c"),
))
Or via gains events for integration with RunStream:
events <- event.NewStateSnapshot(state)
events <- event.NewStateDelta(
event.Replace("/progress", 100),
)
Thread Safety ¶
The Mapper is NOT safe for concurrent use. Each goroutine should have its own Mapper instance. Message conversion functions are stateless and safe for concurrent use.
Index ¶
- Constants
- Variables
- func DecodeState[T any](input *PreparedInput) (T, error)
- func DecodeWorkflowState[T any](input *PreparedWorkflowInput) (T, error)
- func FromGainsMessage(msg ai.Message, index int) events.Message
- func FromGainsMessages(msgs []ai.Message) []events.Message
- func HandleApproval(broker *agent.ApprovalBroker, input *ApprovalInput) error
- func HandleApprovalJSON(broker *agent.ApprovalBroker, data []byte) error
- func HandleUserInput(broker *agent.UserInputBroker, input *UserInputInput) error
- func HandleUserInputJSON(broker *agent.UserInputBroker, data []byte) error
- func InitializeState[T any](input *PreparedInput) (*T, error)
- func MergeState[T any](state *T, input *PreparedInput) error
- func MustDecodeState[T any](input *PreparedInput) T
- func MustDecodeWorkflowState[T any](input *PreparedWorkflowInput) T
- func MustInitializeState[T any](input *PreparedInput) *T
- func MustMergeState[T any](state *T, input *PreparedInput)
- func ToGainsMessage(msg events.Message) ai.Message
- func ToGainsMessages(msgs []events.Message) []ai.Message
- func ToGainsTools(tools []Tool) []ai.Tool
- func ToolNames(tools []Tool) []string
- type ApprovalInput
- type Mapper
- func (m *Mapper) ActivityDelta(activityID string, activityType event.ActivityType, patches []event.JSONPatch) events.Event
- func (m *Mapper) ActivitySnapshot(activityID string, activityType event.ActivityType, content any) events.Event
- func (m *Mapper) MapEvent(e event.Event) events.Event
- func (m *Mapper) MapStream(input <-chan event.Event) <-chan events.Event
- func (m *Mapper) MessagesSnapshot(messages []ai.Message) events.Event
- func (m *Mapper) RunDepth() int
- func (m *Mapper) RunError(err error) events.Event
- func (m *Mapper) RunFinished() events.Event
- func (m *Mapper) RunID() string
- func (m *Mapper) RunStarted() events.Event
- func (m *Mapper) StateDelta(patches ...event.JSONPatch) events.Event
- func (m *Mapper) StateSnapshot(state any) events.Event
- func (m *Mapper) ThreadID() string
- type MapperOption
- type PreparedInput
- type PreparedWorkflowInput
- type RunAgentInput
- type RunWorkflowInput
- type Tool
- type UserInputInput
Constants ¶
const ( // CustomEventRouteSelected is emitted when a route is chosen in a Router step. // Value contains: stepName (string), routeName (string) CustomEventRouteSelected = "gains.route_selected" // CustomEventLoopIteration is emitted at the start of each loop iteration. // Value contains: stepName (string), iteration (int) CustomEventLoopIteration = "gains.loop_iteration" )
Custom event names for gains-specific workflow events. These are emitted as AG-UI CUSTOM events with a name and value.
const ( RoleUser = "user" RoleAssistant = "assistant" RoleSystem = "system" RoleTool = "tool" )
Role constants matching AG-UI protocol.
Variables ¶
var ErrNoMessages = errors.New("no messages provided")
ErrNoMessages is returned when the input contains no messages.
var ErrNoWorkflowName = errors.New("no workflow name provided")
ErrNoWorkflowName is returned when the workflow name is empty.
Functions ¶
func DecodeState ¶
func DecodeState[T any](input *PreparedInput) (T, error)
DecodeState decodes the raw state into a typed struct. Returns the zero value of T if State is nil.
func DecodeWorkflowState ¶ added in v0.2.9
func DecodeWorkflowState[T any](input *PreparedWorkflowInput) (T, error)
DecodeWorkflowState decodes the raw state into a typed struct. Returns the zero value of T if State is nil.
func FromGainsMessage ¶
FromGainsMessage converts a single gains message to an AG-UI message. The index is used to generate a message ID if needed.
func FromGainsMessages ¶
FromGainsMessages converts gains messages to AG-UI messages.
func HandleApproval ¶ added in v0.2.9
func HandleApproval(broker *agent.ApprovalBroker, input *ApprovalInput) error
HandleApproval processes an approval input and sends it to the broker. This is a convenience function for AG-UI server handlers.
func HandleApprovalJSON ¶ added in v0.2.9
func HandleApprovalJSON(broker *agent.ApprovalBroker, data []byte) error
HandleApprovalJSON processes a JSON-encoded approval input.
func HandleUserInput ¶ added in v0.2.9
func HandleUserInput(broker *agent.UserInputBroker, input *UserInputInput) error
HandleUserInput processes a user input and sends it to the broker. This is a convenience function for AG-UI server handlers.
func HandleUserInputJSON ¶ added in v0.2.9
func HandleUserInputJSON(broker *agent.UserInputBroker, data []byte) error
HandleUserInputJSON processes a JSON-encoded user input response.
func InitializeState ¶ added in v0.2.9
func InitializeState[T any](input *PreparedInput) (*T, error)
InitializeState creates a new state struct initialized from frontend state. This is the recommended way to create workflow state from AG-UI input:
input, err := runAgentInput.Prepare() state, err := agui.InitializeState[MyState](input) result, err := workflow.Run(ctx, state, opts...)
func MergeState ¶ added in v0.2.9
func MergeState[T any](state *T, input *PreparedInput) error
MergeState merges frontend state into an existing state struct. Fields from the frontend state overwrite corresponding fields in state. This is useful when you have a pre-populated state with defaults:
state := &MyState{DefaultField: "value"}
agui.MergeState(state, input) // Overwrites with frontend values
func MustDecodeState ¶
func MustDecodeState[T any](input *PreparedInput) T
MustDecodeState is like DecodeState but panics on error.
func MustDecodeWorkflowState ¶ added in v0.2.9
func MustDecodeWorkflowState[T any](input *PreparedWorkflowInput) T
MustDecodeWorkflowState is like DecodeWorkflowState but panics on error.
func MustInitializeState ¶ added in v0.2.9
func MustInitializeState[T any](input *PreparedInput) *T
MustInitializeState is like InitializeState but panics on error.
func MustMergeState ¶ added in v0.2.9
func MustMergeState[T any](state *T, input *PreparedInput)
MustMergeState is like MergeState but panics on error.
func ToGainsMessage ¶
ToGainsMessage converts a single AG-UI message to a gains message.
func ToGainsMessages ¶
ToGainsMessages converts AG-UI messages to gains messages.
func ToGainsTools ¶
ToGainsTools converts a slice of AG-UI tools to gains tools.
Types ¶
type ApprovalInput ¶ added in v0.2.9
type ApprovalInput struct {
ToolCallID string `json:"toolCallId"`
Approved bool `json:"approved"`
Reason string `json:"reason,omitempty"`
}
ApprovalInput represents an approval decision from the AG-UI frontend. This corresponds to a user action on a tool_approval activity.
func ParseApprovalInput ¶ added in v0.2.9
func ParseApprovalInput(data []byte) (*ApprovalInput, error)
ParseApprovalInput parses an approval decision from JSON.
func (*ApprovalInput) ToDecision ¶ added in v0.2.9
func (a *ApprovalInput) ToDecision() agent.ApprovalDecision
ToDecision converts an ApprovalInput to an agent.ApprovalDecision.
type Mapper ¶
type Mapper struct {
// contains filtered or unexported fields
}
Mapper converts gains events to AG-UI events. With the unified event system, this is now a true 1:1 mapping - each gains event maps to exactly one AG-UI event.
The mapper tracks run depth to handle nested RunStart/RunEnd events (e.g., from workflows containing agents or sub-agents). Only the outermost run lifecycle events are mapped to AG-UI RUN_STARTED/RUN_FINISHED.
Create a new Mapper for each run using NewMapper. The Mapper is not safe for concurrent use - each goroutine should have its own Mapper.
func NewMapper ¶
func NewMapper(threadID, runID string, opts ...MapperOption) *Mapper
NewMapper creates a new Mapper for a single run. The threadID and runID are used in lifecycle events (RUN_STARTED, RUN_FINISHED). Use WithInitialState to emit an initial STATE_SNAPSHOT after RUN_STARTED.
func (*Mapper) ActivityDelta ¶ added in v0.2.9
func (m *Mapper) ActivityDelta(activityID string, activityType event.ActivityType, patches []event.JSONPatch) events.Event
ActivityDelta returns an ACTIVITY_DELTA event.
func (*Mapper) ActivitySnapshot ¶ added in v0.2.9
func (m *Mapper) ActivitySnapshot(activityID string, activityType event.ActivityType, content any) events.Event
ActivitySnapshot returns an ACTIVITY_SNAPSHOT event.
func (*Mapper) MapEvent ¶
MapEvent converts a unified gains event to an AG-UI event. This is a true 1:1 mapping - each gains event maps to exactly one AG-UI event. Returns nil for events that have no AG-UI equivalent.
For nested runs (e.g., workflows containing agents), only the outermost RunStart/RunEnd events are mapped to AG-UI lifecycle events.
func (*Mapper) MapStream ¶
MapStream wraps a gains event channel and yields AG-UI events. Events that have no AG-UI equivalent (returning nil from MapEvent) are filtered out. The returned channel closes when the input channel closes.
If the mapper was created with WithInitialState, a STATE_SNAPSHOT event is automatically emitted after the first RUN_STARTED event.
func (*Mapper) MessagesSnapshot ¶ added in v0.2.9
MessagesSnapshot returns a MESSAGES_SNAPSHOT event with the given messages.
func (*Mapper) RunDepth ¶ added in v0.2.9
RunDepth returns the current nesting depth of runs. Returns 0 when no runs are active, 1 during a top-level run, etc.
func (*Mapper) RunFinished ¶
RunFinished returns a RUN_FINISHED event.
func (*Mapper) RunStarted ¶
RunStarted returns a RUN_STARTED event.
func (*Mapper) StateDelta ¶
StateDelta returns a STATE_DELTA event with the given JSON Patch operations.
func (*Mapper) StateSnapshot ¶
StateSnapshot returns a STATE_SNAPSHOT event with the given state.
type MapperOption ¶ added in v0.2.9
type MapperOption func(*Mapper)
MapperOption configures a Mapper.
func WithInitialState ¶ added in v0.2.9
func WithInitialState(state any) MapperOption
WithInitialState configures the mapper to emit a STATE_SNAPSHOT event with the given state immediately after RUN_STARTED. This ensures the frontend has the initial state for shared state synchronization.
type PreparedInput ¶
type PreparedInput struct {
ThreadID string
RunID string
Messages []ai.Message
Tools []Tool // Parsed frontend tools
ToolNames []string // Tool names for cleanup tracking
State any // Raw state from frontend
}
PreparedInput contains validated and converted input ready for agent execution.
func (*PreparedInput) GainsTools ¶
func (p *PreparedInput) GainsTools() []ai.Tool
GainsTools converts the parsed frontend tools to gains tools. Returns nil if no tools were parsed.
type PreparedWorkflowInput ¶ added in v0.2.9
type PreparedWorkflowInput struct {
ThreadID string
RunID string
WorkflowName string
State any // Raw state for workflow initialization
}
PreparedWorkflowInput contains validated workflow input ready for execution.
type RunAgentInput ¶
type RunAgentInput struct {
ThreadID string `json:"thread_id"`
RunID string `json:"run_id"`
Messages []events.Message `json:"messages"`
Tools []any `json:"tools,omitempty"` // Frontend-provided tools
Context []any `json:"context,omitempty"` // Context items
State any `json:"state,omitempty"` // State
ForwardedProps any `json:"forwarded_props,omitempty"` // Forwarded props
}
RunAgentInput represents the AG-UI protocol request for running an agent. This mirrors the AG-UI protocol specification and is transport-agnostic.
func (*RunAgentInput) Prepare ¶
func (r *RunAgentInput) Prepare() (*PreparedInput, error)
Prepare validates the input and converts it to gains types. Returns ErrNoMessages if Messages is empty. Returns an error if tool parsing fails.
type RunWorkflowInput ¶ added in v0.2.9
type RunWorkflowInput struct {
ThreadID string `json:"thread_id"`
RunID string `json:"run_id"`
WorkflowName string `json:"workflow_name"` // Name of workflow to execute
State any `json:"state,omitempty"` // Initial workflow state
ForwardedProps any `json:"forwarded_props,omitempty"`
}
RunWorkflowInput represents the AG-UI protocol request for running a workflow. This is designed for dispatching workflows by name with typed state.
func (*RunWorkflowInput) Prepare ¶ added in v0.2.9
func (r *RunWorkflowInput) Prepare() (*PreparedWorkflowInput, error)
Prepare validates the workflow input. Returns ErrNoWorkflowName if WorkflowName is empty.
type Tool ¶
type Tool struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters json.RawMessage `json:"parameters,omitempty"`
}
Tool represents a tool definition from the AG-UI protocol. Frontend applications send these to define capabilities available to agents.
func ParseTools ¶
ParseTools parses a slice of any (from JSON unmarshaling) into Tool structs. This handles the Tools field from RunAgentInput which is []any.
func (Tool) ToGainsTool ¶
ToGainsTool converts an AG-UI tool to a gains Tool.
type UserInputInput ¶ added in v0.2.9
type UserInputInput struct {
RequestID string `json:"requestId"`
Value string `json:"value,omitempty"`
Confirmed bool `json:"confirmed,omitempty"`
Cancelled bool `json:"cancelled,omitempty"`
}
UserInputInput represents a user input response from the AG-UI frontend. This corresponds to a user action on a user_input activity.
func ParseUserInputInput ¶ added in v0.2.9
func ParseUserInputInput(data []byte) (*UserInputInput, error)
ParseUserInputInput parses a user input response from JSON.
func (*UserInputInput) ToResponse ¶ added in v0.2.9
func (u *UserInputInput) ToResponse() agent.UserInputResponse
ToResponse converts a UserInputInput to an agent.UserInputResponse.