Skip to main content
Built on top of the Agent2Agent Protocol (A2A), Agent Stack extends the protocol with Agent Stack specific capabilities through A2A extensions. These extensions enable agents to access platform services and enhance the user interface beyond what the base A2A protocol provides. Extensions fall into two categories:
  • Service extensions: Demands in the agent card that your client fulfills.
  • UI extensions: Message metadata your UI renders.
All extension definitions are exported from agentstack-sdk/extensions if you prefer a narrower import surface.

Core Helpers

The SDK includes helpers for reading and producing extension metadata:
  • extractServiceExtensionDemands(extension) reads demands from an agent card.
  • fulfillServiceExtensionDemand(extension) merges fulfillments into metadata.
  • extractUiExtensionData(extension) reads UI metadata from a message.
  • resolveUserMetadata(inputs) builds metadata for form, approval, and canvas responses.
  • handleAgentCard(agentCard) returns demands and a resolveMetadata function.
  • handleTaskStatusUpdate(event) maps status updates to actionable UI results.
  • buildMessageBuilder(agentCard) builds user messages with resolved metadata.
Use these directly when you want fine grained control beyond handleAgentCard. extractServiceExtensionDemands, fulfillServiceExtensionDemand, and extractUiExtensionData are factories. You pass an extension definition once and reuse the returned function for that extension. For end to end usage, see A2A Client Integration and Message Building.

Service Extensions

Service extensions use a dependency injection pattern. The agent declares demands and the client provides fulfillments.

LLM Service

Provides OpenAI compatible LLM access with api_base, api_key, and api_model.
import { buildLLMExtensionFulfillmentResolver, handleAgentCard } from "agentstack-sdk";

const { resolveMetadata } = handleAgentCard(agentCard);
const llmResolver = buildLLMExtensionFulfillmentResolver(api, contextToken);

const metadata = await resolveMetadata({
  llm: llmResolver,
});

Embedding Service

Provides OpenAI compatible embedding access with api_base, api_key, and api_model.
import { handleAgentCard } from "agentstack-sdk";

const { resolveMetadata } = handleAgentCard(agentCard);

const metadata = await resolveMetadata({
  embedding: async (demands) => ({
    embedding_fulfillments: Object.fromEntries(
      Object.keys(demands.embedding_demands).map((key) => [
        key,
        {
          identifier: "openai/text-embedding-3-small",
          api_base: "https://api.openai.com/v1",
          api_key: "your-api-key",
          api_model: "text-embedding-3-small",
        },
      ]),
    ),
  }),
});

MCP

Provides Model Context Protocol transports for tool and connector access.
import { MCPTransportType, handleAgentCard } from "agentstack-sdk";

const { resolveMetadata } = handleAgentCard(agentCard);

const metadata = await resolveMetadata({
  mcp: async (demands) => ({
    mcp_fulfillments: Object.fromEntries(
      Object.keys(demands.mcp_demands).map((key) => [
        key,
        {
          transport: {
            type: MCPTransportType.StreamableHttp,
            url: "https://mcp.example.com",
            headers: { Authorization: "Bearer token" },
          },
        },
      ]),
    ),
  }),
});

OAuth

Provides OAuth redirect metadata for authentication flows.
import { handleAgentCard } from "agentstack-sdk";

const { resolveMetadata } = handleAgentCard(agentCard);

const metadata = await resolveMetadata({
  oauth: async (demands) => ({
    oauth_fulfillments: Object.fromEntries(
      Object.keys(demands.oauth_demands).map((key) => [key, { redirect_uri: "https://app.example.com/oauth/callback" }]),
    ),
  }),
  oauthRedirectUri: () => "https://app.example.com/oauth/callback",
});

Secrets

Provides secret values required by the agent.
import { handleAgentCard } from "agentstack-sdk";

const { resolveMetadata } = handleAgentCard(agentCard);

const metadata = await resolveMetadata({
  secrets: async (demands) => ({
    secret_fulfillments: Object.fromEntries(
      Object.keys(demands.secret_demands).map((key) => [key, { secret: "your-secret" }]),
    ),
  }),
});

Settings

Provides runtime configuration values that match the requested fields. This extension lives under ui/settings in the SDK, but it is treated as a service extension because it carries demands and fulfillments.
import { handleAgentCard } from "agentstack-sdk";

const { resolveMetadata } = handleAgentCard(agentCard);

const metadata = await resolveMetadata({
  settings: async (demands) => ({
    values: Object.fromEntries(
      demands.fields.map((field) => {
        if (field.type === "single_select") {
          return [field.id, { type: "single_select", value: field.default_value }];
        }
        return [
          field.id,
          {
            type: "checkbox_group",
            values: Object.fromEntries(
              field.fields.map((checkbox) => [checkbox.id, { value: checkbox.default_value }]),
            ),
          },
        ];
      }),
    ),
  }),
});

