a2a

package
v0.87.2 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Package a2a implements the server side of the Agent2Agent protocol v1.0 task exchange over its JSON-RPC binding: SendMessage, SendStreamingMessage, GetTask, ListTasks, CancelTask, SubscribeToTask, the four push-notification-config operations, and GetExtendedAgentCard.

Wire facts this package pins, each verified against the canonical a2a.proto and the A2A project's own Go SDK rather than recalled:

  • JSON-RPC method names are the PascalCase RPC names ("SendMessage", "GetTask", …). The v0.x slash forms ("message/send") are gone.
  • Field names are camelCase; enums serialize as their proto names ("TASK_STATE_COMPLETED", "ROLE_USER"); timestamps are RFC 3339 UTC.
  • A Part is a flat object carrying exactly one of text / raw / url / data plus optional filename, mediaType, metadata.
  • A streaming result is a StreamResponse: an object with exactly one of task / message / statusUpdate / artifactUpdate.
  • Streaming methods answer with text/event-stream; each event's data is a complete JSON-RPC response object whose result is a StreamResponse. The stream closes once the task is terminal or interrupted.
  • Push notifications POST a StreamResponse to the configured URL with the A2A-Notification-Token header carrying the config's token.

The agent card (discovery) lives in framework/uihost; this package is the exchange behind the card's JSONRPC interface. Skills are what the server does with a message: a GoFastr app is a deterministic agent, so a skill is invoked by name (Message.metadata["skill"], or a data part carrying a "skill" key), never inferred from prose. See framework/docs/ content/a2a.md.

Index

Constants

View Source
const (
	CodeParseError     = -32700
	CodeInvalidRequest = -32600
	CodeMethodNotFound = -32601
	CodeInvalidParams  = -32602
	CodeInternalError  = -32603

	CodeTaskNotFound                   = -32001
	CodeTaskNotCancelable              = -32002
	CodePushNotificationNotSupported   = -32003
	CodeUnsupportedOperation           = -32004
	CodeContentTypeNotSupported        = -32005
	CodeInvalidAgentResponse           = -32006
	CodeExtendedAgentCardNotConfigured = -32007
	CodeExtensionSupportRequired       = -32008
	CodeVersionNotSupported            = -32009

	CodeUnauthenticated = -31401
	CodeUnauthorized    = -31403
)

JSON-RPC error codes. The -327xx/-326xx block is JSON-RPC 2.0 itself; the -3200x block is the A2A v1.0 error mapping (spec §5.4); -31401/-31403 are the auth codes the A2A project's SDKs use for a caller the server could not identify or would not allow, kept so a client built on those SDKs maps our refusals to the same errors it already handles.

View Source
const (
	MethodSendMessage                      = "SendMessage"
	MethodSendStreamingMessage             = "SendStreamingMessage"
	MethodGetTask                          = "GetTask"
	MethodListTasks                        = "ListTasks"
	MethodCancelTask                       = "CancelTask"
	MethodSubscribeToTask                  = "SubscribeToTask"
	MethodCreateTaskPushNotificationConfig = "CreateTaskPushNotificationConfig"
	MethodGetTaskPushNotificationConfig    = "GetTaskPushNotificationConfig"
	MethodListTaskPushNotificationConfigs  = "ListTaskPushNotificationConfigs"
	MethodDeleteTaskPushNotificationConfig = "DeleteTaskPushNotificationConfig"
	MethodGetExtendedAgentCard             = "GetExtendedAgentCard"
)

JSON-RPC method names of the A2A v1.0 JSON-RPC binding. Pinned as the PascalCase RPC names from specification/a2a.proto (§5.3 method mapping); the v0.x slash forms are wire-incompatible, not merely outdated.

View Source
const ProtocolVersion = "1.0"

ProtocolVersion is the A2A protocol version this package speaks, in the Major.Minor form the agent card's supportedInterfaces expects.

View Source
const PushNotificationTokenHeader = "A2A-Notification-Token" // not-a-secret: a header name

PushNotificationTokenHeader is the header carrying PushNotificationConfig.Token on a push delivery, so the receiver can check the notification came from the agent it registered with.

Variables

View Source
var (
	ErrNotFound = errors.New("a2a: not found")
	ErrConflict = errors.New("a2a: version conflict")
)

Store errors. Implementations return these (or wrap them) so the server can map them to JSON-RPC codes without knowing the backend.

Methods lists every JSON-RPC method the server dispatches, in spec order.

Functions

func DefaultRouter

func DefaultRouter(_ context.Context, msg *Message, skills []Skill) (string, error)

