-
Notifications
You must be signed in to change notification settings - Fork 36
feat: define core types and interfaces #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
5d5132b
a script for generating a jsonrpc types from a2a json-schema
yarolegovich 712dbe6
generate plain types from a2a json-schema
yarolegovich 200912d
manual cleanup of generated types
yarolegovich 473c0d2
server interfaces
yarolegovich 3195915
core type dummies
yarolegovich 7239ade
replace chan with seq
yarolegovich 35cec6e
remove duplicated files and split types file into many
yarolegovich 0575127
removed accidentally committed artifact
yarolegovich 5e02a49
file rename
yarolegovich 0544c0a
removed a type wrapper to match other sdk implementations
yarolegovich 1b92e1d
added script doc, removed throwaway script
yarolegovich cb17547
cleanup and fill req context in
yarolegovich 6fa0df2
fix missing message
yarolegovich 5a1f79c
move interface to where it belongs
yarolegovich b54a22d
task id always needs to be present even if message won't be promoted …
yarolegovich d2160b4
Merge branch 'main' into yarolegovich/core-types
yarolegovich 75512c0
add licenses
yarolegovich acebe1d
shorten method name
yarolegovich 4e4cf0e
alphabetic sorting
yarolegovich 3280481
typo fix
yarolegovich bd3d98e
added missing doc comments, resolved minor interface inconsistencies,…
yarolegovich a663339
expose proto generated files as a2apb package
yarolegovich e1aee88
added doc.go files
yarolegovich 38543db
rename Uri -> URI
yarolegovich File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" | ||
| ) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These types are defined by A2A spec, and we're already generating Go types for them. Could you explain the reasoning and benefits of introducing them here, instead of reusing generated types from grpc?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about this at the beginning, but it doesn't feel clean to implement a default transport-agnostic handler that works with proto-generated types, which contain:
TaskState_TASK_STATE_AUTH_REQUIRED, types namedPart_FilePartsfield of typePartinMessageis calledContentin proto type)Id interface{}because clients are allowed to send a number or a stringI think it's better to keep extension points like TaskStore depend on plain structs and have grpc and in the future jsonrpc as translation layers that wrap RequestHandler.
also I think there should be one client that can talk to both grpc and jsonrpc backends, so using proto types in its APIs also seems like a quick solution, not a clean one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, we should definitely have all specification-defined data types (both gRPC and JSON-RPC request/responses) publicly exported and accessible to consumers of this library. One way someone might want to use this SDK is purely as a source of data type definitions that serialize to/deserialize from the specification-defined format. They may want to build all of the actual serving components themselves, but having the types auto-generated is a useful time saver. All of the existing SDKs do this.
https://github.com/a2aproject/a2a-java/tree/main/spec/src/main/java/io/a2a/spec
https://github.com/a2aproject/a2a-js/blob/main/src/types.ts
https://github.com/a2aproject/a2a-python/blob/main/src/a2a/types.py
If you then want to have a separate set of more idiomatic Go types, with converters between, that's reasonable (but then you'd really want to justify the improvement they make vs. the core spec types). However, one major advantage of relying on core generated types throughout is that you don't have to hand edit the types to match the spec when it updates -- just re-run the generator. For simple spec changes, that's all you'd need to do to be up-to-date with a new version of the protocol.
The way the Python SDK approached this problem is to standardize on the types defined by the JSON schema part of the spec. Transport-agnostic implementations that developers provide use those types directly. The gRPC types are converted to the JSON schema generated versions of the types. The Java SDK followed this same pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. I'll regenerate grpc types into a public
a2apbpackage.I see the benefits of using spec types directly especially at the beginning. That's why I made very minimal changes to the result of jsonrpc schema generator tool.
Decoupling at this stage involves more work, but in my opinion it will give us more control over internal architecture, migrations, compatibility management and reduce the room for errors.
Maybe I'm misunderstanding project evolution direction, but here's my reasoning:
Control over changes
A spec changed, and we regenerated types. Now the question is what has just happened to all the places that were using these types:
clientfor making requests, do they need to be updated?Translation layers and manual model evolution requires deliberate changes, and these questions will need to be answered explicitly. There'll be a dedicated place to resolve all inconsistencies with internal logic - the translation layer.
Compatibility
Another question is what are our compatibility guarantees? I've just realized that
ProtocolVersionin AgentCard is a string (not an array) and the spec doesn't mention protocol negotiation. We can have different card paths or introduce a new field, but if we don't support multi-version servers the first person to update their server to the new protocol gets effectively severed from everyone else because their server can only advertise the latest protocol version without an option to fallback to an older one.What about clients? Do we plan to explicitly require creating a client of a specific spec version -
NewClientV*? Or just aNewClientthat'll discover the version and switch to the appropriate spec translator?To address these scenarios we'll need to build translation layers anyway, but now we'll be translating from older spec types to newer spec types which might be harder, as we won't be able to separate version specific information where required. I understand that depending on how the protocol evolves maintaining many versions in the same codebase might become very problematic, but this can be addressed either by gradual version deprecations for very old versions.
Devex
These are minor, but I think decoupling can slightly improve devex for both SDK consumers and developers.
&{Code: ..., Data: ..., Message: ... }, and we would introduce aa2asrv.NewTaskNotFoundErr()or something like that. Butreturn nil, ErrTaskNotFoundI think looks more idiomatic and easier to work with for consumers (errors.Isvserrors.As).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the detailed reasoning. Let me address your points below:
I share Mike's concern here. While the idea of a translation layer acting as a single choke-point for spec changes sounds appealing, in practice, it often just shifts the problem.
If a field is changed/removed from a proto message, the first compile error will be in the translation layer. However, once you fix that, you'll get a cascade of compile errors in every place that used the corresponding field from the internal type. You still have to answer the same hard questions ("How many tests to update?", "Is stored data compatible?"), but now you also have the added maintenance burden of keeping the translator and the duplicated struct in sync.
You've raised an excellent and critical point: the current spec seems to lack a clear protocol versioning and negotiation strategy. This is a fundamental problem, but I think it needs to be addressed at the protocol level.
Trying to solve this inside our SDK with a translation layer is likely premature and could lead us to build a complex abstraction for a problem that might be solved differently in a future spec update. For instance, if we build a translation layer from spec_v1 -> internal_model and spec_v2 -> internal_model, we risk our internal_model becoming a complex superset of all features, making it hard to maintain.
The more robust approach is to push for this to be clarified in the A2A spec itself. Until then, we could build for the current spec and treat significant breaking changes in the protocol as a trigger for a major version bump of our SDK.
This is for sure the strongest point in favor for translation layer, as the generated types can indeed be un-idiomatic. I wonder if there's a workaround for this. Do you think we could introduce a lightweight "facade" layer—not by duplicating structs, but by adding helper methods to the generated types or creating utility functions?
For now I'd defer building a full-blown translation layer until we have a more concrete needs (e.g. we understand how multi-version handling should work), which aligns closely with Mike's advice and established patterns in other SDKs. WDYT?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't be true for every spec change, some can be handled at the translation layer level.
Correct. My point there was that these might be easier to answer as we'll have more flexibility with types and changes made to these types would be decisions controlled on the SDK level, not driven by spec evolution.
I agree, that might become a real problem. Eventually older versions need to be deprecated and the code cleaned up.
I agree that deserves a separate technology-independent discussion. A major version bump doesn't sound like the right strategy for an interoperability protocol.
We can. Doing it "the right" (clean, simple, intuitive) way is harder than (for example) using
time.Timeinstead of a string field.From my point of view the spec is a contract for communication and should stay at the boundary. Its evolution is not concerned with abstractions we invented for the reference implementation (task store, queues etc).
But I'm yielding if nobody is convinced, maybe it's speculative overengineering 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the biggest challenge for automatic generation is that the go ecosystem lacks a magic jsonschema-to-go generator. Some types in spec are not easily translatable (e.g. sum types) without manual intervention. As a result, we will anyway end up with writing our own custom generator and have to maintain it actively.
For example, in gopls (Go language server), we had to build a non-trivial custom spec-to-proto generator, and had to inspect the translator and spec carefully whenever we update the LSP spec, even though LSP had been relatively stable for a while. Another extreme example is the MCP Go SDK. They started with a custom generator but decided to remove it and manually managing types (modelcontextprotocol/go-sdk#7). I heard from the maintainer that maintaining the generator code added more work than manually adding necessary types.
Is there anything we can reuse/learn from other projects (e.g. Google Cloud SDK, google.golang.org/genai) for managing grpc and json api types?
That will make json rpc depend on protobuf (and if we keep the service def type in the package, grpc). I am not sure if that's a good thing.
Another question: is the a2a grpc proto complete and suitable enough to be used to generate types and codes needed for json rpc? I don't see
A2AErrortype in proto for example.TaskStateis a proto enum type, but in json rpc, it's string enum type. (For Google Cloud SDK, it seems like the SOT is the proto file, not jsonschema, so they may not have this issue).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to provide more context for the discussion:
Yeah, go-jsonschema is the best tool I found. I think it produces satisfactory output for the latest spec, but inheritance and union types are a problem:
OpenApi tooling can also be used for creating types from json-schema. But it doesn't support codec validations and doesn't handle the above casese any better (I tried).
I couldn't find anything type-generation related in genai repo. From the commit history it looks like they're manually maintaining a 5k-line file with hand-crafted types.
An interesting proto compiler I came across is gogo/protobuf. It is used by a number of big projects like etcd, mesos, tidb, but is unfortunately deprecated. Among its features it provides many extensions some of which can be used to generate more idiomatic Go structs. Tried the default configuration here.
Good point, in proto-first approach we'll need to hand-craft errors for RequestHandler to return, errors are not a part of proto spec.