目录

Implementing A2A Protocol Communication via AgentAbilityExtension

Starting from API version 26.0.0, AgentAbilityExtension can implement Agent to Agent (A2A) protocol communication.

The A2A protocol is used for communication between agents. The A2A server is responsible for receiving client requests, triggering agents to execute tasks, updating the task status, and returning execution results.

With Agent Framework Kit, you can build an agent server that supports the A2A protocol within the ArkTS app and deliver the following capabilities:

Basic Concepts

Business Capabilities

The A2A protocol supports efficient communication between agents. Its core functions include:

Service Process

  1. Client connection: After discovering the agent through the agent card, the client establishes a connection with the A2A server.
  2. Request sending: The client constructs a request and sends it to the server, requesting the agent to execute a task.
  3. Task processing: After receiving the request, the server triggers the agent to execute the business logic to process the task and generate artifacts.
  4. Result delivery: The server updates the task status and returns the execution result. After receiving the response, the client completes the current interaction.

API Description

This module provides server APIs for the A2A protocol. For details about the APIs, please refer to the A2A server module API reference.

The following table describes the key APIs.

API Description
createA2AServer Creates and obtains a server instance for handling A2A protocol communication.
Server Server instance API responsible for handling agent communication and task lifecycle management.
RequestContext Request context API, which provides context information for the request.
OnDataCallback Event callback function that is triggered when the A2A server receives a client request, initiating the execution of the agent's business logic.
Message Message unit in A2A communication.
Task A2A task, including the task status, artifacts, and message history.

Development Procedure

[h2]Importing Required Modules

import { hilog } from '@kit.PerformanceAnalysisKit';
import { common, Want, AgentExtensionAbility } from '@kit.AbilityKit';
import { RequestContext, createA2AServer, Server, TaskState, Role } from '@kit.AgentFrameworkKit';

[h2]Implementing the Functions

It is recommended that the server extend AgentExtensionAbility to support inter-process communication (IPC). AgentExtensionAbility provides an IPC-based callback API for handling requests.

Follow the steps below to implement each method in AgentExtensionAbility.

  1. Create the OnDataCallback function to define the agent's business logic.

    const TAG = '=====A2AServer===='
    export default class MyAgentExtensionAbility extends AgentExtensionAbility {
      private server: Server | null = null;
    
      // OnDataCallback function.
      private agentOnData = (method: string, context: RequestContext) => {
        // Obtain information such as the agent ID from the structured context parameter.
        const agentId: string = context.getAgentId() ?? '';
        if (!agentId) {
            hilog.error(0x0000, TAG, 'Agent ID not found in request context');
            return;
        }
        const taskId: string = context.getTaskId() ?? '';
        switch (method) {
          case 'Execute':
            // Use server.updateStatus to update the task execution status.
            this.server?.updateStatus(taskId, {
                state: TaskState.WORKING,
                message: {
                    messageId: 'msg1',
                    role: Role.AGENT,
                    parts: [{
                        mediaType: 'text/plain',
                        text: 'Starting work on your request...'
                    }]
                }
            });
    
            // Wait for the agent to generate artifacts.
            this.server?.addArtifact(taskId, {
                artifactId: 'result-artifact',
                parts: [{
                    mediaType: 'text/plain',
                    text: '{"action": "completed", "result": "Task finished successfully"}'
                }]
            });
    
            // Notify the client of task completion.
            this.server?.updateStatus(taskId, {
                state: TaskState.COMPLETED,
                message: {
                    messageId: 'msg3',
                    role: Role.AGENT,
                    parts: [{
                        mediaType: 'text/plain',
                        text: 'Task completed successfully!'
                    }]
                }
            });
            break;
          case 'Cancel':
            // Instruct the agent to handle the task cancellation request based on the agent ID.
            hilog.info(0x0000, TAG, 'Cancel called');
            break;
          case 'PerceptionSuggest':
            hilog.info(0x0000, TAG, `Perception suggest called, metadata: ${JSON.stringify(context.getMetadata(), null, 2)}`);
            break;
          default:
            break;
        }
      }
    }
  2. Implement the onCreate method to ensure that the createA2AServer method is called to create an A2A server object when the request is received for the first time.

    async onCreate(want: Want) {
       try {
         const card = this.context.agentCard;
         this.server = createA2AServer(card, this.agentOnData, want=want);
       } catch (error) {
         hilog.error(0x0000, TAG, `Failed to create server: ${error}`);
       }
    }
  3. Implement onConnect, which is triggered when a client establishes a connection, to start the server.

    onConnect(want: Want, proxy: common.AgentHostProxy) {
      // Callback triggered when the client is connected.
      this.server?.start();
    }
  4. Implement onData, which is triggered when a client request is received, and call server.onMessage to pass the request body.

    async onData(proxy: common.AgentHostProxy, data: string) {
      // Callback for handling requests.
      this.server?.onMessage(data, (response: string) => {
         proxy.sendData(response);
      });
    }
  5. Implement onDisconnect to shut down the server when a client connection is terminated.

    onDisconnect(want: Want, proxy: common.AgentHostProxy) {
      // Callback triggered when the client is disconnected.
      this.server?.stop();
    }
  6. Implement onDestroy to clean up resources when the service object is destroyed.

    onDestroy() {
      // Clean up resources.
      this.server?.stop();
    }

[h2]Complete Sample Code

import { hilog } from '@kit.PerformanceAnalysisKit';
import { common, Want, AgentExtensionAbility } from '@kit.AbilityKit';
import { RequestContext, createA2AServer, Server, TaskState, Role } from '@kit.AgentFrameworkKit';