DefaultRouter picks the skill for a message:

  1. msg.Metadata["skill"], when a non-empty string;
  2. else the first data part whose Data is a JSON object with a string "skill" key;
  3. else the only registered skill, when exactly one is;
  4. else an error naming the available ids.

A skill id that is not registered is an error at step 1 and 2 alike — the router names what exists rather than guessing.

func RequestFromContext

func RequestFromContext(ctx context.Context) (*http.Request, bool)

RequestFromContext returns the request stashed by WithRequest.

func WithRequest

func WithRequest(ctx context.Context, r *http.Request) context.Context

WithRequest returns a context carrying r, for TaskContext.Request.

Types

type Artifact

type Artifact struct {
	ArtifactID  string         `json:"artifactId"`
	Name        string         `json:"name,omitempty"`
	Description string         `json:"description,omitempty"`
	Parts       []Part         `json:"parts"`
	Metadata    map[string]any `json:"metadata,omitempty"`
	Extensions  []string       `json:"extensions,omitempty"`
}

Artifact is an output produced by a task.

type AuthenticationInfo

type AuthenticationInfo struct {
	Scheme      string `json:"scheme"`
	Credentials string `json:"credentials,omitempty"`
}

AuthenticationInfo tells the agent how to authenticate to a push URL. Scheme "Bearer" or "Basic" become an Authorization header built from Credentials verbatim; other schemes are stored and ignored on send.

type CancelTaskRequest

type CancelTaskRequest struct {
	Tenant   string         `json:"tenant,omitempty"`
	ID       string         `json:"id"`
	Metadata map[string]any `json:"metadata,omitempty"`
}

CancelTaskRequest is the params object of CancelTask.

type Capabilities

type Capabilities struct {
	Streaming         bool `json:"streaming"`
	PushNotifications bool `json:"pushNotifications"`
	ExtendedAgentCard bool `json:"extendedAgentCard,omitempty"`
}

Capabilities is what the server advertises; the agent card copies it.

type Config

type Config struct {
	// Skills is what the agent can do; at least one, unique ids, and
	// every Handler non-nil.
	Skills []Skill
	// Store persists tasks and push configs. Nil → NewMemoryStore().
	Store Store
	// Router picks the skill for a message. Nil → DefaultRouter.
	Router Router
	// Owner resolves the caller for every request. ok=false → HTTP 401
	// and CodeUnauthenticated. Nil is a NewServer error.
	Owner func(r *http.Request) (owner string, ok bool)
	// ExtendedCard, when set, serves GetExtendedAgentCard. Nil → the
	// method answers CodeExtendedAgentCardNotConfigured and
	// Capabilities().ExtendedAgentCard is false.
	ExtendedCard func(ctx context.Context, owner string) (map[string]any, error)
	// Push tunes push-notification delivery; see PushOptions.
	Push PushOptions
	// Logger for handler failures, drops, and push errors. Nil →
	// slog.Default().
	Logger *slog.Logger
	// AllowedOrigins permits browser Origins that are not same-origin
	// with the request: tunnels (ngrok, Codespaces) and split-origin
	// web clients. The default posture refuses a present Origin naming
	// any authority other than the request's own Host, mirroring the
	// MCP transport's cross-origin gate; native A2A clients send no
	// Origin and always pass.
	AllowedOrigins []string
	// MaxBodyBytes caps one request body. Default 1 MiB.
	MaxBodyBytes int64
	// TaskTimeout is the ceiling on one skill-handler run. Default
	// 5 minutes.
	TaskTimeout time.Duration
	// MaxHistory caps stored history per task (oldest dropped).
	// Default 200 messages.
	MaxHistory int
	// DefaultPageSize and MaxPageSize bound ListTasks paging.
	// Defaults 50 / 200.
	DefaultPageSize, MaxPageSize int

	// TerminalTaskRetention bounds how many TERMINAL task rows
	// (completed / failed / canceled / rejected) one owner retains:
	// past the bound the OLDEST terminal rows are deleted after each
	// settle. 0 = 64 (every row carries the caller's message body, and
	// the default store is in-RAM, so cheap authenticated writes must
	// not grow it without end); negative disables trimming for hosts
	// that own retention themselves. Applies to stores implementing
	// RetentionTrimmer — both built-ins do.
	TerminalTaskRetention int

	// MaxPushConfigsPerTask bounds push-notification configs per task
	// (each one fans a delivery goroutine out per event): past the
	// bound the OLDEST configs are deleted. 0 = 8; negative disables.
	MaxPushConfigsPerTask int

	// MaxStreamSeatsPerOwner bounds the concurrent SSE streams
	// (SubscribeToTask + SendStreamingMessage) ONE owner may hold: each
	// pins a relay goroutine, a bus channel, and tickers for the
	// stream's whole life. 0 = 16 (the default every stream surface in
	// the tree shares); negative lifts the cap for deployments that
	// bound streams elsewhere.
	MaxStreamSeatsPerOwner int

	// MaxConcurrentRunsPerOwner bounds the concurrent skill-handler runs
	// one owner may start, including ReturnImmediately runs. 0 = 16;
	// negative disables the cap for deployments that bound runs elsewhere.
	MaxConcurrentRunsPerOwner int

	// StreamSeatOverflow selects what an owner at MaxStreamSeatsPerOwner
	// does with their next stream: stream.SeatOverflowRefuse (the
	// default) answers 429 at connect; stream.SeatOverflowEvictOldest
	// closes that owner's oldest stream and seats the new one — the
	// multi-tab-friendly policy for clients that reconnect faster than
	// a half-open connection's seat is reclaimed.
	StreamSeatOverflow stream.SeatOverflowPolicy
}

