Skip to content
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
90d93c6
make core types gob-serializable
yarolegovich Sep 5, 2025
a70892e
in-memory task store
yarolegovich Sep 5, 2025
043ba2e
task update logic
yarolegovich Sep 5, 2025
80d3853
concurrent task execution and cancelation management
yarolegovich Sep 5, 2025
14da9f8
taskexec integration with default request handler
yarolegovich Sep 5, 2025
0df268e
prevent possibility of failed cancelation destroying the queue which …
yarolegovich Sep 8, 2025
67290a0
comments and t.Helper() calls
yarolegovich Sep 12, 2025
efd6922
Merge branch 'main' into yarolegovich/result-aggregation-3
yarolegovich Sep 16, 2025
ef1170a
lint
yarolegovich Sep 16, 2025
a1971d9
Merge branch 'main' into yarolegovich/result-aggregation-3
yarolegovich Sep 22, 2025
601aeda
Merge branch 'yarolegovich/result-aggregation-4' into yarolegovich/re…
yarolegovich Sep 22, 2025
789dfb2
artifact update logic
yarolegovich Sep 22, 2025
9e28fb2
OnSendMessageStream() and tests
yarolegovich Sep 22, 2025
2a84c8b
test and fix
yarolegovich Sep 22, 2025
2fa71f4
PR review improvements
yarolegovich Sep 22, 2025
eeba7ff
Merge branch 'main' into yarolegovich/result-aggregation-4
yarolegovich Oct 2, 2025
c5c9812
fix blocking on nil channel and empty yield(nil, nil) in defer
yarolegovich Oct 2, 2025
c7251a7
lint
yarolegovich Oct 2, 2025
d7fdefe
Merge branch 'yarolegovich/result-aggregation-4' into yarolegovich/ar…
yarolegovich Oct 3, 2025
60944c4
pass RequestContext by reference
yarolegovich Oct 3, 2025
e87b83b
add missing validations, move request context loading into a protecte…
yarolegovich Oct 3, 2025
5910152
added request context interceptor
yarolegovich Oct 3, 2025
f5ec037
fix race
yarolegovich Oct 3, 2025
f853184
interceptor tests
yarolegovich Oct 6, 2025
301d555
fix producer errors not reported
yarolegovich Oct 6, 2025
dc4eb45
make handler tests exercise real queues
yarolegovich Oct 6, 2025
d629498
comment clarification
yarolegovich Oct 6, 2025
c5c795a
fix error masking
yarolegovich Oct 6, 2025
8dbd52b
Merge branch 'main' into yarolegovich/artifacts
yarolegovich Oct 8, 2025
12bb659
refactor messages
yarolegovich Oct 8, 2025
bb87035
Merge branch 'yarolegovich/artifacts' into yarolegovich/req-ctx-loading
yarolegovich Oct 8, 2025
6c17632
refactor test messages
yarolegovich Oct 8, 2025
c6bbb24
server middleware API
yarolegovich Oct 9, 2025
1e83bdc
fix interceptor ordering
yarolegovich Oct 9, 2025
66d0ee9
licenses
yarolegovich Oct 9, 2025
fed7ce6
Merge branch 'main' into yarolegovich/call-ctx-api
yarolegovich Oct 24, 2025
054700f
Merge branch 'main' into yarolegovich/call-ctx-api
yarolegovich Oct 28, 2025
2b71078
fix after merge
yarolegovich Oct 28, 2025
7f2f031
fix grammar
yarolegovich Oct 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions a2a/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ func (*Message) isSendMessageResult() {}
// Event interface is used to represent types that can be sent over a streaming connection.
type Event interface {
isEvent()

Meta() map[string]any
}

func (*Message) isEvent() {}
Expand All @@ -55,6 +57,8 @@ func NewMessageID() string {
return uuid.NewString()
}

var _ Event = (*Message)(nil)

// Message represents a single message in the conversation between a user and an agent.
type Message struct {
// ID is a unique identifier for the message, typically a UUID, generated by the sender.
Expand Down Expand Up @@ -106,6 +110,10 @@ func NewMessageForTask(role MessageRole, task *Task, parts ...Part) *Message {
}
}

func (m *Message) Meta() map[string]any {
return m.Metadata
}

// TaskID is a unique identifier for the task, generated by the server for a new task.
type TaskID string

Expand Down Expand Up @@ -144,6 +152,8 @@ func (ts TaskState) Terminal() bool {
ts == TaskStateRejected
}

var _ Event = (*Task)(nil)