Form

Provides form responses when the agent requests structured input.
import { handleAgentCard } from "agentstack-sdk";

const { resolveMetadata } = handleAgentCard(agentCard);

const metadata = await resolveMetadata({
  form: async () => ({
    form_fulfillments: {
      default: {
        values: { name: "Ada" },
      },
    },
  }),
});

Platform API

Adds context token metadata so the agent can call platform services. This is typically used only when you cannot pass the token through A2A client headers.
import { handleAgentCard } from "agentstack-sdk";

const { resolveMetadata } = handleAgentCard(agentCard);

const metadata = await resolveMetadata({
  getContextToken: () => contextToken,
});
getContextToken is deprecated. Prefer sending the context token via A2A client headers when possible.

UI Extensions

UI extensions are message metadata your UI can render. The SDK includes typed schemas for extracting these payloads.

Form Request

Requests a form render payload.
import { extractUiExtensionData, formRequestExtension } from "agentstack-sdk";

const readForm = extractUiExtensionData(formRequestExtension);
const form = readForm(message.metadata);

Approval

Requests user approval for an action or tool call.
import { approvalExtension, extractUiExtensionData } from "agentstack-sdk";

const readApproval = extractUiExtensionData(approvalExtension);
const approval = readApproval(message.metadata);

Canvas

Requests a canvas edit with indices and description.
import { canvasExtension, extractUiExtensionData } from "agentstack-sdk";

const readCanvas = extractUiExtensionData(canvasExtension);
const request = readCanvas(message.metadata);

Citation

Provides citation ranges and URLs for inline references.
import { citationExtension, extractUiExtensionData } from "agentstack-sdk";

const readCitations = extractUiExtensionData(citationExtension);
const citations = readCitations(message.metadata);

Trajectory

Provides trace entries for reasoning or execution steps.
import { extractUiExtensionData, trajectoryExtension } from "agentstack-sdk";

const readTrajectory = extractUiExtensionData(trajectoryExtension);
const trajectory = readTrajectory(message.metadata);

Agent Detail

Provides agent metadata to display in the UI. This extension is sent in the agent card capabilities, not in message metadata.
import { agentDetailExtension, extractUiExtensionData } from "agentstack-sdk";

const readAgentDetail = extractUiExtensionData(agentDetailExtension);
const uri = agentDetailExtension.getUri();
const params = agentCard.capabilities.extensions?.find((extension) => extension.uri === uri)?.params;

const agentDetail = params ? readAgentDetail({ [uri]: params }) : null;

Error

Provides structured error information.
import { errorExtension, extractUiExtensionData } from "agentstack-sdk";

const readError = extractUiExtensionData(errorExtension);
const error = readError(message.metadata);

OAuth Request

Provides an OAuth authorization endpoint to redirect the user.
import { extractUiExtensionData, oauthRequestExtension } from "agentstack-sdk";

const readOAuth = extractUiExtensionData(oauthRequestExtension);
const oauthRequest = readOAuth(message.metadata);

Secrets Request

Provides secret demand prompts.
import { extractUiExtensionData, secretsRequestExtension } from "agentstack-sdk";

const readSecrets = extractUiExtensionData(secretsRequestExtension);
const secretDemands = readSecrets(message.metadata);

Handling task status updates

Use handleTaskStatusUpdate to parse status updates into UI actions. This covers OAuth, secrets, forms, and approval flows.
import { handleTaskStatusUpdate, TaskStatusUpdateType } from "agentstack-sdk";

for await (const event of stream) {
  if (event.kind === "status-update") {
    handleTaskStatusUpdate(event).forEach((result) => {
      switch (result.type) {
        case TaskStatusUpdateType.FormRequired:
          // Render result.form
          break;
        case TaskStatusUpdateType.OAuthRequired:
          // Redirect to result.url
          break;
        case TaskStatusUpdateType.SecretRequired:
          // Prompt for result.demands
          break;
        case TaskStatusUpdateType.ApprovalRequired:
          // Ask the user to approve result.request
          break;
      }
    });
  }
}

Sending user metadata

When the user responds to a form, approval, or canvas request, use resolveUserMetadata to build message metadata:
import { resolveUserMetadata } from "agentstack-sdk";

const metadata = await resolveUserMetadata({
  form: formValues,
  approvalResponse: { decision: "approve" },
  canvasEditRequest: {
    start_index: 0,
    end_index: 12,
    artifact_id: "artifact-id",
    description: "Replace the title",
  },
});

Type Safety

All extension schemas are typed in TypeScript and validated with Zod at runtime. This helps catch malformed extension payloads early and keeps your UI logic aligned with the protocol.