Config constructs a Server. Owner is required: multi-user scoping is not optional, so a nil Owner is a NewServer error rather than a silently single-tenant server.

type DeleteTaskPushNotificationConfigRequest

type DeleteTaskPushNotificationConfigRequest struct {
	Tenant string `json:"tenant,omitempty"`
	TaskID string `json:"taskId"`
	ID     string `json:"id"`
}

DeleteTaskPushNotificationConfigRequest is the params object of DeleteTaskPushNotificationConfig. The result is an empty object.

type Error

type Error struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Data    any    `json:"data,omitempty"`
}

Error is a JSON-RPC error object. Returning one from a skill handler or a Store surfaces it to the client with its code intact; any other error is reported as CodeInternalError with a generic message, never its text.

func ErrTaskNotCancelable

func ErrTaskNotCancelable(id string, state TaskState) *Error

ErrTaskNotCancelable is the refusal for cancelling a task already in a terminal state.

func ErrTaskNotFound

func ErrTaskNotFound(id string) *Error

ErrTaskNotFound is the refusal for an unknown task id — or, deliberately, for a task another owner holds: the two are indistinguishable to the caller, so task ids do not enumerate across owners.

func ErrUnauthenticated

func ErrUnauthenticated() *Error

ErrUnauthenticated is the refusal for a caller with no resolved identity.

func ErrUnsupportedOperation

func ErrUnsupportedOperation(msg string) *Error

ErrUnsupportedOperation is the refusal for an operation the task's state does not admit, such as a message to a terminal task.

func Errorf

func Errorf(code int, format string, args ...any) *Error

Errorf builds an Error with a formatted message.

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface.

type GetExtendedAgentCardRequest

type GetExtendedAgentCardRequest struct {
	Tenant string `json:"tenant,omitempty"`
}

GetExtendedAgentCardRequest is the params object of GetExtendedAgentCard.

type GetTaskPushNotificationConfigRequest

type GetTaskPushNotificationConfigRequest struct {
	Tenant string `json:"tenant,omitempty"`
	TaskID string `json:"taskId"`
	ID     string `json:"id"`
}

GetTaskPushNotificationConfigRequest is the params object of GetTaskPushNotificationConfig.

type GetTaskRequest

type GetTaskRequest struct {
	Tenant        string `json:"tenant,omitempty"`
	ID            string `json:"id"`
	HistoryLength *int   `json:"historyLength,omitempty"`
}

GetTaskRequest is the params object of GetTask.

type Handler

type Handler func(ctx context.Context, t TaskContext) error

Handler does the work of a skill for one inbound message. It reports progress and results through the TaskContext; returning nil with the task still in progress completes it, returning an error fails it (a context cancellation cancels it instead). A returned *Error keeps its code and message; any other error is logged and the client sees the generic "skill handler failed", so a wrapped database or network error never reaches a caller. Fail(TextPart(...)) is the way to say why.

type ListQuery

type ListQuery struct {
	ContextID string
	Status    TaskState
	// After filters to tasks whose status timestamp is after this instant
	// (zero: no filter).
	After  time.Time
	Limit  int
	Offset int
}

ListQuery is the store-level shape of ListTasks.

type ListTaskPushNotificationConfigsRequest

type ListTaskPushNotificationConfigsRequest struct {
	Tenant    string `json:"tenant,omitempty"`
	TaskID    string `json:"taskId"`
	PageSize  *int   `json:"pageSize,omitempty"`
	PageToken string `json:"pageToken,omitempty"`
}

ListTaskPushNotificationConfigsRequest is the params object of ListTaskPushNotificationConfigs.

type ListTaskPushNotificationConfigsResponse

type ListTaskPushNotificationConfigsResponse struct {
	Configs       []PushNotificationConfig `json:"configs"`
	NextPageToken string                   `json:"nextPageToken,omitempty"`
}