// Task represents a single, stateful operation or conversation between a client and an agent.
type Task struct {
// ID is a unique identifier for the task, generated by the server for a new task.
Expand Down Expand Up @@ -178,6 +188,10 @@ type TaskStatus struct {
Timestamp *time.Time `json:"timestamp,omitempty" yaml:"timestamp,omitempty" mapstructure:"timestamp,omitempty"`
}

func (m *Task) Meta() map[string]any {
return m.Metadata
}

// ArtifactID is a unique identifier for the artifact within the scope of the task.
type ArtifactID string

Expand Down Expand Up @@ -207,6 +221,8 @@ type Artifact struct {
Parts ContentParts `json:"parts" yaml:"parts" mapstructure:"parts"`
}

var _ Event = (*TaskArtifactUpdateEvent)(nil)

// TaskArtifactUpdateEvent is an event sent by the agent to notify the client that an artifact has been
// generated or updated. This is typically used in streaming models.
type TaskArtifactUpdateEvent struct {
Expand All @@ -230,6 +246,10 @@ type TaskArtifactUpdateEvent struct {
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty" mapstructure:"metadata,omitempty"`
}

func (a *TaskArtifactUpdateEvent) Meta() map[string]any {
return a.Metadata
}

// NewArtifactEvent create a TaskArtifactUpdateEvent for an Artifact with a random ID.
func NewArtifactEvent(task *Task, parts ...Part) *TaskArtifactUpdateEvent {
return &TaskArtifactUpdateEvent{
Expand All @@ -255,6 +275,8 @@ func NewArtifactUpdateEvent(task *Task, id ArtifactID, parts ...Part) *TaskArtif
}
}

var _ Event = (*TaskStatusUpdateEvent)(nil)

// TaskStatusUpdateEvent is an event sent by the agent to notify the client of a change in a task's status.
// This is typically used in streaming or subscription models.
type TaskStatusUpdateEvent struct {
Expand Down Expand Up @@ -288,6 +310,10 @@ func NewStatusUpdateEvent(task *Task, state TaskState, msg *Message) *TaskStatus
}
}

func (a *TaskStatusUpdateEvent) Meta() map[string]any {
return a.Metadata
}

// ContentParts is an array of content parts that form the message body or an artifact.
type ContentParts []Part

Expand Down
46 changes: 46 additions & 0 deletions a2asrv/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2025 The A2A Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package a2asrv

// User can be attached to call context by authentication middleware.
type User interface {
// Name returns a username.
Name() string
// Authenticated returns true if requested was authenticated.
Comment thread
yarolegovich marked this conversation as resolved.
Outdated
Authenticated() bool
}

// AuthenticatedUser is a simple implementation of User interface which can be configured with a username.
type AuthenticatedUser struct {
UserName string
}

func (u *AuthenticatedUser) Name() string {
return u.UserName
}

func (u *AuthenticatedUser) Authenticated() bool {
return true
}

type unauthenticatedUser struct{}

func (unauthenticatedUser) Name() string {
return ""
}

func (unauthenticatedUser) Authenticated() bool {
return false
}
72 changes: 72 additions & 0 deletions a2asrv/extensions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2025 The A2A Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package a2asrv

import (
"context"
"slices"

"github.com/a2aproject/a2a-go/a2a"
)

const ExtensionsMetaKey = "X-A2A-Extensions"

// Extensions provides utility methods for accessing extensions requested by the client and keeping track of extensions
// activated during request processing.
type Extensions struct {
callCtx *CallContext
}

// ExtensionsFrom is a helper function for quick access to Extensions in the current CallContext.
func ExtensionsFrom(ctx context.Context) (*Extensions, bool) {
serverCallCtx, ok := CallContextFrom(ctx)
if !ok {
return nil, false
}
return serverCallCtx.Extensions(), true
}

// Active returns true if an extension has already been activated in the current CallContext using ExtensionContext.Activate.
func (e *Extensions) Active(extension *a2a.AgentExtension) bool {
return slices.Contains(e.callCtx.activatedExtensions, extension.URI)
}

// Activate marks extension as activated in the current CallContext. A list of activated extensions might be attached as
// response metadata by a transport implementation.
func (e *Extensions) Activate(extension *a2a.AgentExtension) {
if e.Active(extension) {
return
}
e.callCtx.activatedExtensions = append(e.callCtx.activatedExtensions, extension.URI)
}

// ActivatedURIs returns all URIs activated during call execution.
func (e *Extensions) ActivatedURIs() []string {
return slices.Clone(e.callCtx.activatedExtensions)
}

// Requested returns true if the provided extension was requested by the client.
func (e *Extensions) Requested(extension *a2a.AgentExtension) bool {
return slices.Contains(e.RequestedURIs(), extension.URI)
}

// RequestedURIs returns all URIs of extensions requested by the client.
func (e *Extensions) RequestedURIs() []string {
requested, ok := e.callCtx.RequestMeta().Get(ExtensionsMetaKey)
if !ok {
return []string{}
}
return slices.Clone(requested)
}
Loading