agui

package
v0.641.3 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 36 Imported by: 0

Documentation

Overview

Package agui implements the AG-UI protocol (Agent-User Interaction Protocol, https://docs.ag-ui.com) as an isolated side-car adapter on top of Pando.

AG-UI is the wire contract CopilotKit and other Generative-UI frontends speak to any agent backend. Making Pando speak it is what lets a React application drive a Pando agent with `<CopilotKit agent="pando">` and zero Pando-specific frontend code.

Architectural invariants

This package is deliberately a leaf adapter. It must never become a second implementation of the agent, and it must never disturb the surfaces that already exist (TUI, Web-UI, ACP). The following invariants are load-bearing:

  • I1: no change to agent.NewAgent / agent.Run signatures. This package builds its OWN agent.Service instances through the already-exported constructors, exactly like internal/app/app.go does.
  • I2: app.CoderAgent is never read or mutated here. TUI/Web-UI/ACP and the api BackgroundSessionManager keep using it untouched.
  • I3: no AG-UI prompt ever reaches a desktop user. The runtime creates its own permission.Service and userinput.Service, so approvals and questions raised by a web run stay inside this adapter.
  • I4: no new event types in agent.AgentEvent. Translation to AG-UI events is one-way and lives in translate.go.
  • I5: off by default and removable. Deleting this package plus the ~90 lines of wiring in config/app/api restores the previous tree.
  • I6: no *app.App import (that would also be an import cycle); dependencies arrive through the narrow Deps struct.
  • I7: its own route namespace, its own auth/CORS policy, optionally its own listener.

Implementation status

P0 (protocol layer), P1 (runtime, agent pool, thread map, translation, endpoint), P2 (shared state), P3 (frontend tools) and P4 (human in the loop) are implemented:

  • P2: STATE_SNAPSHOT after every RUN_STARTED and STATE_DELTA (RFC-6902) for todos, token usage and touched files. See state.go.

  • P3: RunAgentInput.Tools become blocking tools.BaseTool proxies; a call suspends the run with RUN_FINISHED{outcome:"interrupt"} and is resolved by the tool message of the next request on the thread. Runs are therefore detached from their HTTP request; see run.go and frontend_tool.go.

  • P4: permission prompts reach the client as a synthetic pando_permission_request tool call, and AskUserQuestion is substituted by a tool that waits on the client instead of on a local overlay. Both fail closed (deny / cancel) when nobody answers. Gated by Config.HumanInTheLoop; with it off the pre-P4 policy applies. See hitl.go.

  • P5: durable thread->session mapping in the adapter-owned agui_threads table (threads.go), hardened /info discovery, and the dedicated listener of invariant I7 (listener.go), reachable through `pando serve --agui-port` or the standalone `pando agui-serve` process.

  • P6: the client half, outside this package — the `@pando-ai/sdk/agui` subpath export and the Next.js application under examples/copilotkit. No Go code was involved, which is the point: the protocol is the contract.

  • P7: mesnada sub-agents are projected into the shared-state document as StateDoc.SubAgents (subagents.go), derived from the mesnada_* tool traffic the adapter already observes — the orchestrator is never consulted and no agent event type was added.

Tool calls that never reach the event stream

agent.processEvent only publishes AgentEventTypeToolCall for providers that stream tool use incrementally (EventToolUseStart/Delta/Stop). A provider that reports its tool calls in one final EventComplete leaves the assistant message correct while the event stream stays silent about them. Every other surface renders from the database and never notices; this adapter has only the stream, so it must reconstruct what the stream omits: a suspending call carries its own name and arguments (suspension.call) so the handler can emit START/ARGS/END before the interrupt, and a tool result for a call that was never opened opens it first (translate.go), because a bare TOOL_CALL_END is rejected by AG-UI clients as a protocol error that aborts the whole run.

Tool results are not JSON

tools.NewStructuredResponse renders TOON when it can, TOML next and indented JSON only as a last resort, and it chooses per value. Any code here that reads a tool result structurally must go through decodeToolResult (subagents.go); json.Unmarshal alone silently sees nothing, which is what kept the sub-agent board empty until 2026-07-29.

The state document belongs to the thread

StateDoc is per thread and lives in the Runtime's stateStore, not in the run: a conversation's todos, touched files and sub-agents must survive its turns. A per-run document made them vanish on every new message. The store is memory only — the durable half is the thread->session binding in threads.go — and it evicts the least recently used threads past maxStateThreads.

Deliberately not implemented

CopilotKit's own runtime protocol (GraphQL) is not served by Pando. The remaining half of P7 was "skip the Node hop" by embedding that runtime; it is declined on purpose. AG-UI is the protocol every agent backend implements and CopilotKit's runtime already translates it, so reimplementing that runtime in Go would buy one process hop at the cost of tracking a second, faster-moving protocol — and it would move the API token into the browser, which is the one place it must not be.

The AG-UI spec revision targeted here is the one documented at https://docs.ag-ui.com as of 2026-07-28.

Index

Constants

View Source
const (
	// OutcomeSuccess marks a run that completed on its own.
	OutcomeSuccess = "success"
	// OutcomeInterrupt marks a run that stopped waiting for the client (a
	// frontend tool result, a human decision). The agent-side run may still be
	// alive; the client resumes it with the next request on the same thread.
	OutcomeInterrupt = "interrupt"
)

Run outcomes reported by RunFinishedEvent.

View Source
const (
	RoleDeveloper = "developer"
	RoleSystem    = "system"
	RoleAssistant = "assistant"
	RoleUser      = "user"
	RoleTool      = "tool"
	RoleActivity  = "activity"
	RoleReasoning = "reasoning"
)

Message roles defined by AG-UI.

View Source
const (
	ContentText     = "text"
	ContentImage    = "image"
	ContentAudio    = "audio"
	ContentVideo    = "video"
	ContentDocument = "document"
)

Multimodal input content kinds.

View Source
const (
	FileActionRead    = "read"
	FileActionWrite   = "write"
	FileActionEdit    = "edit"
	FileActionPatched = "patch"
)

File actions reported in StateDoc.Files.

Variables

View Source
var ErrInvalidInput = errors.New("agui: invalid RunAgentInput")

ErrInvalidInput is returned by DecodeRunAgentInput when the payload does not satisfy the protocol's minimum requirements.

View Source
var ErrStreamingUnsupported = errors.New("agui: streaming unsupported by the response writer")

ErrStreamingUnsupported is returned when the ResponseWriter cannot flush, in which case SSE cannot be served at all.

Functions

This section is empty.

Types

type ActivitySnapshotEvent

type ActivitySnapshotEvent struct {
	BaseEvent
	MessageID    string `json:"messageId"`
	ActivityType string `json:"activityType"`
	Content      any    `json:"content"`
	Replace      bool   `json:"replace,omitempty"`
}

func NewActivitySnapshot

func NewActivitySnapshot(messageID, activityType string, content any) ActivitySnapshotEvent

type AgentDescriptor

type AgentDescriptor struct {
	Name        string           `json:"name"`
	Description string           `json:"description,omitempty"`
	URL         string           `json:"url"`
	Model       *ModelDescriptor `json:"model,omitempty"`
}

AgentDescriptor is one entry of the /info response.

type BaseEvent

type BaseEvent struct {
	Type      EventType `json:"type"`
	Timestamp int64     `json:"timestamp,omitempty"`
	RawEvent  any       `json:"rawEvent,omitempty"`
}

BaseEvent carries the fields every AG-UI event shares.

func (BaseEvent) EventType

func (b BaseEvent) EventType() EventType

EventType implements Event.

type Capabilities

type Capabilities struct {
	// FrontendTools reports whether RunAgentInput.tools are proxied.
	FrontendTools bool `json:"frontendTools"`
	// HumanInTheLoop reports whether permission prompts and agent questions
	// reach the client as tool calls.
	HumanInTheLoop bool `json:"humanInTheLoop"`
	// SharedState reports STATE_SNAPSHOT / STATE_DELTA support.
	SharedState bool `json:"sharedState"`
	// Interrupts reports RUN_FINISHED{outcome:"interrupt"} and resumption.
	Interrupts bool `json:"interrupts"`
}

Capabilities advertises the optional halves of the protocol this adapter implements, so a client can tell "not supported" from "nothing happened" without probing.

type Config

type Config struct {
	Path string
	// Port serves the adapter on its own listener when > 0. Zero means the
	// adapter is mounted on the API server's mux.
	Port           int
	Agents         []config.AgentName
	AllowedOrigins []string
	RequireToken   bool
	FrontendTools  bool
	AgentPoolSize  int
	AgentPoolTTL   time.Duration
	AutoApprove    bool
	// HumanInTheLoop surfaces permission prompts and questions to the AG-UI
	// client. With it off (and AutoApprove off) a run that needs approval is
	// denied instead of asking, which is the pre-P4 behaviour.
	HumanInTheLoop bool
}

Config is the adapter's resolved configuration.

func ConfigFromApp

func ConfigFromApp(c config.AGUIConfig) Config

ConfigFromApp resolves an AGUIConfig into the adapter's own Config, applying the defaults the adapter guarantees even when the config file predates this feature.

type Context

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

Context is ambient information the page attaches to the run.

type CustomEvent

type CustomEvent struct {
	BaseEvent
	Name  string `json:"name"`
	Value any    `json:"value,omitempty"`
}

func NewCustom

func NewCustom(name string, value any) CustomEvent

NewCustom builds a CUSTOM event. Pando-specific signals that have no AG-UI counterpart are emitted this way, namespaced as "pando.<something>", so a generic AG-UI client can ignore them safely.

type Deps

type Deps struct {
	Sessions     session.Service
	Messages     message.Service
	History      history.Service
	Skills       *skills.SkillManager
	Gateway      *mcpgateway.Gateway
	Orchestrator *orchestrator.Orchestrator
	Remembrances *rag.RemembrancesService
	// LSP is the language-server provider used by the edit/view tools.
	// *app.App satisfies it.
	LSP tools.LSPProvider
	// DB is the shared SQLite connection, used only for the adapter's own
	// agui_threads table (thread -> session bindings). It is optional: with a nil
	// DB the mapping is in-memory and does not survive a restart.
	DB *sql.DB
	// Token is the API bearer token clients must present when
	// Config.RequireToken is set. It is supplied by the caller so the adapter
	// shares the API server's token instead of minting a second one.
	Token string
}

Deps are the collaborators the adapter needs to build its own agents. It mirrors the arguments internal/app/app.go already passes to agent.CoderAgentToolsWithMesnada, minus the two services the adapter creates itself (permissions and user input, see invariant I3 in doc.go) and minus the agent itself (invariant I2).

The struct exists so this package never imports *app.App: that would be an import cycle and would also make the adapter reachable from the rest of the application, which is exactly what the isolation invariants forbid.

type Event

type Event interface {
	EventType() EventType
}

Event is any AG-UI event. Implementations embed BaseEvent.

type EventType

type EventType string

EventType is the AG-UI event discriminator. Values are serialized uppercase with underscores, matching the protocol's EventType enum.

const (
	EventTextMessageStart   EventType = "TEXT_MESSAGE_START"
	EventTextMessageContent EventType = "TEXT_MESSAGE_CONTENT"
	EventTextMessageEnd     EventType = "TEXT_MESSAGE_END"
	EventTextMessageChunk   EventType = "TEXT_MESSAGE_CHUNK"

	EventToolCallStart  EventType = "TOOL_CALL_START"
	EventToolCallArgs   EventType = "TOOL_CALL_ARGS"
	EventToolCallEnd    EventType = "TOOL_CALL_END"
	EventToolCallResult EventType = "TOOL_CALL_RESULT"

	EventStateSnapshot    EventType = "STATE_SNAPSHOT"
	EventStateDelta       EventType = "STATE_DELTA"
	EventMessagesSnapshot EventType = "MESSAGES_SNAPSHOT"

	EventActivitySnapshot EventType = "ACTIVITY_SNAPSHOT"
	EventActivityDelta    EventType = "ACTIVITY_DELTA"

	EventRaw    EventType = "RAW"
	EventCustom EventType = "CUSTOM"

	EventRunStarted  EventType = "RUN_STARTED"
	EventRunFinished EventType = "RUN_FINISHED"
	EventRunError    EventType = "RUN_ERROR"

	EventStepStarted  EventType = "STEP_STARTED"
	EventStepFinished EventType = "STEP_FINISHED"

	EventReasoningStart          EventType = "REASONING_START"
	EventReasoningMessageStart   EventType = "REASONING_MESSAGE_START"
	EventReasoningMessageContent EventType = "REASONING_MESSAGE_CONTENT"
	EventReasoningMessageEnd     EventType = "REASONING_MESSAGE_END"
	EventReasoningEnd            EventType = "REASONING_END"
)

type FileState

type FileState struct {
	Path   string `json:"path"`
	Name   string `json:"name"`
	Action string `json:"action"`
}

FileState is one workspace file the run touched.

type InfoResponse

type InfoResponse struct {
	Protocol     string            `json:"protocol"`
	Version      string            `json:"version,omitempty"`
	Path         string            `json:"path"`
	Agents       []AgentDescriptor `json:"agents"`
	Capabilities Capabilities      `json:"capabilities"`
}

InfoResponse is the agent-discovery payload.

type InputContent

type InputContent struct {
	Type string `json:"type"`
	Text string `json:"text,omitempty"`
	// URL/Data carry non-text parts. Only text is consumed today; other kinds
	// are preserved so P2+ can map them onto message.Attachment.
	URL      string `json:"url,omitempty"`
	Data     string `json:"data,omitempty"`
	MimeType string `json:"mimeType,omitempty"`
}

InputContent is one part of a multimodal user message.

type JSONPatchOperation

type JSONPatchOperation struct {
	Op    string `json:"op"`
	Path  string `json:"path"`
	Value any    `json:"value,omitempty"`
	From  string `json:"from,omitempty"`
}

JSONPatchOperation is a single RFC-6902 operation as carried by StateDelta.

type Listener

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

Listener is a running dedicated AG-UI listener.

func (*Listener) Addr

func (l *Listener) Addr() string

Addr is the address the listener actually bound to.

func (*Listener) Shutdown

func (l *Listener) Shutdown(ctx context.Context) error

Shutdown stops the listener. It does not close the Runtime: the caller owns that, because the same Runtime may also be mounted elsewhere.

func (*Listener) URL

func (l *Listener) URL() string

URL is the base URL clients should point CopilotKit at.

func (*Listener) Wait

func (l *Listener) Wait() error

Wait blocks until the listener stops, returning the reason. A clean shutdown reports nil.

type ListenerOptions

type ListenerOptions struct {
	// Host to bind to. Empty means localhost, never all interfaces: this
	// surface drives an agent that executes code, so exposing it on 0.0.0.0 has
	// to be a deliberate act.
	Host string
	// CertFile and KeyFile enable TLS. Both or neither.
	CertFile string
	KeyFile  string
}

ListenerOptions describes the dedicated listener requested by Config.Port.

type Message

type Message struct {
	ID           string         `json:"id"`
	Role         string         `json:"role"`
	Content      MessageContent `json:"content,omitempty"`
	Name         string         `json:"name,omitempty"`
	ToolCalls    []ToolCall     `json:"toolCalls,omitempty"`
	ToolCallID   string         `json:"toolCallId,omitempty"`
	Error        string         `json:"error,omitempty"`
	ActivityType string         `json:"activityType,omitempty"`
}

Message is one entry of the AG-UI conversation.

type MessageContent

type MessageContent struct {
	Text  string
	Parts []InputContent
}

MessageContent holds either a plain string or a list of multimodal parts. AG-UI allows both shapes on user messages.

func (MessageContent) HasNonTextParts

func (m MessageContent) HasNonTextParts() bool

HasNonTextParts reports whether the message carries content this adapter cannot forward to the agent yet.

func (MessageContent) MarshalJSON

func (m MessageContent) MarshalJSON() ([]byte, error)

MarshalJSON writes back the shape the content was received in.

func (MessageContent) String

func (m MessageContent) String() string

String flattens the content to plain text, joining the text parts of a multimodal message. Non-text parts are skipped (see doc.go, P2).

func (*MessageContent) UnmarshalJSON

func (m *MessageContent) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts either a JSON string or an array of InputContent.

type MessagesSnapshotEvent

type MessagesSnapshotEvent struct {
	BaseEvent
	Messages []Message `json:"messages"`
}

func NewMessagesSnapshot

func NewMessagesSnapshot(msgs []Message) MessagesSnapshotEvent

type ModelDescriptor

type ModelDescriptor struct {
	ID            string `json:"id"`
	Name          string `json:"name,omitempty"`
	Provider      string `json:"provider,omitempty"`
	ContextWindow int64  `json:"contextWindow,omitempty"`
}

ModelDescriptor tells the client what it is talking to, so a dashboard can show the model and size its own context budget. It carries no credentials and no provider endpoint: the provider is named, never located.

type ModelState

type ModelState struct {
	ID            string `json:"id"`
	Name          string `json:"name,omitempty"`
	Provider      string `json:"provider,omitempty"`
	ContextWindow int64  `json:"contextWindow,omitempty"`
}

ModelState describes the model backing the thread.

type RawEvent

type RawEvent struct {
	BaseEvent
	Event  any    `json:"event"`
	Source string `json:"source,omitempty"`
}

func NewRaw

func NewRaw(event any, source string) RawEvent

type ReasoningEndEvent

type ReasoningEndEvent struct {
	BaseEvent
	MessageID string `json:"messageId"`
}

func NewReasoningEnd

func NewReasoningEnd(messageID string) ReasoningEndEvent

type ReasoningMessageContentEvent

type ReasoningMessageContentEvent struct {
	BaseEvent
	MessageID string `json:"messageId"`
	Delta     string `json:"delta"`
}

func NewReasoningMessageContent

func NewReasoningMessageContent(messageID, delta string) ReasoningMessageContentEvent

type ReasoningMessageEndEvent

type ReasoningMessageEndEvent struct {
	BaseEvent
	MessageID string `json:"messageId"`
}

func NewReasoningMessageEnd

func NewReasoningMessageEnd(messageID string) ReasoningMessageEndEvent

type ReasoningMessageStartEvent

type ReasoningMessageStartEvent struct {
	BaseEvent
	MessageID string `json:"messageId"`
	Role      string `json:"role"`
}

func NewReasoningMessageStart

func NewReasoningMessageStart(messageID string) ReasoningMessageStartEvent

type ReasoningStartEvent

type ReasoningStartEvent struct {
	BaseEvent
	MessageID string `json:"messageId"`
}

func NewReasoningStart

func NewReasoningStart(messageID string) ReasoningStartEvent

type RunAgentInput

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

RunAgentInput is the request body of an AG-UI run. It is sent in full on every turn: the client owns the visible transcript, so `Messages` carries the whole conversation even though Pando keeps its own history server-side.

func DecodeRunAgentInput

func DecodeRunAgentInput(r io.Reader, maxBytes int64) (*RunAgentInput, error)

DecodeRunAgentInput reads and validates a RunAgentInput from an HTTP body. maxBytes caps the payload; pass 0 for the default.

func (*RunAgentInput) ContextBlock

func (in *RunAgentInput) ContextBlock() string

ContextBlock renders the client-supplied context entries as a text block that can be prefixed to the prompt. Returns "" when there is no context.

func (*RunAgentInput) LastUserMessage

func (in *RunAgentInput) LastUserMessage() (Message, bool)

LastUserMessage returns the trailing user message, which is the only part of the transcript this adapter forwards to Pando: the agent keeps its own history, so replaying the whole array would duplicate context and defeat compaction and prompt caching.

func (*RunAgentInput) TrailingToolMessages

func (in *RunAgentInput) TrailingToolMessages() []Message

TrailingToolMessages returns the tool messages that appear after the last user message. They are frontend tool results resolving a previously interrupted run rather than a new prompt (P3 consumes them).

func (*RunAgentInput) Validate

func (in *RunAgentInput) Validate() error

Validate enforces the protocol's required fields.

type RunErrorEvent

type RunErrorEvent struct {
	BaseEvent
	Message string `json:"message"`
	Code    string `json:"code,omitempty"`
}

func NewRunError

func NewRunError(msg, code string) RunErrorEvent

type RunFinishedEvent

type RunFinishedEvent struct {
	BaseEvent
	ThreadID string `json:"threadId"`
	RunID    string `json:"runId"`
	Outcome  string `json:"outcome,omitempty"`
	Result   any    `json:"result,omitempty"`
}

func NewRunFinished

func NewRunFinished(threadID, runID, outcome string, result any) RunFinishedEvent

type RunStartedEvent

type RunStartedEvent struct {
	BaseEvent
	ThreadID    string `json:"threadId"`
	RunID       string `json:"runId"`
	ParentRunID string `json:"parentRunId,omitempty"`
}

func NewRunStarted

func NewRunStarted(threadID, runID string) RunStartedEvent

type Runtime

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

Runtime is the AG-UI adapter. It owns everything the protocol needs that Pando does not already provide: its own agent instances, its own permission and user-input services, and the thread bookkeeping AG-UI clients expect.

Nothing outside this package holds a reference to those objects, which is what keeps the adapter isolated from the TUI, Web-UI and ACP surfaces.

func New

func New(deps Deps, cfg Config) (*Runtime, error)

New builds the adapter. It does not start any listener; call Register to mount it on a mux.

func (*Runtime) Close

func (r *Runtime) Close()

Close stops the adapter's background work and cancels every detached run. Pooled agents are left to the garbage collector.

func (*Runtime) Handler

func (r *Runtime) Handler() http.Handler

Handler returns a standalone mux carrying only the AG-UI routes, for serving the adapter on its own listener (Config.Port > 0).

func (*Runtime) Register

func (r *Runtime) Register(mux *http.ServeMux)

Register mounts the adapter's routes on mux:

POST {path}/{agent}  run an agent, streaming AG-UI events over SSE
GET  {path}/info     agent discovery, consumed by CopilotKit's runtime

It is the only place this package touches the outside world's routing, and it is called only when the feature is enabled.

func (*Runtime) StartListener

func (r *Runtime) StartListener(opts ListenerOptions) (*Listener, error)

StartListener serves the adapter on its own port, carrying only the AG-UI routes.

This is deployment shape 2 of invariant I7: a browser origin that can reach the AG-UI endpoint then cannot reach the Web-UI API at all — no sessions list, no config, no file endpoints, no static UI. It is the recommended shape for anything beyond localhost.

The listener is bound synchronously so a port clash is reported to the caller instead of being lost in a goroutine.

type SSEWriter

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

SSEWriter serializes AG-UI events to the Server-Sent Events wire format.

AG-UI puts the discriminator inside the JSON payload rather than in the SSE `event:` field, so every frame is a bare `data:` line. Clients (including AG-UI's HttpAgent) parse the JSON and switch on `type`.

func NewSSEWriter

func NewSSEWriter(w http.ResponseWriter) (*SSEWriter, error)

NewSSEWriter writes the SSE response headers and returns a writer bound to w. Headers other than the streaming ones (CORS, auth) must already be set.

func (*SSEWriter) Close

func (s *SSEWriter) Close()

Close marks the stream finished. Further writes are refused; the HTTP handler returning is what actually terminates the response.

func (*SSEWriter) Comment

func (s *SSEWriter) Comment(text string) error

Comment emits an SSE comment. Used as a heartbeat to keep intermediaries from dropping an idle connection while the agent is busy in a long tool call.

func (*SSEWriter) Write

func (s *SSEWriter) Write(ev Event) error

Write encodes and flushes a single event.

func (*SSEWriter) WriteAll

func (s *SSEWriter) WriteAll(events []Event) error

WriteAll encodes a batch, stopping at the first failure.

type StateDeltaEvent

type StateDeltaEvent struct {
	BaseEvent
	Delta []JSONPatchOperation `json:"delta"`
}

func NewStateDelta

func NewStateDelta(ops []JSONPatchOperation) StateDeltaEvent

type StateDoc

type StateDoc struct {
	Thread     string           `json:"thread"`
	Session    string           `json:"session"`
	Agent      string           `json:"agent"`
	Model      ModelState       `json:"model"`
	Todos      []tools.TodoItem `json:"todos"`
	TokenUsage *TokenUsageState `json:"tokenUsage"`
	Files      []FileState      `json:"files"`
	// SubAgents lists the mesnada tasks this thread delegated (P7). See
	// subagents.go: it is derived from the mesnada_* tool traffic, never read
	// from the orchestrator.
	SubAgents []SubAgentState `json:"subAgents"`
	// Client echoes RunAgentInput.state. It is read-only context the page pushed
	// into the run; the adapter never writes it back into Pando's config.
	Client any `json:"client,omitempty"`
}

StateDoc is the document published to the client. Field order is irrelevant to the protocol; the JSON pointer paths used by the deltas are what matter, so the tags below are part of the contract.

type StateSnapshotEvent

type StateSnapshotEvent struct {
	BaseEvent
	Snapshot any `json:"snapshot"`
}

func NewStateSnapshot

func NewStateSnapshot(snapshot any) StateSnapshotEvent

type StepFinishedEvent

type StepFinishedEvent struct {
	BaseEvent
	StepName string `json:"stepName"`
}

func NewStepFinished

func NewStepFinished(name string) StepFinishedEvent

type StepStartedEvent

type StepStartedEvent struct {
	BaseEvent
	StepName string `json:"stepName"`
}

func NewStepStarted

func NewStepStarted(name string) StepStartedEvent

type SubAgentState

type SubAgentState struct {
	ID     string `json:"id"`
	Status string `json:"status"`
	// Role is set for tasks created by mesnada_swarm, which assigns fixed roles.
	Role     string `json:"role,omitempty"`
	Prompt   string `json:"prompt,omitempty"`
	Engine   string `json:"engine,omitempty"`
	Model    string `json:"model,omitempty"`
	Persona  string `json:"persona,omitempty"`
	Error    string `json:"error,omitempty"`
	ExitCode *int   `json:"exitCode,omitempty"`
	// Conclusion is the delegated task's self-reported outcome
	// (success|partial|failed|blocked), present once the task concluded.
	Conclusion string `json:"conclusion,omitempty"`
	// Summary is the conclusion's one-paragraph summary, when captured.
	Summary string `json:"summary,omitempty"`
}

SubAgentState is one delegated mesnada task as published to the client. Field names are camelCase because the JSON pointers built from them are part of the protocol contract, like the rest of StateDoc.

type TextMessageContentEvent

type TextMessageContentEvent struct {
	BaseEvent
	MessageID string `json:"messageId"`
	Delta     string `json:"delta"`
}

func NewTextMessageContent

func NewTextMessageContent(messageID, delta string) TextMessageContentEvent

type TextMessageEndEvent

type TextMessageEndEvent struct {
	BaseEvent
	MessageID string `json:"messageId"`
}

func NewTextMessageEnd

func NewTextMessageEnd(messageID string) TextMessageEndEvent

type TextMessageStartEvent

type TextMessageStartEvent struct {
	BaseEvent
	MessageID string `json:"messageId"`
	Role      string `json:"role"`
}

func NewTextMessageStart

func NewTextMessageStart(messageID string) TextMessageStartEvent

type TokenUsageState

type TokenUsageState struct {
	PromptTokens     int64   `json:"promptTokens"`
	CompletionTokens int64   `json:"completionTokens"`
	ContextWindow    int64   `json:"contextWindow"`
	Estimated        bool    `json:"estimated"`
	CacheReadTokens  int64   `json:"cacheReadTokens,omitempty"`
	CacheWriteTokens int64   `json:"cacheWriteTokens,omitempty"`
	ReasoningTokens  int64   `json:"reasoningTokens,omitempty"`
	Cost             float64 `json:"cost,omitempty"`
}

TokenUsageState is the live context budget, mirroring agent.TokenUsageInfo in the protocol's camelCase convention.

type Tool

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

Tool is a frontend-declared tool. The agent may call it; the browser executes it and returns the outcome as a tool message on the next run.

type ToolCall

type ToolCall struct {
	ID       string           `json:"id"`
	Type     string           `json:"type,omitempty"`
	Function ToolCallFunction `json:"function"`
}

ToolCall is an assistant-issued call as echoed back by the client.

type ToolCallArgsEvent

type ToolCallArgsEvent struct {
	BaseEvent
	ToolCallID string `json:"toolCallId"`
	Delta      string `json:"delta"`
}

func NewToolCallArgs

func NewToolCallArgs(id, delta string) ToolCallArgsEvent

type ToolCallEndEvent

type ToolCallEndEvent struct {
	BaseEvent
	ToolCallID string `json:"toolCallId"`
}

func NewToolCallEnd

func NewToolCallEnd(id string) ToolCallEndEvent

type ToolCallFunction

type ToolCallFunction struct {
	Name      string `json:"name"`
	Arguments string `json:"arguments"`
}

ToolCallFunction is the OpenAI-shaped function payload of a tool call.

type ToolCallResultEvent

type ToolCallResultEvent struct {
	BaseEvent
	MessageID  string `json:"messageId"`
	ToolCallID string `json:"toolCallId"`
	Content    string `json:"content"`
	Role       string `json:"role,omitempty"`
}

func NewToolCallResult

func NewToolCallResult(messageID, toolCallID, content string) ToolCallResultEvent

type ToolCallStartEvent

type ToolCallStartEvent struct {
	BaseEvent
	ToolCallID      string `json:"toolCallId"`
	ToolCallName    string `json:"toolCallName"`
	ParentMessageID string `json:"parentMessageId,omitempty"`
}

func NewToolCallStart

func NewToolCallStart(id, name, parentMessageID string) ToolCallStartEvent

Jump to

Keyboard shortcuts

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