ListTaskPushNotificationConfigsResponse is the result of ListTaskPushNotificationConfigs. Configs is never null on the wire.

type ListTasksRequest

type ListTasksRequest struct {
	Tenant               string     `json:"tenant,omitempty"`
	ContextID            string     `json:"contextId,omitempty"`
	Status               TaskState  `json:"status,omitempty"`
	PageSize             *int       `json:"pageSize,omitempty"`
	PageToken            string     `json:"pageToken,omitempty"`
	HistoryLength        *int       `json:"historyLength,omitempty"`
	StatusTimestampAfter *Timestamp `json:"statusTimestampAfter,omitempty"`
	IncludeArtifacts     *bool      `json:"includeArtifacts,omitempty"`
}

ListTasksRequest is the params object of ListTasks.

type ListTasksResponse

type ListTasksResponse struct {
	Tasks         []Task `json:"tasks"`
	NextPageToken string `json:"nextPageToken"`
	PageSize      int    `json:"pageSize"`
	TotalSize     int    `json:"totalSize"`
}

ListTasksResponse is the result of ListTasks. Tasks is never null on the wire: an empty page encodes as [].

type MemoryStore

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

MemoryStore is the in-process Store: a mutex, two maps, deep copies at the boundary. Every read and write is scoped by owner in the map key, not filtered after the fact, so the owner predicate cannot be forgotten on a new query shape. Use the SQLStore to share tasks across replicas.

func NewMemoryStore

func NewMemoryStore() *MemoryStore

NewMemoryStore returns an empty in-memory store.

func (*MemoryStore) CreatePushConfig

func (m *MemoryStore) CreatePushConfig(_ context.Context, rec *PushConfigRecord) error

func (*MemoryStore) CreateTask

func (m *MemoryStore) CreateTask(_ context.Context, rec *TaskRecord) error

func (*MemoryStore) DeletePushConfig

func (m *MemoryStore) DeletePushConfig(_ context.Context, owner, taskID, id string) error

func (*MemoryStore) GetPushConfig

func (m *MemoryStore) GetPushConfig(_ context.Context, owner, taskID, id string) (*PushConfigRecord, error)

func (*MemoryStore) GetTask

func (m *MemoryStore) GetTask(_ context.Context, owner, id string) (*TaskRecord, error)

func (*MemoryStore) ListPushConfigs

func (m *MemoryStore) ListPushConfigs(_ context.Context, owner, taskID string) ([]*PushConfigRecord, error)

func (*MemoryStore) ListTasks

func (m *MemoryStore) ListTasks(_ context.Context, owner string, q ListQuery) (recs []*TaskRecord, total int, err error)

func (*MemoryStore) TrimPushConfigs added in v0.85.0

func (m *MemoryStore) TrimPushConfigs(_ context.Context, owner, taskID string, keep int) error

TrimPushConfigs implements RetentionTrimmer: it keeps the newest `keep` configs for (owner, taskID) — the same order ListPushConfigs serves — and deletes the older ones.

func (*MemoryStore) TrimTerminalTasks added in v0.85.0

func (m *MemoryStore) TrimTerminalTasks(_ context.Context, owner string, keep int) error

TrimTerminalTasks implements RetentionTrimmer: it keeps the newest `keep` terminal rows for owner and deletes the older ones. Non-terminal rows (a task in flight, or paused on input) are never retention's to touch.

func (*MemoryStore) UpdateTask

func (m *MemoryStore) UpdateTask(_ context.Context, rec *TaskRecord) error

type Message

type Message struct {
	MessageID        string         `json:"messageId"`
	ContextID        string         `json:"contextId,omitempty"`
	TaskID           string         `json:"taskId,omitempty"`
	Role             Role           `json:"role"`
	Parts            []Part         `json:"parts"`
	Metadata         map[string]any `json:"metadata,omitempty"`
	Extensions       []string       `json:"extensions,omitempty"`
	ReferenceTaskIDs []string       `json:"referenceTaskIds,omitempty"`
}

Message is one turn of the exchange, from the client (RoleUser) or the agent (RoleAgent).

type Part

type Part struct {
	Text      *string        `json:"text,omitempty"`
	Raw       []byte         `json:"raw,omitempty"`
	URL       *string        `json:"url,omitempty"`
	Data      *any           `json:"data,omitempty"`
	Filename  string         `json:"filename,omitempty"`
	MediaType string         `json:"mediaType,omitempty"`
	Metadata  map[string]any `json:"metadata,omitempty"`
}