const TAG = '=====A2AServer===='
export default class MyAgentExtensionAbility extends AgentExtensionAbility {
  private server: Server | null = null;

  // OnDataCallback function.
  private agentOnData = (method: string, context: RequestContext) => {
    // Obtain information such as the agent ID from the structured context parameter.
    const agentId: string = context.getAgentId() ?? '';
    if (!agentId) {
      hilog.error(0x0000, TAG, 'Agent ID not found in request context');
      return;
    }
    const taskId: string = context.getTaskId() ?? '';
    switch (method) {
      case 'Execute':
        // Use server.updateStatus to update the task execution status.
        this.server?.updateStatus(taskId, {
          state: TaskState.WORKING,
          message: {
            messageId: 'msg1',
            role: Role.AGENT,
            parts: [{
              mediaType: 'text/plain',
              text: 'Starting work on your request...'
            }]
          }
        });

        // Wait for the agent to generate artifacts.
        this.server?.addArtifact(taskId, {
          artifactId: 'result-artifact',
          parts: [{
            mediaType: 'text/plain',
            text: '{"action": "completed", "result": "Task finished successfully"}'
          }]
        });

        // Notify the client of task completion.
        this.server?.updateStatus(taskId, {
          state: TaskState.COMPLETED,
          message: {
            messageId: 'msg3',
            role: Role.AGENT,
            parts: [{
              mediaType: 'text/plain',
              text: 'Task completed successfully!'
            }]
          }
        });
        break;
      case 'Cancel':
        // Instruct the agent to handle the task cancellation request based on the agent ID.
        hilog.info(0x0000, TAG, 'Cancel called');
        break;
      case 'PerceptionSuggest':
        hilog.info(0x0000, TAG, `Perception suggest called, metadata: ${JSON.stringify(context.getMetadata(), null, 2)}`);
        this.server?.updateStatus(taskId, {
          state: TaskState.COMPLETED,
          message: {
            messageId: 'onChipsMsg1',
            role: Role.AGENT,
            parts: [{
              mediaType: 'text/plain',
              text: 'On chips task completed successfully!'
            }]
          }
        });
      default:
        break;
    }
  }

  async onCreate(want: Want) {
    try {
      const card = this.context.agentCard;
      this.server = createA2AServer(card, this.agentOnData, want=want);
    } catch (error) {
      hilog.error(0x0000, TAG, `Failed to create server: ${error}`);
    }
  }

  onConnect(want: Want, proxy: common.AgentHostProxy) {
    // Callback triggered when the client is connected.
    this.server?.start();
  }

  async onData(proxy: common.AgentHostProxy, data: string) {
    // Callback for handling requests.
    this.server?.onMessage(data, (response: string) => {
      proxy.sendData(response);
    });
  }

  onDisconnect(want: Want, proxy: common.AgentHostProxy) {
    // Callback triggered when the client is disconnected.
    this.server?.stop();
  }

  onDestroy() {
    // Clean up resources.
    this.server?.stop();
  }
}

[h2]Example of Sending AgentChips Data via the A2A Agent

The AgentChips request is used by Celia to detect the app currently used by the user and request recommended content from the agent. This section provides an example of how an app responds to a Celia AgentChips request.

Celia sends a PerceptionSuggest request to the app where AgentExtensionAbility is located.

By defining the handling logic for the 'PerceptionSuggest' method in OnDataCallback, the agent can respond according to the sample message format defined in the Celia AgentChips interaction flow.

The following is an example of implementing OnDataCallback in the AgentChips scenario:

export default class MyAgentExtensionAbility extends AgentExtensionAbility {
  private server: Server | null = null;
  // Define the business processing logic of the agent.
  private agentOnData: OnDataCallback = (method: string, context: RequestContext) => {
    switch (method) {
      // Type of the request from Celia.
      case 'PerceptionSuggest':
        // Obtain the task ID.
        const taskId = context.getTaskId();

        // Generate an artifact ID. A fixed value is used in this example.
        const artId = 'artifact-1';

        if (!taskId) {
          console.error(TAG, "Task ID not found in request context");
          return;
        }

        // Simulate the agent analyzing recommendation results and return the recommendations to Celia.
        this.server?.addArtifact(taskId, {
          artifactId: artId,
          parts: [
            {
              data: {
                suggestionCandidates: [
                  {
                    text: "Latest TV series",
                    reply: "Latest TV series",
                  }
                ]
              },
              mediaType: "application/json"
            }
          ],
          append: false,
          lastChunk: true
        });

        // Notify the client of the completion of the AgentChips task.
        this.server?.updateStatus(taskId, {
          state: TaskState.COMPLETED,
          message: {
            messageId: "demo-msg-123",
            role: Role.AGENT,
            parts: [{
              mediaType: "text/plain",
              text: "Task completed successfully!"
            }]
          }
        });

      default:
        break;
    }
  }

  async onCreate(want: Want) {
    try {
      const card = this.context.agentCard;
      this.server = createA2AServer(card, this.agentOnData, want=want);
    } catch (error) {
      hilog.error(0x0000, TAG, `Failed to create server: ${error}`);
    }
  }

  onConnect(want: Want, proxy: common.AgentHostProxy) {
    // Callback triggered when the client is connected.
    this.server?.start();
  }

  async onData(proxy: common.AgentHostProxy, data: string) {
    // Callback for handling requests.
    this.server?.onMessage(data, (response: string) => {
      proxy.sendData(response);
    });
  }

  onDisconnect(want: Want, proxy: common.AgentHostProxy) {
    // Callback triggered when the client is disconnected.
    this.server?.stop();
  }

  onDestroy() {
    // Clean up resources.
    this.server?.stop();
  }
}