diff --git a/a2a/agent.go b/a2a/agent.go new file mode 100644 index 00000000..425aa1f5 --- /dev/null +++ b/a2a/agent.go @@ -0,0 +1,222 @@ +// 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 a2a + +// Defines optional capabilities supported by an agent. +type AgentCapabilities struct { + // A list of protocol extensions supported by the agent. + Extensions []AgentExtension + + // Indicates if the agent supports sending push notifications for asynchronous + // task updates. + PushNotifications *bool + + // Indicates if the agent provides a history of state transitions for a task. + StateTransitionHistory *bool + + // Indicates if the agent supports Server-Sent Events (SSE) for streaming + // responses. + Streaming *bool +} + +// The AgentCard is a self-describing manifest for an agent. It provides essential +// metadata including the agent's identity, capabilities, skills, supported +// communication methods, and security requirements. +type AgentCard struct { + // A list of additional supported interfaces (transport and URL combinations). + // This allows agents to expose multiple transports, potentially at different + // URLs. + // + // Best practices: + // - SHOULD include all supported transports for completeness + // - SHOULD include an entry matching the main 'url' and 'preferredTransport' + // - MAY reuse URLs if multiple transports are available at the same endpoint + // - MUST accurately declare the transport available at each URL + // + // Clients can select any interface from this list based on their transport + // capabilities + // and preferences. This enables transport negotiation and fallback scenarios. + AdditionalInterfaces []AgentInterface + + // A declaration of optional capabilities supported by the agent. + Capabilities AgentCapabilities + + // Default set of supported input MIME types for all skills, which can be + // overridden on a per-skill basis. + DefaultInputModes []string + + // Default set of supported output MIME types for all skills, which can be + // overridden on a per-skill basis. + DefaultOutputModes []string + + // A human-readable description of the agent, assisting users and other agents + // in understanding its purpose. + Description string + + // An optional URL to the agent's documentation. + DocumentationURL *string + + // An optional URL to an icon for the agent. + IconURL *string + + // A human-readable name for the agent. + Name string + + // The transport protocol for the preferred endpoint (the main 'url' field). + // If not specified, defaults to 'JSONRPC'. + // + // IMPORTANT: The transport specified here MUST be available at the main 'url'. + // This creates a binding between the main URL and its supported transport + // protocol. + // Clients should prefer this transport and URL combination when both are + // supported. + PreferredTransport TransportProtocol + + // The version of the A2A protocol this agent supports. + ProtocolVersion string + + // Information about the agent's service provider. + Provider *AgentProvider + + // A list of security requirement objects that apply to all agent interactions. + // Each object + // lists security schemes that can be used. Follows the OpenAPI 3.0 Security + // Requirement Object. + // This list can be seen as an OR of ANDs. Each object in the list describes one + // possible + // set of security requirements that must be present on a request. This allows + // specifying, + // for example, "callers must either use OAuth OR an API Key AND mTLS." + Security []map[string][]string + + // A declaration of the security schemes available to authorize requests. The key + // is the + // scheme name. Follows the OpenAPI 3.0 Security Scheme Object. + SecuritySchemes map[string]any + + // JSON Web Signatures computed for this AgentCard. + Signatures []AgentCardSignature + + // The set of skills, or distinct capabilities, that the agent can perform. + Skills []AgentSkill + + // If true, the agent can provide an extended agent card with additional details + // to authenticated users. Defaults to false. + SupportsAuthenticatedExtendedCard *bool + + // The preferred endpoint URL for interacting with the agent. + // This URL MUST support the transport specified by 'preferredTransport'. + URL string + + // The agent's own version number. The format is defined by the provider. + Version string +} + +// AgentCardSignature represents a JWS signature of an AgentCard. +// This follows the JSON format of an RFC 7515 JSON Web Signature (JWS). +type AgentCardSignature struct { + // The unprotected JWS header values. + Header map[string]any + + // The protected JWS header for the signature. This is a Base64url-encoded + // JSON object, as per RFC 7515. + Protected string + + // The computed signature, Base64url-encoded. + Signature string +} + +// A declaration of a protocol extension supported by an Agent. +type AgentExtension struct { + // A human-readable description of how this agent uses the extension. + Description *string + + // Optional, extension-specific configuration parameters. + Params map[string]any + + // If true, the client must understand and comply with the extension's + // requirements + // to interact with the agent. + Required *bool + + // The unique URI identifying the extension. + URI string +} + +// Declares a combination of a target URL and a transport protocol for interacting +// with the agent. +// This allows agents to expose the same functionality over multiple transport +// mechanisms. +type AgentInterface struct { + // The transport protocol supported at this URL. + Transport string + + // The URL where this interface is available. Must be a valid absolute HTTPS URL + // in production. + URL string +} + +// Represents the service provider of an agent. +type AgentProvider struct { + // The name of the agent provider's organization. + Org string + + // A URL for the agent provider's website or relevant documentation. + URL string +} + +// Represents a distinct capability or function that an agent can perform. +type AgentSkill struct { + // A detailed description of the skill, intended to help clients or users + // understand its purpose and functionality. + Description string + + // Example prompts or scenarios that this skill can handle. Provides a hint to + // the client on how to use the skill. + Examples []string + + // A unique identifier for the agent's skill. + ID string + + // The set of supported input MIME types for this skill, overriding the agent's + // defaults. + InputModes []string + + // A human-readable name for the skill. + Name string + + // The set of supported output MIME types for this skill, overriding the agent's + // defaults. + OutputModes []string + + // Security schemes necessary for the agent to leverage this skill. + // As in the overall AgentCard.security, this list represents a logical OR of + // security + // requirement objects. Each object is a set of security schemes that must be used + // together + // (a logical AND). + Security []map[string][]string + + // A set of keywords describing the skill's capabilities. + Tags []string +} + +type TransportProtocol string + +const ( + TransportProtocolJSONRPC TransportProtocol = "JSONRPC" + TransportProtocolGRPC TransportProtocol = "GRPC" + TransportProtocolHTTPJSON TransportProtocol = "HTTP+JSON" +) diff --git a/a2a/auth.go b/a2a/auth.go new file mode 100644 index 00000000..90364bee --- /dev/null +++ b/a2a/auth.go @@ -0,0 +1,183 @@ +// 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 a2a + +// Defines a security scheme that can be used to secure an agent's endpoints. +// This is a discriminated union type based on the OpenAPI 3.0 Security Scheme. +type SecurityScheme interface { + isSecurityScheme() +} + +func (APIKeySecurityScheme) isSecurityScheme() {} +func (HTTPAuthSecurityScheme) isSecurityScheme() {} +func (OpenIDConnectSecurityScheme) isSecurityScheme() {} +func (MutualTLSSecurityScheme) isSecurityScheme() {} +func (OAuth2SecurityScheme) isSecurityScheme() {} + +// Defines a security scheme using an API key. +type APIKeySecurityScheme struct { + // An optional description for the security scheme. + Description *string + + // The location of the API key. + In APIKeySecuritySchemeIn + + // The name of the header, query, or cookie parameter to be used. + Name string + + // The type of the security scheme. Must be 'apiKey'. + Type string +} + +type APIKeySecuritySchemeIn string + +const ( + APIKeySecuritySchemeInCookie APIKeySecuritySchemeIn = "cookie" + APIKeySecuritySchemeInHeader APIKeySecuritySchemeIn = "header" + APIKeySecuritySchemeInQuery APIKeySecuritySchemeIn = "query" +) + +// Defines configuration details for the OAuth 2.0 Authorization Code flow. +type AuthzCodeOAuthFlow struct { + // The authorization URL to be used for this flow. + // This MUST be a URL and use TLS. + AuthzURL string + + // The URL to be used for obtaining refresh tokens. + // This MUST be a URL and use TLS. + RefreshURL *string + + // The available scopes for the OAuth2 security scheme. A map between the scope + // name and a short description for it. + Scopes map[string]string + + // The token URL to be used for this flow. + // This MUST be a URL and use TLS. + TokenURL string +} + +// Defines configuration details for the OAuth 2.0 Client Credentials flow. +type ClientCredentialsOAuthFlow struct { + // The URL to be used for obtaining refresh tokens. This MUST be a URL. + RefreshURL *string + + // The available scopes for the OAuth2 security scheme. A map between the scope + // name and a short description for it. + Scopes map[string]string + + // The token URL to be used for this flow. This MUST be a URL. + TokenURL string +} + +// Defines a security scheme using HTTP authentication. +type HTTPAuthSecurityScheme struct { + // A hint to the client to identify how the bearer token is formatted (e.g., + // "JWT"). + // This is primarily for documentation purposes. + BearerFormat *string + + // An optional description for the security scheme. + Description *string + + // The name of the HTTP Authentication scheme to be used in the Authorization + // header, + // as defined in RFC7235 (e.g., "Bearer"). + // This value should be registered in the IANA Authentication Scheme registry. + Scheme string + + // The type of the security scheme. Must be 'http'. + Type string +} + +// Defines configuration details for the OAuth 2.0 Implicit flow. +type ImplicitOAuthFlow struct { + // The authorization URL to be used for this flow. This MUST be a URL. + AuthzURL string + + // The URL to be used for obtaining refresh tokens. This MUST be a URL. + RefreshURL *string + + // The available scopes for the OAuth2 security scheme. A map between the scope + // name and a short description for it. + Scopes map[string]string +} + +// Defines a security scheme using mTLS authentication. +type MutualTLSSecurityScheme struct { + // An optional description for the security scheme. + Description *string + + // The type of the security scheme. Must be 'mutualTLS'. + Type string +} + +// Defines a security scheme using OAuth 2.0. +type OAuth2SecurityScheme struct { + // An optional description for the security scheme. + Description *string + + // An object containing configuration information for the supported OAuth 2.0 + // flows. + Flows OAuthFlows + + // URL to the oauth2 authorization server metadata + // [RFC8414](https://datatracker.ietf.org/doc/html/rfc8414). TLS is required. + Oauth2MetadataURL *string + + // The type of the security scheme. Must be 'oauth2'. + Type string +} + +// Defines the configuration for the supported OAuth 2.0 flows. +type OAuthFlows struct { + // Configuration for the OAuth Authorization Code flow. Previously called + // accessCode in OpenAPI 2.0. + AuthzCode *AuthzCodeOAuthFlow + + // Configuration for the OAuth Client Credentials flow. Previously called + // application in OpenAPI 2.0. + ClientCredentials *ClientCredentialsOAuthFlow + + // Configuration for the OAuth Implicit flow. + Implicit *ImplicitOAuthFlow + + // Configuration for the OAuth Resource Owner Password flow. + Password *PasswordOAuthFlow +} + +// Defines a security scheme using OpenID Connect. +type OpenIDConnectSecurityScheme struct { + // An optional description for the security scheme. + Description *string + + // The OpenID Connect Discovery URL for the OIDC provider's metadata. + OpenIDConnectURL string + + // The type of the security scheme. Must be 'openIDConnect'. + Type string +} + +// Defines configuration details for the OAuth 2.0 Resource Owner Password flow. +type PasswordOAuthFlow struct { + // The URL to be used for obtaining refresh tokens. This MUST be a URL. + RefreshURL *string + + // The available scopes for the OAuth2 security scheme. A map between the scope + // name and a short description for it. + Scopes map[string]string + + // The token URL to be used for this flow. This MUST be a URL. + TokenURL string +} diff --git a/a2a/core.go b/a2a/core.go new file mode 100644 index 00000000..c6664a94 --- /dev/null +++ b/a2a/core.go @@ -0,0 +1,343 @@ +// 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 a2a + +import "time" + +// Represents a response for non-streaming message send. +type SendMessageResult interface { + isSendMessageResult() +} + +func (Task) isSendMessageResult() {} +func (Message) isSendMessageResult() {} + +// Event interface is used to represent types that can be sent over a streaming connection. +type Event interface { + isEvent() +} + +func (t Message) isEvent() {} +func (t Task) isEvent() {} +func (t TaskStatusUpdateEvent) isEvent() {} +func (t TaskArtifactUpdateEvent) isEvent() {} + +type MessageRole string + +const ( + MessageRoleAgent MessageRole = "agent" + MessageRoleUser MessageRole = "user" +) + +// Represents a single message in the conversation between a user and an agent. +type Message struct { + // The context identifier for this message, used to group related interactions. + ContextID *string + + // The URIs of extensions that are relevant to this message. + Extensions []string + + // The type of this object, used as a discriminator. Always 'message' for a + // Message. + Kind string + + // A unique identifier for the message, typically a UUID, generated by the sender. + MessageID string + + // Optional metadata for extensions. The key is an extension-specific identifier. + Metadata map[string]any + + // An array of content parts that form the message body. A message can be + // composed of multiple parts of different types (e.g., text and files). + Parts []Part + + // A list of other task IDs that this message references for additional context. + ReferenceTasks []TaskID + + // Identifies the sender of the message. + // service. + Role MessageRole + + // The identifier of the task this message is part of. Can be omitted for the + // first message of a new task. + TaskID *TaskID +} + +type TaskID string + +type TaskState string + +const ( + TaskStateAuthRequired TaskState = "auth-required" + TaskStateCanceled TaskState = "canceled" + TaskStateCompleted TaskState = "completed" + TaskStateFailed TaskState = "failed" + TaskStateInputRequired TaskState = "input-required" + TaskStateRejected TaskState = "rejected" + TaskStateSubmitted TaskState = "submitted" + TaskStateUnknown TaskState = "unknown" + TaskStateWorking TaskState = "working" +) + +// Represents a single, stateful operation or conversation between a client and an +// agent. +type Task struct { + // A collection of artifacts generated by the agent during the execution of the + // task. + Artifacts []Artifact + + // A server-generated identifier for maintaining context across multiple related + // tasks or interactions. + ContextID string + + // An array of messages exchanged during the task, representing the conversation + // history. + History []Message + + // A unique identifier for the task, generated by the server for a new task. + ID TaskID + + // The type of this object, used as a discriminator. Always 'task' for a Task. + Kind string + + // Optional metadata for extensions. The key is an extension-specific identifier. + Metadata map[string]any + + // The current status of the task, including its state and a descriptive message. + Status TaskStatus +} + +// Represents the status of a task at a specific point in time. +type TaskStatus struct { + // An optional, human-readable message providing more details about the current + // status. + Message *Message + + // The current state of the task's lifecycle. + State TaskState + + // A datetime indicating when this status was recorded. + Timestamp *time.Time +} + +// Represents a file, data structure, or other resource generated by an agent +// during a task. +type Artifact struct { + // A unique identifier for the artifact within the scope of the task. + ArtifactID string + + // An optional, human-readable description of the artifact. + Description *string + + // The URIs of extensions that are relevant to this artifact. + Extensions []string + + // Optional metadata for extensions. The key is an extension-specific identifier. + Metadata map[string]any + + // An optional, human-readable name for the artifact. + Name *string + + // An array of content parts that make up the artifact. + Parts []Part +} + +// 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 { + // If true, the content of this artifact should be appended to a previously sent + // artifact with the same ID. + Append *bool + + // The artifact that was generated or updated. + Artifact Artifact + + // The context ID associated with the task. + ContextID string + + // The type of this event, used as a discriminator. Always 'artifact-update'. + Kind string + + // If true, this is the final chunk of the artifact. + LastChunk *bool + + // The ID of the task this artifact belongs to. + TaskID TaskID + + // Optional metadata for extensions. + Metadata map[string]any +} + +// 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 { + // The context ID associated with the task. + ContextID string + + // If true, this is the final event in the stream for this interaction. + Final bool + + // The type of this event, used as a discriminator. Always 'status-update'. + Kind string + + // The new status of the task. + Status TaskStatus + + // The ID of the task that was updated. + TaskID TaskID + + // Optional metadata for extensions. + Metadata map[string]any +} + +// A discriminated union representing a part of a message or artifact, which can +// be text, a file, or structured data. +type Part interface { + isPart() +} + +func (TextPart) isPart() {} +func (FilePart) isPart() {} +func (DataPart) isPart() {} + +// Represents a text segment within a message or artifact. +type TextPart struct { + // The type of this part, used as a discriminator. Always 'text'. + Kind string + + // The string content of the text part. + Text string + + // Optional metadata associated with this part. + Metadata map[string]any +} + +// Represents a structured data segment (e.g., JSON) within a message or artifact. +type DataPart struct { + // The structured data content. + Data map[string]any + + // The type of this part, used as a discriminator. Always 'data'. + Kind string + + // Optional metadata associated with this part. + Metadata map[string]any +} + +// Represents a file segment within a message or artifact. The file content can be +// provided either directly as bytes or as a URI. +type FilePart struct { + // The file content, represented as either a URI or as base64-encoded bytes. + File FilePartFile + + // The type of this part, used as a discriminator. Always 'file'. + Kind string + + // Optional metadata associated with this part. + Metadata map[string]any +} + +// Represents a file with its content provided directly as a base64-encoded string. +type FilePartFile struct { + // The base64-encoded content of the file. + Bytes string + + // The MIME type of the file (e.g., "application/pdf"). + MimeType *string + + // An optional name for the file (e.g., "document.pdf"). + Name *string + + // A URL pointing to the file's content. + URI string +} + +// Represents a file with its content provided directly as a base64-encoded string. +type FileWithBytes struct { + // The base64-encoded content of the file. + Bytes string + + // The MIME type of the file (e.g., "application/pdf"). + MimeType *string + + // An optional name for the file (e.g., "document.pdf"). + Name *string +} + +// Represents a file with its content located at a specific URI. +type FileWithURI struct { + // The MIME type of the file (e.g., "application/pdf"). + MimeType *string + + // An optional name for the file (e.g., "document.pdf"). + Name *string + + // A URL pointing to the file's content. + URI string +} + +// Requests + +// Defines parameters containing a task ID, used for simple task operations. +type TaskIDParams struct { + // The unique identifier of the task. + ID TaskID + + // Optional metadata associated with the request. + Metadata map[string]any +} + +// Defines parameters for querying a task, with an option to limit history length. +type TaskQueryParams struct { + // The number of most recent messages from the task's history to retrieve. + HistoryLength *int + + // The unique identifier of the task. + ID TaskID + + // Optional metadata associated with the request. + Metadata map[string]any +} + +// Defines configuration options for a `message/send` or `message/stream` request. +type MessageSendConfig struct { + // A list of output MIME types the client is prepared to accept in the response. + AcceptedOutputModes []string + + // If true, the client will wait for the task to complete. The server may reject + // this if the task is long-running. + Blocking *bool + + // The number of most recent messages from the task's history to retrieve in the + // response. + HistoryLength *int + + // Configuration for the agent to send push notifications for updates after the + // initial response. + PushConfig *PushConfig +} + +// Defines the parameters for a request to send a message to an agent. This can be used +// to create a new task, continue an existing one, or restart a task. +type MessageSendParams struct { + // Optional configuration for the send request. + Config *MessageSendConfig + + // The message object being sent to the agent. + Message Message + + // Optional metadata for extensions. + Metadata map[string]any +} diff --git a/a2a/doc.go b/a2a/doc.go new file mode 100644 index 00000000..5642398a --- /dev/null +++ b/a2a/doc.go @@ -0,0 +1,19 @@ +// 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 a2a contains core types and constants from the A2A protocol +// shared by client and server implementations. +// +// These types implement the A2A specification and are transport-agnostic. +package a2a diff --git a/a2a/errors.go b/a2a/errors.go new file mode 100644 index 00000000..67056b35 --- /dev/null +++ b/a2a/errors.go @@ -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 a2a + +import "errors" + +var ( + // ErrTaskNotFound indicates that a task with the provided ID was not found. + ErrTaskNotFound = errors.New("task not found") + + // ErrTaskNotCancelable indicates that the task was in a state where it could not be cancelled. + ErrTaskNotCancelable = errors.New("task cannot be canceled") + + // ErrPushNotificationNotSupported indicates that the agent does not support push notifications. + ErrPushNotificationNotSupported = errors.New("push notification not supported") + + // ErrUnsupportedOperation indicates that the requested operation is not supported by the agent. + ErrUnsupportedOperation = errors.New("this operation is not supported") + + // ErrUnsupportedContentType indicates an incompatibility between the requested + // content types and the agent's capabilities. + ErrUnsupportedContentType = errors.New("incompatible content types") + + // ErrInvalidAgentResponse indicates that the agent returned a response that + // does not conform to the specification for the current method. + ErrInvalidAgentResponse = errors.New("invalid agent response") + + // ErrInvalidRequest indicates that the received request was invalid. + ErrInvalidRequest = errors.New("invalid request") + + // ErrAuthenticatedExtendedCardNotConfigured indicates that the agent does not have an Authenticated + // Extended Card configured. + ErrAuthenticatedExtendedCardNotConfigured = errors.New("extended card not configured") +) diff --git a/a2a/push.go b/a2a/push.go new file mode 100644 index 00000000..4528fa77 --- /dev/null +++ b/a2a/push.go @@ -0,0 +1,75 @@ +// 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 a2a + +// Defines parameters for fetching a specific push notification configuration for a task. +type GetTaskPushConfigParams struct { + // The unique identifier of the task. + TaskID TaskID + + // The ID of the push notification configuration to retrieve. + ConfigID *string +} + +// Defines parameters for listing all push notification configurations associated with a task. +type ListTaskPushConfigParams struct { + // The unique identifier of the task. + TaskID TaskID +} + +// Defines parameters for deleting a specific push notification configuration for a task. +type DeleteTaskPushConfigParams struct { + // The unique identifier of the task. + TaskID TaskID + + // The ID of the push notification configuration to delete. + ConfigID string +} + +// A container associating a push notification configuration with a specific task. +type TaskPushConfig struct { + // The push notification configuration for this task. + Config PushConfig + + // The ID of the task. + TaskID TaskID +} + +// Defines the configuration for setting up push notifications for task updates. +type PushConfig struct { + // Optional authentication details for the agent to use when calling the + // notification URL. + Auth *PushAuthInfo + + // A unique ID for the push notification configuration, set by the client + // to support multiple notification callbacks. + ID *string + + // A unique token for this task or session to validate incoming push + // notifications. + Token *string + + // The callback URL where the agent should send push notifications. + URL string +} + +// Defines authentication details for a push notification endpoint. +type PushAuthInfo struct { + // Optional credentials required by the push notification endpoint. + Credentials *string + + // A list of supported authentication schemes (e.g., 'Basic', 'Bearer'). + Schemes []string +} diff --git a/grpc/a2a.pb.go b/a2apb/a2a.pb.go similarity index 99% rename from grpc/a2a.pb.go rename to a2apb/a2a.pb.go index 8dc9e385..1b6d7658 100644 --- a/grpc/a2a.pb.go +++ b/a2apb/a2a.pb.go @@ -6,7 +6,7 @@ // protoc (unknown) // source: a2a.proto -package grpc +package a2apb import ( _ "google.golang.org/genproto/googleapis/api/annotations" @@ -3579,9 +3579,9 @@ const file_a2a_proto_rawDesc = "" + "\x1eListTaskPushNotificationConfig\x12-.a2a.v1.ListTaskPushNotificationConfigRequest\x1a..a2a.v1.ListTaskPushNotificationConfigResponse\"=\xdaA\x06parent\x82\xd3\xe4\x93\x02.\x12,/v1/{parent=tasks/*}/pushNotificationConfigs\x12P\n" + "\fGetAgentCard\x12\x1b.a2a.v1.GetAgentCardRequest\x1a\x11.a2a.v1.AgentCard\"\x10\x82\xd3\xe4\x93\x02\n" + "\x12\b/v1/card\x12\xa8\x01\n" + - " DeleteTaskPushNotificationConfig\x12/.a2a.v1.DeleteTaskPushNotificationConfigRequest\x1a\x16.google.protobuf.Empty\";\xdaA\x04name\x82\xd3\xe4\x93\x02.*,/v1/{name=tasks/*/pushNotificationConfigs/*}Bo\n" + + " DeleteTaskPushNotificationConfig\x12/.a2a.v1.DeleteTaskPushNotificationConfigRequest\x1a\x16.google.protobuf.Empty\";\xdaA\x04name\x82\xd3\xe4\x93\x02.*,/v1/{name=tasks/*/pushNotificationConfigs/*}Bs\n" + "\n" + - "com.a2a.v1B\bA2aProtoP\x01Z\x1egithub.com/a2aproject/a2a/grpc\xa2\x02\x03AXX\xaa\x02\x06A2a.V1\xca\x02\x06A2a\\V1\xe2\x02\x12A2a\\V1\\GPBMetadata\xea\x02\aA2a::V1b\x06proto3" + "com.a2a.v1B\bA2aProtoP\x01Z\"github.com/a2aproject/a2a-go/a2apb\xa2\x02\x03AXX\xaa\x02\x06A2a.V1\xca\x02\x06A2a\\V1\xe2\x02\x12A2a\\V1\\GPBMetadata\xea\x02\aA2a::V1b\x06proto3" var ( file_a2a_proto_rawDescOnce sync.Once diff --git a/grpc/a2a_grpc.pb.go b/a2apb/a2a_grpc.pb.go similarity index 99% rename from grpc/a2a_grpc.pb.go rename to a2apb/a2a_grpc.pb.go index b1695cfb..09b461ac 100644 --- a/grpc/a2a_grpc.pb.go +++ b/a2apb/a2a_grpc.pb.go @@ -6,7 +6,7 @@ // - protoc (unknown) // source: a2a.proto -package grpc +package a2apb import ( context "context" diff --git a/a2apb/doc.go b/a2apb/doc.go new file mode 100644 index 00000000..2e3e8365 --- /dev/null +++ b/a2apb/doc.go @@ -0,0 +1,17 @@ +// 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 a2apb contains types and interfaces generated from published +// official A2A .proto specification. +package a2apb diff --git a/a2asrv/agent.go b/a2asrv/agent.go new file mode 100644 index 00000000..d3b0a3f1 --- /dev/null +++ b/a2asrv/agent.go @@ -0,0 +1,55 @@ +// 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" + + "github.com/a2aproject/a2a-go/a2a" +) + +// AgentExecutor implementations translate agent outputs to A2A events. +type AgentExecutor interface { + // Execute invokes an agent with the provided context and translates agent outputs + // into A2A events writing them to the provided event queue. + // + // Returns an error if agent invocation failed. + Execute(ctx context.Context, reqCtx RequestContext, queue EventWriter) error + + // Cancel requests the agent to stop processing an ongoing task. + // + // The agent should attempt to gracefully stop the task identified by the + // task ID in the request context and publish a TaskStatusUpdateEvent with + // state TaskStateCanceled to the event queue. + // + // Returns an error if the cancellation request cannot be processed. + Cancel(ctx context.Context, reqCtx RequestContext, queue EventWriter) error +} + +// AgentCardProducer creates an AgentCard instances used for agent discovery and capability negotiation. +type AgentCardProducer interface { + // Card returns a self-describing manifest for an agent. It provides essential + // metadata including the agent's identity, capabilities, skills, supported + // communication methods, and security requirements and is publicly available. + Card() a2a.AgentCard +} + +// ExtendedAgentCardProducer can create both public agent cards and cards available to authenticated users only. +type ExtendedAgentCardProducer interface { + AgentCardProducer + + // ExtendedCard returns a manifest for an agent which is only available to authenticated users. + ExtendedCard() a2a.AgentCard +} diff --git a/a2asrv/doc.go b/a2asrv/doc.go new file mode 100644 index 00000000..ec6b06dd --- /dev/null +++ b/a2asrv/doc.go @@ -0,0 +1,20 @@ +// 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 provides a configurable A2A protocol server implementation. +// +// The server can be configured with custom implementations of core interfaces +// like TaskStore, AgentExecutor, and PushNotifier to support different +// deployment scenarios and business requirements. +package a2asrv diff --git a/a2asrv/events.go b/a2asrv/events.go new file mode 100644 index 00000000..7f367766 --- /dev/null +++ b/a2asrv/events.go @@ -0,0 +1,56 @@ +// 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" + + "github.com/a2aproject/a2a-go/a2a" +) + +// EventReader defines the interface for reading events from a queue. +// A2A server stack reads events written by AgentExecutor. +type EventReader interface { + // Read dequeues an event or blocks if the queue is empty. + Read(ctx context.Context) (a2a.Event, error) +} + +// EventWriter defines the interface for writing events to a queue. +// AgentExecutor translates agent responses to Messages, Tasks or Task update events. +type EventWriter interface { + // Write enqueues an event or blocks if a bounded queue is full. + Write(ctx context.Context, event a2a.Event) error +} + +// EventQueue defines the interface for publishing and consuming +// events generated during agent execution. +type EventQueue interface { + EventReader + EventWriter + + // Close shuts down a connection to the queue. + Close() +} + +// EventQueueManager manages event queues on a per-task basis. +// It provides lifecycle management for task-specific event queues, +// enabling multiple clients to attach to the same task's event stream. +type EventQueueManager interface { + // GetOrCreate returns an existing queue if one exists, or creates a new one. + GetOrCreate(ctx context.Context, taskId a2a.TaskID) (EventQueue, error) + + // Destroy closes the queue for the specified task and frees all associates resources. + Destroy(ctx context.Context, taskId a2a.TaskID) error +} diff --git a/a2asrv/handler.go b/a2asrv/handler.go new file mode 100644 index 00000000..41b55ca0 --- /dev/null +++ b/a2asrv/handler.go @@ -0,0 +1,52 @@ +// 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" + "iter" + + "github.com/a2aproject/a2a-go/a2a" +) + +// RequestHandler defines a transport-agnostic interface for handling incoming A2A requests. +type RequestHandler interface { + // OnGetTask handles the 'tasks/get' protocol method. + OnGetTask(ctx context.Context, query a2a.TaskQueryParams) (a2a.Task, error) + + // OnCancelTask handles the 'tasks/cancel' protocol method. + OnCancelTask(ctx context.Context, id a2a.TaskIDParams) (a2a.Task, error) + + // OnSendMessage handles the 'message/send' protocol method (non-streaming). + OnSendMessage(ctx context.Context, message a2a.MessageSendParams) (a2a.SendMessageResult, error) + + // OnResubscribeToTask handles the `tasks/resubscribe` protocol method. + OnResubscribeToTask(ctx context.Context, id a2a.TaskIDParams) iter.Seq2[a2a.Event, error] + + // OnMessageSendStream handles the 'message/stream' protocol method (streaming). + OnSendMessageStream(ctx context.Context, message a2a.MessageSendParams) iter.Seq2[a2a.Event, error] + + // OnGetTaskPushNotificationConfig handles the `tasks/pushNotificationConfig/get` protocol method. + OnGetTaskPushConfig(ctx context.Context, params a2a.GetTaskPushConfigParams) (a2a.TaskPushConfig, error) + + // OnListTaskPushNotificationConfig handles the `tasks/pushNotificationConfig/list` protocol method. + OnListTaskPushConfig(ctx context.Context, params a2a.ListTaskPushConfigParams) ([]a2a.TaskPushConfig, error) + + // OnSetTaskPushConfig handles the `tasks/pushNotificationConfig/set` protocol method. + OnSetTaskPushConfig(ctx context.Context, params a2a.TaskPushConfig) (a2a.TaskPushConfig, error) + + // OnDeleteTaskPushNotificationConfig handles the `tasks/pushNotificationConfig/delete` protocol method. + OnDeleteTaskPushConfig(ctx context.Context, params a2a.DeleteTaskPushConfigParams) error +} diff --git a/a2asrv/reqctx.go b/a2asrv/reqctx.go new file mode 100644 index 00000000..02e5adb2 --- /dev/null +++ b/a2asrv/reqctx.go @@ -0,0 +1,42 @@ +// 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" + + "github.com/a2aproject/a2a-go/a2a" +) + +// RequestContextBuilder defines an extension point for constructing request contexts +// that contain the information needed by AgentExecutor implementations to process incoming requests. +type RequestContextBuilder interface { + // Build constructs a RequestContext from the provided parameters. + Build(ctx context.Context, p a2a.MessageSendParams, t *a2a.Task) RequestContext +} + +// RequestContext provides information about an incoming A2A request to AgentExecutor. +type RequestContext struct { + // Request which triggered the execution. + Request a2a.MessageSendParams + // TaskID is an ID of the task or a newly generated UUIDv4 in case Message did not reference any Task. + TaskID a2a.TaskID + // Task is present if request message specified a TaskID. + Task *a2a.Task + // RelatedTasks can be present when Message includes Task references and RequestContextBuilder is configured to load them. + RelatedTasks []a2a.Task + // ContextID is a server-generated identifier for maintaining context across multiple related tasks or interactions. Matches the Task ContextID. + ContextID string +} diff --git a/a2asrv/tasks.go b/a2asrv/tasks.go new file mode 100644 index 00000000..c39f6c28 --- /dev/null +++ b/a2asrv/tasks.go @@ -0,0 +1,53 @@ +// 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" + + "github.com/a2aproject/a2a-go/a2a" +) + +// PushNotifier defines the interface for sending push notifications +// about task state changes to external endpoints. +type PushNotifier interface { + // SendPush sends a push notification containing the latest task state. + SendPush(ctx context.Context, task a2a.Task) error +} + +// PushConfigStore manages push notification configurations for tasks. +type PushConfigStore interface { + // Save creates or updates a push notification configuration for a task. + // PushConfig has an ID and a Task can have multiple associated configurations. + Save(ctx context.Context, taskId a2a.TaskID, config a2a.PushConfig) error + + // Get retrieves all registered push configurations for a Task. + Get(ctx context.Context, taskId a2a.TaskID) ([]a2a.PushConfig, error) + + // Delete removes a push configuration registered for a Task with the given configID. + Delete(ctx context.Context, taskId a2a.TaskID, configID string) error + + // DeleteAll removes all registered push configurations of a Task. + DeleteAll(ctx context.Context, taskId a2a.TaskID) error +} + +// TaskStore provides storage for A2A tasks. +type TaskStore interface { + // Save stores a task. + Save(ctx context.Context, task a2a.Task) error + + // Get retrieves a task by ID. + Get(ctx context.Context, taskId a2a.TaskID) (a2a.Task, error) +} diff --git a/buf.gen.yaml b/buf.gen.yaml index 466f107a..cfd67fe2 100644 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -17,15 +17,15 @@ managed: override: - file_option: go_package path: a2a.proto - value: github.com/a2aproject/a2a/grpc + value: github.com/a2aproject/a2a-go/a2apb plugins: - remote: buf.build/protocolbuffers/go - out: grpc + out: a2apb opt: - paths=source_relative - remote: buf.build/grpc/go - out: grpc + out: a2apb opt: - paths=source_relative \ No newline at end of file diff --git a/internal/events/queue.go b/internal/events/queue.go new file mode 100644 index 00000000..b55c7d4d --- /dev/null +++ b/internal/events/queue.go @@ -0,0 +1,17 @@ +// 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 events + +type InMemoryQueueManager struct{} diff --git a/internal/push/sender.go b/internal/push/sender.go new file mode 100644 index 00000000..4943ceb7 --- /dev/null +++ b/internal/push/sender.go @@ -0,0 +1,17 @@ +// 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 push + +type HTTPPushSender struct{} diff --git a/internal/push/store.go b/internal/push/store.go new file mode 100644 index 00000000..9230d3e0 --- /dev/null +++ b/internal/push/store.go @@ -0,0 +1,17 @@ +// 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 push + +type InMemoryPushConfigStore struct{} diff --git a/internal/task/store.go b/internal/task/store.go new file mode 100644 index 00000000..19a52c4b --- /dev/null +++ b/internal/task/store.go @@ -0,0 +1,17 @@ +// 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 task + +type InMemoryTaskStore struct{} diff --git a/tools/jsonrpc_gen.sh b/tools/jsonrpc_gen.sh new file mode 100755 index 00000000..14eb1df7 --- /dev/null +++ b/tools/jsonrpc_gen.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Downloads the published A2A spec and generates go types +# in ./internal/jsonrpc/spec.go file. +# +# Ensure $GOBIN is in path and dependencies are installed: +# > go install github.com/atombender/go-jsonschema@latest +# +# Then run: +# > ./tools/jsonrpc_gen.sh + +set -euo pipefail + +# Configuration +SCHEMA_URL="https://raw.githubusercontent.com/a2aproject/A2A/main/specification/json/a2a.json" +SCHEMA_FILE="a2a.json" +OUTPUT_DIR="./internal/jsonrpc" +OUTPUT_FILE="spec.go" +PACKAGE_NAME="jsonrpc" + +mkdir -p "$OUTPUT_DIR" + +echo "Created output directory: $OUTPUT_DIR" + +echo "Downloading A2A JSON schema..." + +curl -s -o "$SCHEMA_FILE" "$SCHEMA_URL" + +echo "Generating Go types..." + +go-jsonschema \ + --package "$PACKAGE_NAME" \ + --output "$OUTPUT_DIR/$OUTPUT_FILE" \ + "$SCHEMA_FILE" + +echo "Formatting generated Go code..." + +gofmt -w "$OUTPUT_DIR/$OUTPUT_FILE" + +# Clean up downloaded schema file +rm -f "$SCHEMA_FILE" +echo "Cleaned up temporary files" + +echo "Done: $OUTPUT_DIR/$OUTPUT_FILE" \ No newline at end of file