Part is one unit of content in a message or artifact. Exactly one of Text, Raw, URL, or Data is set; the JSON form is flat, with the set field as the discriminator: {"text": "…"}, {"raw": "<base64>"}, {"url": "…"}, {"data": <any JSON value>}. Filename, MediaType and Metadata are optional on every kind.

Data is a pointer so that a JSON null or false or 0 data payload is distinguishable from "no data part": the pointer is non-nil whenever the key was present.

func DataPart

func DataPart(v any) Part

DataPart builds a structured-data part. MediaType defaults to application/json at the caller's discretion; the spec leaves it optional.

func RawPart

func RawPart(b []byte, filename, mediaType string) Part

RawPart builds a file-by-value part; the bytes serialize as base64.

func TextPart

func TextPart(s string) Part

TextPart builds a text part.

func URLPart

func URLPart(u, filename, mediaType string) Part

URLPart builds a file-by-reference part.

func (Part) Kind

func (p Part) Kind() string

Kind returns which content field the part carries: "text", "raw", "url", "data", or "" when none is set (an invalid part).

func (*Part) UnmarshalJSON

func (p *Part) UnmarshalJSON(b []byte) error

UnmarshalJSON decodes a part and enforces the exactly-one-content rule, so a malformed client part is refused at the boundary rather than reaching a skill as an empty Part.

func (Part) Validate

func (p Part) Validate() error

Validate enforces the exactly-one-content rule the spec states for Part.

type PushConfigRecord

type PushConfigRecord struct {
	Config    PushNotificationConfig
	Owner     string
	CreatedAt time.Time
}

PushConfigRecord is a push-notification config as the Store holds it.

type PushNotificationConfig

type PushNotificationConfig struct {
	Tenant         string              `json:"tenant,omitempty"`
	ID             string              `json:"id,omitempty"`
	TaskID         string              `json:"taskId"`
	URL            string              `json:"url"`
	Token          string              `json:"token,omitempty"`
	Authentication *AuthenticationInfo `json:"authentication,omitempty"`
}

PushNotificationConfig is a webhook the agent notifies about one task. It is both the params object of CreateTaskPushNotificationConfig and the result of the create/get calls.

type PushOptions

type PushOptions struct {
	// Client delivers notifications. Nil means a 10s-timeout client on
	// the SSRF-guarded transport. A supplied client is used as a
	// template: redirect following is forced off (a 3xx must never carry
	// the notification token to a second host) and, unless AllowPrivate
	// is set, it is wrapped with the dial-time guard, so a caller cannot
	// accidentally opt registered URLs out of the SSRF posture by
	// bringing their own client.
	Client *http.Client
	// AllowPrivate permits internal hosts (loopback, RFC1918, CGNAT,
	// link-local). Tests and internal deployments only: a
	// caller-registered URL that the server POSTs to is an SSRF vector
	// otherwise.
	AllowPrivate bool
	// Disable turns the capability off: Capabilities().PushNotifications
	// reports false, CreateTaskPushNotificationConfig answers
	// CodePushNotificationNotSupported, and a config inside
	// SendMessage's configuration is refused the same way.
	Disable bool
}

PushOptions tunes push-notification delivery. The zero value delivers over a guarded default client with private hosts refused.

type RetentionTrimmer added in v0.85.0

type RetentionTrimmer interface {
	// TrimTerminalTasks deletes the owner's OLDEST terminal task rows
	// until at most keep remain. Non-terminal rows are never touched.
	// keep < 0 means unlimited (no-op).
	TrimTerminalTasks(ctx context.Context, owner string, keep int) error
	// TrimPushConfigs deletes the OLDEST push-notification configs
	// registered for (owner, taskID) until at most keep remain.
	// keep < 0 means unlimited (no-op).
	TrimPushConfigs(ctx context.Context, owner, taskID string, keep int) error
}

RetentionTrimmer is the optional Store capability that bounds the rows cheap authenticated writes mint. The server asks the store to trim after every persist that leaves a task terminal and after every push-config create; a store that does not implement it keeps its own retention policy. Both built-in stores implement it.

type Role

type Role string

Role is the author of a message; serializes as "ROLE_USER" / "ROLE_AGENT".

const (
	RoleUnspecified Role = "ROLE_UNSPECIFIED"
	RoleUser        Role = "ROLE_USER"
	RoleAgent       Role = "ROLE_AGENT"
)

type Router

type Router func(ctx context.Context, msg *Message, skills []Skill) (skillID string, err error)

Router picks the skill for a message. The default reads msg.Metadata["skill"], then the first data part carrying a "skill" key, then the only skill when exactly one is registered, and otherwise rejects the task with a message naming the available skill ids.

type SQLStore

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

