Agent Hosting API
Complete API reference for the @a2aletheia/sdk/agent module.
Note: This entry point requires Node.js and Express. It is not browser-compatible.
Exports
import {
// Main classes
AletheiaAgent,
AgentResponse,
// Task storage
RedisTaskStore,
// Re-exports from @a2a-js/sdk/server
InMemoryTaskStore,
A2AError,
RequestContext,
DefaultExecutionEventBus,
// Types
type AletheiaAgentConfig,
type AletheiaExtensions,
type AgentContext,
type AgentHandler,
type CancelHandler,
type RedisLike,
type RedisTaskStoreOptions,
type Part,
type TextPart,
type DataPart,
type FilePart,
type Artifact,
type AgentCard,
type AgentSkill,
type AgentCapabilities,
type Message,
type Task,
type TaskState,
type TaskStatus,
type TaskStore,
type ExecutionEventBus,
type AgentExecutor,
} from "@a2aletheia/sdk/agent";
AletheiaAgent Class
High-level API for building A2A-compliant agents with Aletheia trust layer integration.
Constructor
constructor(config: AletheiaAgentConfig)
Creates a new agent instance with the specified configuration.
Methods
handle(handler: AgentHandler): this
Registers the message handler for incoming requests. Returns this for chaining.
agent.handle(async (context, response) => {
response.text(`Echo: ${context.textContent}`);
});
onCancel(handler: CancelHandler): this
Registers an optional cancel handler for task cancellation requests. Returns this for chaining.
agent.onCancel(async (taskId, response) => {
await cleanupResources(taskId);
response.canceled();
});
on(event: AletheiaEventType | "*", handler: AletheiaEventHandler): () => void
Subscribes to lifecycle events. Returns an unsubscribe function.
// Specific event
const unsub = agent.on("message.received", (event) => {
console.log("Received:", event.data);
});
// Wildcard - receive all events
agent.on("*", (event) => {
metrics.increment(`aletheia.${event.type}`);
});
// Later: unsub() to remove listener
handleRequest(body: unknown): Promise<A2AResponse | AsyncGenerator<A2AResponse>>
Framework-agnostic request handler. Use this for Hono, Fastify, or other frameworks instead of the built-in Express server.
// Example with Hono
app.post("/", async (c) => {
const body = await c.req.json();
const result = await agent.handleRequest(body);
if ("next" in result) {
// Streaming response
return stream(c, async (stream) => {
for await (const chunk of result as AsyncGenerator<A2AResponse>) {
await stream.write(JSON.stringify(chunk) + "\n");
}
});
}
return c.json(result);
});
start(port: number): Promise<void>
Starts a standalone Express server on the specified port. Resolves when the server is listening.
await agent.start(4000);
console.log("Agent running on port 4000");
stop(): void
Stops the standalone server if running.
agent.stop();
getAgentCard(): AgentCard
Returns the agent’s AgentCard (manifest) object.
const card = agent.getAgentCard();
console.log(card.name, card.version);
getRequestHandler(): A2ARequestHandler
Returns the underlying A2A request handler for custom integrations.
const handler = agent.getRequestHandler();
// Use with custom routing or middleware
getTaskStore(): TaskStore
Returns the task store instance used by the agent.
const store = agent.getTaskStore();
const task = await store.load("task-123");
getAletheiaExtensions(): AletheiaExtensions | undefined
Returns the Aletheia-specific extensions configured for this agent.
const extensions = agent.getAletheiaExtensions();
if (extensions?.did) {
console.log("Agent DID:", extensions.did);
}
Properties
logger: AletheiaLogger
Logger instance for the agent. Use for custom logging within handlers.
agent.logger.info("Custom log message", { taskId: "123" });
events: EventEmitter
Event emitter for lifecycle events. Prefer using on() method instead.
AgentResponse Class
Response helper that wraps ExecutionEventBus with convenience methods for sending responses.
Constructor
constructor(context: RequestContext | null, eventBus: ExecutionEventBus)
Creates a new response instance. Typically created internally by the agent framework.
Properties
isFinished: boolean
Whether the response has been finalized. After calling a final method (text, data, message, done, fail, canceled), this becomes true.
Quick Response Methods
These methods send a response and immediately finalize the request.
Note: All response methods automatically include
contextIdandtaskIdfrom the request context. This enables orchestrators to preserve conversation state across multi-turn interactions.
text(content: string): void
Sends a text response and completes the request.
response.text("Hello, world!");
// Response includes: { contextId: "...", taskId: "...", parts: [...] }
data(data: Record<string, unknown>): void
Sends a JSON data response and completes the request.
response.data({ status: "success", result: 42 });
// Response includes: { contextId: "...", taskId: "...", parts: [...] }
message(parts: Part[]): void
Sends a message with custom parts and completes the request.
response.message([
{ kind: "text", text: "Here's the analysis:" },
{ kind: "data", data: { score: 95 } },
]);
// Response includes: { contextId: "...", taskId: "...", parts: [...] }
Streaming Methods
These methods publish intermediate updates. Use done(), fail(), or canceled() to finalize.
Note: All streaming methods also automatically include
contextIdandtaskId.
working(message?: string): void
Publishes a “working” status update (non-final). Call to indicate the agent is processing.
response.working("Analyzing your request...");
artifact(artifact: Artifact, options?: { append?: boolean; lastChunk?: boolean }): void
Publishes an artifact update. Use for streaming intermediate results.
response.artifact(
{
name: "partial-results",
parts: [{ kind: "data", data: intermediateData }],
},
{ append: false, lastChunk: false }
);
Options:
append- Whether to append to existing artifact (default:false)lastChunk- Whether this is the last chunk (default:false)
done(message?: string): void
Publishes a “completed” status and finalizes the response.
response.done("Task completed successfully!");
fail(error: string): void
Publishes a “failed” status and finalizes the response with an error message.
response.fail("Unable to process the request");
canceled(): void
Publishes a “canceled” status and finalizes the response. Typically called from cancel handlers.
response.canceled();
inputRequired(message: string): void
Publishes an “input-required” status. Use when the agent needs additional input from the user.
response.inputRequired("Please provide your API key to continue.");
RedisTaskStore Class
A TaskStore implementation backed by Redis for persistent task storage across server restarts.
Constructor
constructor(redis: RedisLike, options?: RedisTaskStoreOptions)
Creates a new Redis-backed task store.
Methods
save(task: Task): Promise<void>
Saves a task to Redis.
await taskStore.save({
id: "task-123",
status: { state: "working" },
// ...
});
load(taskId: string): Promise<Task | undefined>
Loads a task from Redis by ID. Returns undefined if not found.
const task = await taskStore.load("task-123");
if (task) {
console.log("Task state:", task.status.state);
}
Types
AletheiaAgentConfig
Configuration interface for creating an AletheiaAgent.
interface AletheiaAgentConfig {
// Required
name: string; // Agent display name
version: string; // Agent version (semver)
url: string; // Agent's public base URL
description: string; // Human-readable description
skills: AgentSkill[]; // Array of capabilities
// Optional AgentCard fields
defaultInputModes?: string[]; // Default: ["text/plain", "application/json"]
defaultOutputModes?: string[]; // Default: ["text/plain", "application/json"]
capabilities?: Partial<AgentCapabilities>; // Streaming, pushNotifications, etc.
iconUrl?: string; // URL to agent icon
documentationUrl?: string; // URL to documentation
provider?: { // Provider info
organization: string;
url: string;
};
// Aletheia-specific
aletheiaExtensions?: AletheiaExtensions; // Trust layer extensions
registryUrl?: string; // Aletheia registry URL
taskStore?: TaskStore; // Task persistence backend
// Observability
logger?: AletheiaLogger; // Custom logger
logLevel?: AletheiaLogLevel; // "debug" | "info" | "warn" | "error"
}
AletheiaExtensions
Aletheia-specific extensions for AgentCard metadata.
interface AletheiaExtensions {
owner?: string; // Ethereum address of owner (for SIWE auth)
livenessPingUrl?: string; // URL for liveness health checks
did?: string; // DID identifier (did:web or did:key)
publicKeyMultibase?: string; // Public key in multibase format (for did:web)
}
Fields:
| Field | Type | Description |
|---|---|---|
owner |
string |
Ethereum address of the agent owner (for SIWE authentication) |
livenessPingUrl |
string |
URL for Aletheia liveness health checks |
did |
string |
DID identifier. Use did:web:yourdomain.com for production or leave unset for auto-generated did:key |
publicKeyMultibase |
string |
Ed25519 public key in multibase format (e.g., z6Mk...). Required for did:web, auto-derived for did:key |
DID Methods:
| Method | When to Use | publicKeyMultibase |
|---|---|---|
did:key (auto) |
Development, testing | Optional - auto-derived |
did:web |
Production | Required - from generateAgentKeyPair() |
AgentContext
Extended request context with helper methods for accessing message content.
interface AgentContext extends RequestContext {
readonly textContent: string; // All text parts joined with newlines
readonly dataContent: Record<string, unknown> | null; // First data part's data
readonly parts: Part[]; // Raw message parts
// Inherited from RequestContext:
readonly contextId?: string; // Conversation identifier for multi-turn
readonly taskId: string; // Task identifier
}
Key Properties:
| Property | Type | Description |
|---|---|---|
textContent |
string |
All text parts joined with newlines |
dataContent |
Record<string, unknown> \| null |
First data part’s data |
parts |
Part[] |
Raw message parts |
contextId |
string \| undefined |
Conversation identifier — use for stateful agents |
taskId |
string |
Unique task identifier |
Usage for Stateful Agents:
agent.handle(async (context, response) => {
// Use contextId as session key for conversation history
const sessionId = context.contextId ?? "default";
const history = conversationStore.get(sessionId) ?? [];
// Process with history...
response.text("Response"); // Automatically includes contextId
});
AgentHandler
Handler function signature for processing incoming messages.
type AgentHandler = (
context: AgentContext,
response: AgentResponse
) => Promise<void>;
CancelHandler
Handler function signature for cancellation requests.
type CancelHandler = (
taskId: string,
response: AgentResponse
) => Promise<void>;
RedisLike
Minimal Redis client interface compatible with ioredis, node-redis v4, and similar libraries.
interface RedisLike {
get(key: string): Promise<string | null>;
set(key: string, value: string, ...args: unknown[]): Promise<unknown>;
del(...args: unknown[]): Promise<unknown>;
}
RedisTaskStoreOptions
Options for configuring RedisTaskStore.
interface RedisTaskStoreOptions {
prefix?: string; // Key prefix. Default: "aletheia:task:"
ttlSeconds?: number; // TTL in seconds. Default: 86400 (24 hours). Set to 0 to disable.
}
Re-exports from @a2a-js/sdk
The following are re-exported from @a2a-js/sdk and @a2a-js/sdk/server for convenience:
Classes
| Export | Source | Description |
|---|---|---|
InMemoryTaskStore |
@a2a-js/sdk/server |
In-memory task storage |
A2AError |
@a2a-js/sdk/server |
A2A error class |
RequestContext |
@a2a-js/sdk/server |
Incoming request context |
DefaultExecutionEventBus |
@a2a-js/sdk/server |
Default event bus implementation |
Types
| Export | Source | Description |
|---|---|---|
Part |
@a2a-js/sdk |
Message part (union type) |
TextPart |
@a2a-js/sdk |
Text message part |
DataPart |
@a2a-js/sdk |
Data message part |
FilePart |
@a2a-js/sdk |
File message part |
Artifact |
@a2a-js/sdk |
Artifact structure |
AgentCard |
@a2a-js/sdk |
Agent manifest |
AgentSkill |
@a2a-js/sdk |
Skill definition |
AgentCapabilities |
@a2a-js/sdk |
Agent capabilities |
Message |
@a2a-js/sdk |
Message structure |
Task |
@a2a-js/sdk |
Task structure |
TaskState |
@a2a-js/sdk |
Task state enum |
TaskStatus |
@a2a-js/sdk |
Task status structure |
TaskStore |
@a2a-js/sdk/server |
Task store interface |
ExecutionEventBus |
@a2a-js/sdk/server |
Event bus interface |
AgentExecutor |
@a2a-js/sdk/server |
Agent executor interface |
Examples
Full Configuration
import { AletheiaAgent, RedisTaskStore } from "@a2aletheia/sdk/agent";
import { ConsoleLogger } from "@a2aletheia/sdk";
import Redis from "ioredis";
const redis = new Redis("redis://localhost:6379");
const taskStore = new RedisTaskStore(redis, {
prefix: "myapp:task:",
ttlSeconds: 3600,
});
const agent = new AletheiaAgent({
name: "TranslatorAgent",
version: "1.0.0",
url: "https://translator.example.com",
description: "Translates text between languages",
skills: [
{
id: "translate",
name: "Translate Text",
description: "Translate text to another language",
tags: ["translation", "nlp"],
},
],
defaultInputModes: ["text/plain"],
defaultOutputModes: ["text/plain"],
capabilities: {
streaming: true,
pushNotifications: false,
},
iconUrl: "https://translator.example.com/icon.png",
documentationUrl: "https://translator.example.com/docs",
provider: {
organization: "Example Corp",
url: "https://example.com",
},
aletheiaExtensions: {
owner: "0x1234567890abcdef1234567890abcdef12345678",
did: "did:web:translator.example.com",
livenessPingUrl: "https://translator.example.com/health",
},
taskStore,
logger: new ConsoleLogger("info"),
logLevel: "info",
});
Text Handler
agent.handle(async (context, response) => {
const input = context.textContent;
response.text(`You said: ${input}`);
});
Data Handler
agent.handle(async (context, response) => {
const data = context.dataContent ?? { query: context.textContent };
const result = await processQuery(data);
response.data({
success: true,
result,
timestamp: new Date().toISOString(),
});
});
Streaming Handler
agent.handle(async (context, response) => {
response.working("Starting analysis...");
const chunks = await analyzeInChunks(context.textContent);
for (let i = 0; i < chunks.length; i++) {
response.artifact(
{
name: "analysis-results",
parts: [{ kind: "data", data: chunks[i] }],
},
{ append: i > 0, lastChunk: i === chunks.length - 1 }
);
}
response.done("Analysis complete!");
});
Event Subscription
// Track all messages
agent.on("message.received", (event) => {
console.log("Incoming:", event.data);
});
agent.on("message.sent", (event) => {
console.log("Outgoing:", event.data);
});
// Error tracking
agent.on("message.failed", (event) => {
errorTracker.capture(event.data);
});
// Wildcard for metrics
const unsubscribe = agent.on("*", (event) => {
metrics.increment(`agent.events.${event.type}`);
});
// Later: unsubscribe() to stop listening
Redis Integration
import { AletheiaAgent, RedisTaskStore } from "@a2aletheia/sdk/agent";
import IORedis from "ioredis";
// Using ioredis
const redis = new IORedis({
host: "localhost",
port: 6379,
password: "secret",
db: 0,
});
const taskStore = new RedisTaskStore(redis, {
prefix: "aletheia:task:",
ttlSeconds: 86400, // 24 hours
});
const agent = new AletheiaAgent({
name: "MyAgent",
version: "1.0.0",
url: "https://my-agent.example.com",
description: "An agent with persistent task storage",
skills: [{ id: "process", name: "Process", description: "Process requests" }],
taskStore,
});
await agent.start(4000);
Framework-Agnostic Usage (Hono)
import { AletheiaAgent } from "@a2aletheia/sdk/agent";
import { Hono } from "hono";
import { stream } from "hono/streaming";
const agent = new AletheiaAgent({
name: "HonoAgent",
version: "1.0.0",
url: "https://my-agent.example.com",
description: "Agent hosted on Hono",
skills: [{ id: "echo", name: "Echo", description: "Echo" }],
});
agent.handle(async (context, response) => {
response.text(`Echo: ${context.textContent}`);
});
const app = new Hono();
// Serve agent card
app.get("/.well-known/agent-card.json", (c) => {
return c.json(agent.getAgentCard());
});
// Handle A2A requests
app.post("/", async (c) => {
const body = await c.req.json();
const result = await agent.handleRequest(body);
if (Symbol.asyncIterator in result) {
return stream(c, async (s) => {
for await (const chunk of result) {
await s.write(JSON.stringify(chunk) + "\n");
}
});
}
return c.json(result);
});
export default app;