A2A JavaScript SDK

repository·main·Indexed 20 days ago

https://github.com/a2aproject/a2a-js

Official implementation of the Agent2Agent (A2A) Protocol v1.0. This SDK allows developers to build agentic applications as A2A Servers or A2A Clients using JSON-RPC, HTTP/REST, or gRPC transports. It includes features for streaming task events, task cancellation, webhook-based push notifications, and a compatibility layer for interoperability with A2A v0.3 deployments.

Tokens
36.5K
Snippets
107
Records
176
Agent score
68%

What's inside @a2a-js/sdk

  1. Overview of A2A SDK capabilities and transports

    main

    The @a2a-js/sdk implements the A2A Protocol Specification v1.0.0. It supports three primary wire transports, all of which are backed by a single DefaultRequestHandler:

    • JSON-RPC: Supported by both Clients and Servers.
    • HTTP+JSON/REST: Supported by both Clients and Servers.
    • gRPC: Supported by both Clients and Servers (Node.js only).

    The SDK also includes an opt-in compatibility layer to allow v1.0 deployments to interoperate with peers still using v0.3.

  2. How A2A servers are structured

    main

    An A2A server is composed of three primary layers:

    1. AgentExecutor: This is where you implement your core business logic. It receives a RequestContext and communicates progress by publishing Message, Task, status, and artifact events to an ExecutionEventBus.
    2. DefaultRequestHandler: The orchestration layer. It manages message routing, task storage, cancellation logic, and push notifications.
    3. Transport Adapters: These expose the server to the network. You can mount multiple adapters to a single DefaultRequestHandler instance to support multiple protocols simultaneously.

    Available Transport Adapters:

    • jsonRpcHandler and restHandler (from @a2a-js/sdk/server/express)
    • grpcService (from @a2a-js/sdk/server/grpc)

    For a complete implementation, refer to the multi-transport-agent sample.

  3. How to build an A2A Client

    main

    Use the ClientFactory to instantiate a Client. The factory can automatically discover the best transport (JSON-RPC, REST, or gRPC) by fetching the agent's card from a URL.

    Methods:

    • factory.createFromUrl(baseUrl, path?): Fetches the agent card and selects the best transport based on supportedInterfaces and preferredTransports.
    • factory.createFromAgentCard(card): Creates a client from an in-memory AgentCard.

    Available Transport Factories:

    • JsonRpcTransportFactory
    • RestTransportFactory
    • GrpcTransportFactory (Node.js only, exported from @a2a-js/sdk/client/grpc)

    Every Client method (e.g., sendMessage, getTask, cancelTask) accepts a RequestOptions object, allowing you to provide a signal (for cancellation), custom serviceParameters (HTTP headers), and context on a per-call basis.

    // Example conceptual usage
    const factory = new ClientFactory();
    const client = await factory.createFromUrl('https://api.example.com/agent');
    
    await client.sendMessage(message, { 
      serviceParameters: { 'X-Custom-Header': 'value' } 
    });
  4. How the A2A protocol extension mechanism works

    main

    The A2A protocol allows agents to extend their behavior using an extension mechanism.

    1. Declaration: An agent declares its supported extensions in its agent card under capabilities.extensions.
    2. Activation: Extensions are not active by default. A client must opt-in to a specific extension per request by providing the A2A-Extensions HTTP header containing the extension's identifier.
    3. Execution: When a client requests an extension, the agent's executor intercepts relevant events (such as TaskStatusUpdateEvent) and applies the extension logic (e.g., modifying metadata or messages) before forwarding them to the SDK event bus.

    If the A2A-Extensions header is omitted, the extension does not activate, and no additional metadata or modified behavior is applied.

  5. Behavior of `tasks/resubscribe` streaming

    main

    The legacy tasks/resubscribe JSON-RPC method behaves differently regarding error handling and headers compared to other streaming methods:

    • Headers: It always responds with Content-Type: text/event-stream and HTTP 200 immediately. This happens even if the call fails (e.g., task not found).
    • Errors: Pre-stream errors are emitted as SSE error events on the open stream rather than as a standard JSON-RPC error envelope.
    • Comparison: Other streaming methods (like message/stream or v1.0 SubscribeToTask) use a 'peek-then-flush' approach where errors are returned as a standard JSON 200 error envelope before SSE headers are committed.
  6. How v0.3 requests are routed in v1.0 handlers

    main

    Once legacyCompat is enabled, the compat layer automatically translates wire bodies. Your AgentExecutor only interacts with v1.0 types.

    Routing Logic by Transport:

    • JSON-RPC: Detected by the method name. v1.0 PascalCase names (e.g., SendMessage) route to the v1.0 dispatcher. Kebab-style names (e.g., message/send) or unknown methods route to the v0.3 dispatcher.
    • REST: v0.3 routes are prefixed with /v1/. v1.0 routes use the operation name directly (e.g., /<operation>).
    • gRPC: Uses separate service descriptors (A2AService vs LegacyA2AService).
    • Agent Card: Determined by the A2A-Version header (defaults to '0.3' if absent).
  7. Consume streaming task events

    main

    Long-running tasks emit a stream of task, status-update, and artifact-update events.

    • On the Server: Publish these events through the ExecutionEventBus within your AgentExecutor.
    • On the Client: Consume the stream by iterating over the AsyncGenerator returned by client.sendMessageStream(...).
    const stream = client.sendMessageStream(message, options);
    for await (const event of stream) {
      console.log('Received event:', event);
    }
  8. Handle versioning in custom PushNotificationStore implementations

    main

    When using the v0.3 compatibility layer, the PushNotificationSender needs to know which wire version to use when dispatching a notification.

    If you implement a custom PushNotificationStore, you should implement the optional loadWithMetadata method. This allows the store to return the specific wireVersion that was active when the webhook was registered.

    Warning: If your custom store omits loadWithMetadata, the sender will default to the version of the client that triggered the event (context.requestedVersion). This can cause a mismatch where a v0.3 webhook receives a v1.0 body if a v1.0 client triggers the event. To avoid this, mirror the InMemoryPushNotificationStore implementation which persists the version as a StoredPushNotificationConfig { config, wireVersion }.

  9. The `tasks/resubscribe` streaming contract in v0.3

    main

    The legacy tasks/resubscribe handler follows a specific streaming contract to maintain compatibility with strict v0.3 clients:

    • It always responds with Content-Type: text/event-stream and HTTP 200.
    • The header is committed before the first iterator pull.
    • If the underlying call fails immediately (e.g., the task does not exist), errors are emitted as SSE error events on the open stream rather than as a standard JSON-RPC error response. This is because strict v0.3 clients reject any response that is not text/event-stream for this method.
  10. How JSON-RPC method dispatch works with legacyCompat

    main

    When jsonRpcHandler({ legacyCompat: { enabled: true } }) is used, the dispatcher routes requests based on the method field:

    1. v1.0 Dispatch: If the method is a PascalCase name (e.g., SendMessage, ListTasks), it is routed to the v1.0 dispatcher. You can check this using isV1JsonRpcMethod.
    2. v0.3 Dispatch: If the method is kebab-style (e.g., message/send, tasks/get), an unknown string, or if the method field is missing, it is routed to the v0.3 dispatcher. This ensures that malformed requests surface v0.3-shaped errors (like -32600 Invalid Request) which legacy clients expect.
  11. Understand the structure of generated v0.3 Protobuf bindings

    main

    The generated file pb/a2a.ts (produced via npx buf generate) contains the message-type definitions for the v0.3 compatibility layer.

    Included in pb/a2a.ts:

    • All v0.3 message-type interface declarations (e.g., Task, Message).
    • All v0.3 enum declarations along with fromJSON and toJSON helpers.
    • Per-message fromJSON and toJSON helpers.
    • The protobufPackage constant.

    Excluded from pb/a2a.ts (located elsewhere):

    • Wire encode / decode methods: These are omitted to keep the types free of gRPC runtimes. They are located in src/compat/v0_3/grpc/pb/a2a.ts.
    • gRPC service descriptors: These are also located in src/compat/v0_3/grpc/pb/a2a.ts.
    • Transitively imported .proto files (like google/protobuf/struct.proto): These live in src/compat/v0_3/grpc/pb/google/.

    Design Note: This separation ensures that components like the v1.0 JsonRpcTransportFactory and RestTransportFactory can remain compatible with runtimes like Cloudflare Workers by avoiding dependencies on @grpc/grpc-js and wire-encoding runtimes.

  12. Customize client calls with CallInterceptor

    main

    The SDK provides a CallInterceptor mechanism to wrap every method call with before and after hooks. This is transport-agnostic and is ideal for cross-cutting concerns such as:

    • Header Injection: Injecting request IDs, tracing information, or authentication tokens into RequestOptions.serviceParameters.
    • Metrics Collection: Recording start times in before and calculating elapsed time or success/failure rates in after.

    For protocol-level header conventions, refer to the A2A Specification §3.6 HTTP Headers.

    // Conceptual usage pattern based on the sample implementation
    // Interceptors wrap method calls with before/after hooks
    // Example: Injecting a Request ID
    // interceptor.before(call) -> inject X-Request-ID into serviceParameters
    // interceptor.after(call, result) -> log completion