SQLStore persists tasks and push configs in an existing *sql.DB (SQLite or Postgres, probed like battery/queue's detectDBDialect). The wire Task lives in a task_json column; the scalar columns exist only for owner scoping, filtering, and paging, so the JSON shape can evolve without a migration for every field. Construct with NewSQLStore; tables are created IF NOT EXISTS. Safe for concurrent use and shared across replicas.

func NewSQLStore

func NewSQLStore(db *sql.DB, opts ...SQLStoreOption) (*SQLStore, error)

NewSQLStore constructs the store and ensures its tables exist.

func (*SQLStore) CreatePushConfig

func (s *SQLStore) CreatePushConfig(ctx context.Context, rec *PushConfigRecord) error

func (*SQLStore) CreateTask

func (s *SQLStore) CreateTask(ctx context.Context, rec *TaskRecord) error

func (*SQLStore) DeletePushConfig

func (s *SQLStore) DeletePushConfig(ctx context.Context, owner, taskID, id string) error

func (*SQLStore) GetPushConfig

func (s *SQLStore) GetPushConfig(ctx context.Context, owner, taskID, id string) (*PushConfigRecord, error)

func (*SQLStore) GetTask

func (s *SQLStore) GetTask(ctx context.Context, owner, id string) (*TaskRecord, error)

func (*SQLStore) ListPushConfigs

func (s *SQLStore) ListPushConfigs(ctx context.Context, owner, taskID string) ([]*PushConfigRecord, error)

func (*SQLStore) ListTasks

func (s *SQLStore) ListTasks(ctx context.Context, owner string, q ListQuery) ([]*TaskRecord, int, error)

func (*SQLStore) TrimPushConfigs added in v0.85.0

func (s *SQLStore) TrimPushConfigs(ctx context.Context, owner, taskID string, keep int) error

TrimPushConfigs implements RetentionTrimmer: it keeps the newest `keep` configs for (owner, taskID) — the order ListPushConfigs serves — and deletes the older ones.

func (*SQLStore) TrimTerminalTasks added in v0.85.0

func (s *SQLStore) TrimTerminalTasks(ctx context.Context, owner string, keep int) error

TrimTerminalTasks implements RetentionTrimmer: it keeps the newest `keep` terminal rows for owner and deletes the older ones. The id walk then delete (instead of one DELETE ... LIMIT statement) keeps both dialects happy; ids are deleted in bounded batches.

func (*SQLStore) UpdateTask

func (s *SQLStore) UpdateTask(ctx context.Context, rec *TaskRecord) error

UpdateTask persists rec only when its Version matches the stored row; on success rec.Version is the new stored version and UpdatedAt is refreshed. Zero rows updated means conflict or absence, distinguished by one follow-up select on that path only.

type SQLStoreOption

type SQLStoreOption func(*SQLStore)

SQLStoreOption configures NewSQLStore.

func WithTablePrefix

func WithTablePrefix(prefix string) SQLStoreOption

WithTablePrefix puts both tables behind a prefix (e.g. "tenant1_"), for schemas shared by several services.

type SendMessageConfiguration

type SendMessageConfiguration struct {
	AcceptedOutputModes        []string                `json:"acceptedOutputModes,omitempty"`
	TaskPushNotificationConfig *PushNotificationConfig `json:"taskPushNotificationConfig,omitempty"`
	HistoryLength              *int                    `json:"historyLength,omitempty"`
	ReturnImmediately          bool                    `json:"returnImmediately,omitempty"`
}

SendMessageConfiguration tunes one SendMessage call.

type SendMessageRequest

type SendMessageRequest struct {
	Tenant        string                    `json:"tenant,omitempty"`
	Message       *Message                  `json:"message"`
	Configuration *SendMessageConfiguration `json:"configuration,omitempty"`
	Metadata      map[string]any            `json:"metadata,omitempty"`
}

SendMessageRequest is the params object of SendMessage and SendStreamingMessage.

type SendMessageResponse

type SendMessageResponse struct {
	Task    *Task    `json:"task,omitempty"`
	Message *Message `json:"message,omitempty"`
}

SendMessageResponse is the non-streaming result: a task (the usual outcome) or, for an agent that answers without a task, a message.

type Server

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

Server is the A2A task-exchange HTTP handler. Construct with NewServer and mount behind the middleware that establishes the principal Config.Owner resolves.

func NewServer

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

NewServer validates cfg and applies its defaults.

func (*Server) Capabilities

func (s *Server) Capabilities() Capabilities

Capabilities is what the agent card copies: this server always streams, and push/extended card follow configuration.

func (*Server) ServeHTTP

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP answers one JSON-RPC request. POST only; the caller's Origin must be absent or name the request's own authority, the body is capped, the content type must be JSON, and the caller must resolve to an owner before any method runs.

func (*Server) SetAllowedOrigins added in v0.83.0

func (s *Server) SetAllowedOrigins(origins []string)

SetAllowedOrigins permits browser Origins that are not same-origin with the request, for servers constructed without going through Config.AllowedOrigins. Mirrors core/mcp's SetAllowedOrigins.

func (*Server) Skills

func (s *Server) Skills() []Skill

Skills returns a copy of the registered skills in registration order.

type Skill

type Skill struct {
	ID          string   `json:"id"`
	Name        string   `json:"name"`
	Description string   `json:"description"`
	Tags        []string `json:"tags"`
	Examples    []string `json:"examples,omitempty"`
	InputModes  []string `json:"inputModes,omitempty"`
	OutputModes []string `json:"outputModes,omitempty"`

	// Handler runs the skill for one message. Nil is a build error.
	Handler Handler `json:"-"`
}

Skill is one thing the agent can do, invoked by name. The descriptive fields are exactly the AgentSkill fields of the agent card (spec §8), so the card and the server list one set of skills from one source.

type Store

type Store interface {
	CreateTask(ctx context.Context, rec *TaskRecord) error
	// GetTask returns ErrNotFound for an unknown id OR another owner's task.
	GetTask(ctx context.Context, owner, id string) (*TaskRecord, error)
	// UpdateTask persists rec if rec.Version matches the stored row, then
	// increments rec.Version; otherwise ErrConflict.
	UpdateTask(ctx context.Context, rec *TaskRecord) error
	// ListTasks returns the owner's tasks matching q, newest status first,
	// plus the total match count before Limit/Offset.
	ListTasks(ctx context.Context, owner string, q ListQuery) (recs []*TaskRecord, total int, err error)

	CreatePushConfig(ctx context.Context, rec *PushConfigRecord) error
	GetPushConfig(ctx context.Context, owner, taskID, id string) (*PushConfigRecord, error)
	ListPushConfigs(ctx context.Context, owner, taskID string) ([]*PushConfigRecord, error)
	DeletePushConfig(ctx context.Context, owner, taskID, id string) error
}

Store persists tasks and push configs. Every read and write is scoped by owner: a task belongs to the principal that created it, and no call returns or touches another owner's rows. Implementations are safe for concurrent use and shared across replicas (the SQL store); the memory store is per process.

type StreamResponse

type StreamResponse struct {
	Task           *Task                    `json:"task,omitempty"`
	Message        *Message                 `json:"message,omitempty"`
	StatusUpdate   *TaskStatusUpdateEvent   `json:"statusUpdate,omitempty"`
	ArtifactUpdate *TaskArtifactUpdateEvent `json:"artifactUpdate,omitempty"`
}

StreamResponse is the streaming result and push-notification payload: an object carrying exactly one of task / message / statusUpdate / artifactUpdate. Construct with one field set; Validate rejects the rest.

func (StreamResponse) Validate

func (r StreamResponse) Validate() error

Validate enforces the exactly-one rule for StreamResponse.

type SubscribeToTaskRequest

type SubscribeToTaskRequest struct {
	Tenant string `json:"tenant,omitempty"`
	ID     string `json:"id"`
}

SubscribeToTaskRequest is the params object of SubscribeToTask.

type Task

type Task struct {
	ID        string         `json:"id"`
	ContextID string         `json:"contextId"`
	Status    TaskStatus     `json:"status"`
	Artifacts []Artifact     `json:"artifacts,omitempty"`
	History   []Message      `json:"history,omitempty"`
	Metadata  map[string]any `json:"metadata,omitempty"`
}

Task is the unit of work a client and an agent exchange messages about.

type TaskArtifactUpdateEvent

type TaskArtifactUpdateEvent struct {
	TaskID    string         `json:"taskId"`
	ContextID string         `json:"contextId"`
	Artifact  Artifact       `json:"artifact"`
	Append    bool           `json:"append,omitempty"`
	LastChunk bool           `json:"lastChunk,omitempty"`
	Metadata  map[string]any `json:"metadata,omitempty"`
}

TaskArtifactUpdateEvent announces a new or extended artifact.

type TaskContext

type TaskContext interface {
	// Task returns a snapshot of the task as currently persisted.
	Task() *Task
	// Message returns the message this run was started or resumed with.
	Message() *Message
	// Owner returns the principal that owns the task (Config.Owner's
	// value for the request that created it).
	Owner() string
	// Request returns the HTTP request that started or resumed this run,
	// for handlers that re-dispatch into the app with the caller's
	// credentials. Its Context is not the handler's context.
	Request() *http.Request

	// Working moves the task to TASK_STATE_WORKING with an optional agent
	// status message (nil parts: no message). Idempotent once working.
	Working(parts ...Part) error
	// Artifact records an artifact and emits an artifact-update event. An
	// artifact whose ArtifactID matches an existing one replaces it unless
	// append is set, in which case its parts are appended.
	Artifact(a Artifact, append bool) error
	// Complete ends the task in TASK_STATE_COMPLETED with an optional
	// final agent message.
	Complete(parts ...Part) error
	// Fail ends the task in TASK_STATE_FAILED.
	Fail(parts ...Part) error
	// Reject ends the task in TASK_STATE_REJECTED: the agent will not do
	// this work (unknown skill, bad input).
	Reject(parts ...Part) error
	// RequireInput pauses the task in TASK_STATE_INPUT_REQUIRED. The next
	// SendMessage addressed to the task resumes it: the handler runs again
	// with the new message and the task's history.
	RequireInput(parts ...Part) error
	// RequireAuth pauses the task in TASK_STATE_AUTH_REQUIRED, the same
	// way as RequireInput.
	RequireAuth(parts ...Part) error
}

TaskContext is a handler's view of, and control over, its task. Every mutating call persists the task through the Store and fans the corresponding event out to streaming subscribers and push configs before returning, so a crash after the call cannot lose what the client was already told.

type TaskRecord

type TaskRecord struct {
	Task    Task
	Owner   string
	SkillID string
	// Version is bumped on every UpdateTask; an update whose Version does
	// not match the stored row fails with ErrConflict.
	Version   int64
	CreatedAt time.Time
	UpdatedAt time.Time
}

TaskRecord is a task as the Store holds it: the wire Task plus the bookkeeping the server needs and never sends.

func (*TaskRecord) Clone

func (r *TaskRecord) Clone() *TaskRecord

Clone deep-copies the record: the Task via cloneTask, the scalars plainly.

type TaskState

type TaskState string

TaskState is the lifecycle state of a task. Values serialize as the proto enum names (ProtoJSON), e.g. "TASK_STATE_COMPLETED".

const (
	TaskStateUnspecified   TaskState = "TASK_STATE_UNSPECIFIED"
	TaskStateSubmitted     TaskState = "TASK_STATE_SUBMITTED"
	TaskStateWorking       TaskState = "TASK_STATE_WORKING"
	TaskStateCompleted     TaskState = "TASK_STATE_COMPLETED"
	TaskStateFailed        TaskState = "TASK_STATE_FAILED"
	TaskStateCanceled      TaskState = "TASK_STATE_CANCELED"
	TaskStateInputRequired TaskState = "TASK_STATE_INPUT_REQUIRED"
	TaskStateRejected      TaskState = "TASK_STATE_REJECTED"
	TaskStateAuthRequired  TaskState = "TASK_STATE_AUTH_REQUIRED"
)

func (TaskState) Interrupted

func (s TaskState) Interrupted() bool

Interrupted reports whether the task is paused waiting on the client: input or authentication. A message addressed to an interrupted task resumes it; a stream closes when a task enters one of these states.

func (TaskState) Terminal

func (s TaskState) Terminal() bool

Terminal reports whether the state ends a task: no further messages are accepted and no further updates are emitted.

func (TaskState) Valid

func (s TaskState) Valid() bool

Valid reports whether s is one of the nine proto enum values.

type TaskStatus

type TaskStatus struct {
	State     TaskState  `json:"state"`
	Message   *Message   `json:"message,omitempty"`
	Timestamp *Timestamp `json:"timestamp,omitempty"`
}

TaskStatus is the current state of a task plus the agent's most recent status message, if any.

type TaskStatusUpdateEvent

type TaskStatusUpdateEvent struct {
	TaskID    string         `json:"taskId"`
	ContextID string         `json:"contextId"`
	Status    TaskStatus     `json:"status"`
	Metadata  map[string]any `json:"metadata,omitempty"`
}

TaskStatusUpdateEvent announces a state change on a streaming connection or a push notification.

type Timestamp

type Timestamp struct{ time.Time }

Timestamp serializes as RFC 3339 in UTC with millisecond precision, the form the spec shows ("2025-10-28T14:25:33.142Z"). Decoding accepts any RFC 3339 string.

func Now

func Now() *Timestamp

Now returns the current time as a Timestamp, truncated to milliseconds so a value survives a JSON round trip byte-for-byte.

func (Timestamp) MarshalJSON

func (t Timestamp) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler.

func (*Timestamp) UnmarshalJSON

func (t *Timestamp) UnmarshalJSON(b []byte) error

UnmarshalJSON implements json.Unmarshaler.

Jump to

Keyboard shortcuts

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