npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@inference-gateway/adk

v0.9.0

Published

Agent Development Kit (ADK) for the Inference Gateway, written in TypeScript.

Readme

⚠️ Early Stage: This project is in early bootstrap and the public API is not yet stable. Breaking changes are expected before 1.0. Pin a specific version in production and review CHANGELOG.md before upgrading.


Table of Contents


Overview

The TypeScript ADK (Agent Development Kit) is a Node.js library that simplifies building Agent-to-Agent (A2A) protocol compatible agents. A2A enables seamless communication between AI agents, allowing them to collaborate, delegate tasks, and share capabilities across different systems and providers.

It is the TypeScript sibling of the Go ADK and the Rust ADK, and ships as the npm package @inference-gateway/adk. Patterns mirror the Go implementation where the languages allow - for example, BUILD_AGENT_NAME / BUILD_AGENT_DESCRIPTION / BUILD_AGENT_VERSION mirror the Go ADK's BuildAgentName / BuildAgentDescription / BuildAgentVersion LD-flag injection points.

What is A2A?

Agent-to-Agent (A2A) is a standardized protocol that enables AI agents to:

  • Communicate with each other using a unified JSON-RPC interface
  • Delegate tasks to specialized agents with specific capabilities
  • Stream responses in real-time for better user experience
  • Discover capabilities through standardized agent cards

🚀 Quick Start

Installation

pnpm add @inference-gateway/adk
# or
npm install @inference-gateway/adk
# or
yarn add @inference-gateway/adk

Requires Node.js 24 LTS or newer. The package is ESM-only.

Hello, A2A

A minimal A2A agent that echoes every message it receives, plus a client that sends one message and waits for the task to complete. This is the smallest end-to-end usage of the ADK - the full runnable version with shutdown handling, message extraction, and dead-lettering lives in examples/minimal/.

server.ts - boot an A2A server with built-in message/send, tasks/get, and tasks/list handlers, plus an inline echo worker:

import {
  createA2AServer,
  createMessageSendHandler,
  createTaskGetHandler,
  createTaskListHandler,
  InMemoryTaskStorage,
  MESSAGE_SEND_METHOD,
  TASK_GET_METHOD,
  TASK_LIST_METHOD,
  TASK_STATE,
  transitionTask,
  type AgentCard,
} from '@inference-gateway/adk';

const card: AgentCard = {
  name: 'hello-agent',
  description: 'Echoes every message it receives.',
  version: '0.1.0',
  protocolVersion: '0.3.0',
  url: 'http://127.0.0.1:8080',
  defaultInputModes: ['text/plain'],
  defaultOutputModes: ['text/plain'],
  capabilities: {
    streaming: false,
    pushNotifications: false,
    stateTransitionHistory: false,
  },
  skills: [
    { id: 'echo', name: 'Echo', description: 'Echo input.', tags: ['echo'] },
  ],
};

const storage = new InMemoryTaskStorage();
const server = createA2AServer({ card });
server.registerMethod(
  MESSAGE_SEND_METHOD,
  createMessageSendHandler({ storage })
);
server.registerMethod(TASK_GET_METHOD, createTaskGetHandler({ storage }));
server.registerMethod(TASK_LIST_METHOD, createTaskListHandler({ storage }));

void runWorker();
await server.listen(8080, '127.0.0.1');
console.log('listening on http://127.0.0.1:8080');

async function runWorker(): Promise<void> {
  while (true) {
    const task = await storage.dequeue();
    const running = transitionTask(task, TASK_STATE.IN_PROGRESS);
    storage.updateActive(running);
    storage.storeDeadLetter(transitionTask(running, TASK_STATE.COMPLETED));
  }
}

client.ts - send a message and poll tasks/get until the task reaches a terminal state:

import {
  createA2AClient,
  isTerminal,
  type ManagedTaskState,
} from '@inference-gateway/adk';

const client = createA2AClient({ baseURL: 'http://127.0.0.1:8080' });

let task = await client.sendMessage({
  message: {
    messageId: crypto.randomUUID(),
    role: 'ROLE_USER',
    parts: [{ text: 'hello, agent' }],
  },
});

while (!isTerminal(task.status.state as ManagedTaskState)) {
  await new Promise((r) => setTimeout(r, 200));
  task = await client.getTask(task.id);
}

console.log(task);

Examples

Complete, runnable examples live under examples/:

  • examples/minimal/ - A2A server + client with no LLM. Demonstrates the full task lifecycle, a graceful echo worker with dead-lettering, and A2AClient polling. Mirrors the Go ADK's examples/minimal/.
  • examples/streaming/ - A2A server + client over SSE. Boots a server with capabilities.streaming = true, registers a custom message/stream executor that emits word-by-word delta events, and consumes the SSE frames from a plain fetch-based client. Mirrors the Go ADK's examples/streaming/.
  • examples/input-required/ - Pause + client-driven resume. The server pauses a task to ask for a missing piece of information (INPUT_REQUIRED), and the client detects the pause, sends a follow-up on the same contextId, and polls until completion. Mirrors the Go ADK's examples/input-required/.
  • examples/ai-powered/ - LLM-backed A2A agent with weather and time tools. Wires AgentBuilder + OpenAICompatibleLLMClient into DefaultBackgroundTaskHandler, dispatches tool calls in a chat-completion loop, and answers natural-language prompts through any OpenAI-compatible provider routed via the Inference Gateway. Mirrors the Go ADK's examples/ai-powered/.

Each example ships its own README with setup instructions.

✨ Key Features

Core Capabilities

  • 🤖 A2A Protocol Compliance - JSON-RPC 2.0 endpoint, agent-card discovery at /.well-known/agent-card.json, and /health liveness probe
  • 📬 Built-in Handlers - Drop-in message/send, tasks/get, and tasks/list JSON-RPC handlers backed by any TaskStorage
  • 🔌 Extensible JSON-RPC - Register custom methods on the per-server MethodRegistry
  • 🔁 Task Lifecycle - Strict state machine (SUBMITTED → WORKING → {INPUT_REQUIRED | COMPLETED | FAILED | CANCELLED}) with TaskTransitionError on invalid transitions
  • 🗄️ Pluggable Storage - Small TaskStorage interface with InMemoryTaskStorage included; queue / active / dead-letter semantics out of the box
  • 🛰️ A2A Client - A2AClient with sendMessage, getTask, getAgentCard, getHealth, configurable timeout, retry with exponential backoff, and a typed error taxonomy
  • 📇 AgentCard Loading - Load from file or JSON with ${VAR} env-placeholder resolution, optional shallow overrides, and required-field validation
  • 🏷️ Build-Time Metadata - Inject name / description / version at bundle or runtime (mirrors the Go ADK's BuildAgent* LD flags)
  • ☁️ CloudEvents v1.0 - createCloudEvent helper for wrapping agent events in a spec-compliant envelope
  • 📡 SSE Streaming - SSEStreamWriter for emitting Server-Sent Events with a configurable heartbeat

Developer Experience

  • 📦 ESM-only - Modern ES2024 bundle via tsup, targeted at Node 24 LTS+
  • 🛡️ Strict TypeScript - verbatimModuleSyntax, noUncheckedIndexedAccess, exactOptionalPropertyTypes, isolatedModules
  • 📚 Generated A2A Types - Types generated from the canonical inference-gateway/schemas at a pinned commit SHA, with a drift check enforced in CI
  • 🧪 Well Tested - Vitest suite covering the public surface; a dedicated drift test guards the generated A2A types
  • 🪶 Minimal Dependencies - Only hono + @hono/node-server at runtime

Status & Roadmap

The TypeScript ADK currently focuses on the core A2A protocol surface: message/send, tasks/get, tasks/list, AgentCard discovery, the task lifecycle state machine, in-memory storage, the retrying client, CloudEvents, and SSE. Capabilities that exist in the Go ADK but are not yet implemented here include: LLM client / multi-provider chat completion, streaming task handlers, additional JSON-RPC methods (tasks/cancel, tasks/resubscribe, tasks/pushNotificationConfig/*, agent/getAuthenticatedExtendedCard), Redis-backed storage, file artifacts (filesystem & MinIO), OIDC/OAuth authentication, TLS configuration, push notifications, and OpenTelemetry-based observability. The TS ADK tracks the Go ADK as the long-term feature target - contributions toward parity are welcome.

📖 API Reference

Core Components

A2AServer / createA2AServer

The main HTTP server. Exposes the JSON-RPC endpoint at DEFAULT_JSONRPC_PATH (POST /), an AgentCard discovery endpoint at AGENT_CARD_PATH (/.well-known/agent-card.json), and a liveness probe at HEALTH_PATH (/health). The endpoint mount points and the agent-card Cache-Control header (DEFAULT_AGENT_CARD_CACHE_CONTROL) are configurable via A2AServerConfig.

const server = createA2AServer({ card });
await server.listen(8080, '127.0.0.1');
// ...
await server.close();

A freshly constructed server already serves discovery and health - no method registration is required just to be reachable.

MethodRegistry and JSON-RPC dispatch

JSON-RPC methods are registered on a per-server MethodRegistry. Call server.registerMethod(name, handler) (or unregisterMethod / hasMethod / registeredMethods) to wire up methods. The lower-level helpers dispatch, createSuccessResponse, createErrorResponse, JSONRPCError, and JSONRPC_ERROR_CODES are exported as well - useful for custom transports or focused unit tests.

Built-in handlers

  • createMessageSendHandler({ storage }) registers as MESSAGE_SEND_METHOD (message/send). It accepts a JSON-RPC message/send request, creates a SUBMITTED task, enqueues it on the supplied TaskStorage, and returns the wire Task immediately. Your worker code dequeues and progresses the task.
  • createTaskGetHandler({ storage }) registers as TASK_GET_METHOD (tasks/get). It looks up the requested task across active and dead-letter storage and returns whatever it finds.
  • createTaskListHandler({ storage }) registers as TASK_LIST_METHOD (tasks/list). It returns tasks filtered by optional state / contextId, paginated with an opaque cursor and a limit clamped to maxLimit (default 100). The response shape is { tasks, nextCursor? }; nextCursor is omitted on the final page. Pagination is stable under concurrent inserts and deletes because the cursor is keyset-encoded on (createdAt, id).

These handlers are pure adapters between the JSON-RPC surface and a TaskStorage - no business logic lives in them.

Task lifecycle

Tasks are managed by an explicit state machine. Use:

  • createTask(input) to construct a new managed task
  • transitionTask(task, nextState, options?) to move it forward (throws TaskTransitionError on invalid transitions; options.message attaches an agent reply to status.message)
  • canTransition(from, to) to probe a transition without applying it
  • isTerminal(state) / isPaused(state) for state-class predicates
  • toWireTask(task) to convert from the internal ManagedTask to the wire-format Task returned over JSON-RPC
  • The TASK_STATE const for the canonical state literals

TaskStorage and InMemoryTaskStorage

TaskStorage is a queue-centric interface where tasks live in one of three locations:

  1. Queue - enqueued, waiting to be dequeued by a worker.
  2. Active - enqueued or in-flight (after dequeue, before terminal).
  3. Dead letter - terminal tasks (COMPLETED / FAILED / CANCELLED) retained for audit and lookup.

The included InMemoryTaskStorage is suitable for tests, local development, and single-instance deployments. Key methods:

  • enqueue(task) / dequeue(signal?) - FIFO queue, with optional AbortSignal for cancellation
  • updateActive(task) - persist a state transition without re-enqueueing
  • storeDeadLetter(task) - move a terminal task out of the active map
  • getTask(id) / listTasks(filter?) - read across active + dead-letter
  • getStats() - snapshot of storage health (counts by state, queue length, etc.)
  • cleanupCompleted(), deleteContext(id) - cleanup helpers

To plug in a different backend (Redis, Postgres, S3-backed), implement TaskStorage and pass your implementation to the message-send and task-get handlers.

A2AClient / createA2AClient

A typed client for calling A2A servers:

const client = createA2AClient({ baseURL: 'http://localhost:8080' });

const task = await client.sendMessage({ message });
const refresh = await client.getTask(task.id, { historyLength: 10 });
const card = await client.getAgentCard();
const health = await client.getHealth();

The client supports configurable per-attempt timeouts (DEFAULT_TIMEOUT_MS), exponential-backoff retries (withRetry, DEFAULT_RETRY_CONFIG, isRetryableError), custom headers, a pluggable fetch implementation, and a configurable User-Agent. Pass retry: false to disable retries entirely.

Errors are categorized for handling:

  • A2AHTTPError - non-2xx HTTP response
  • A2AJSONRPCError - JSON-RPC envelope error field
  • A2ATimeoutError - per-attempt timeout fired
  • A2ANetworkError - DNS / connection / unexpected fetch failure
  • A2AAbortError - caller-supplied signal aborted

All extend A2AClientError.

AgentCard loading

import {
  loadAgentCardFromFile,
  loadAgentCardFromJSON,
} from '@inference-gateway/adk';

const card = loadAgentCardFromFile('./agent.json', {
  env: process.env,
  overrides: { url: 'https://prod.example.com' },
});

The loader runs a four-step pipeline:

  1. Parse JSON.
  2. Resolve ${VAR} placeholders against options.env (defaults to process.env). A missing env var throws AgentCardLoadError rather than silently substituting an empty string.
  3. Shallow-merge options.overrides over the resolved object (overrides win).
  4. Validate the required-field subset (name, description, version, protocolVersion, defaultInputModes, defaultOutputModes, capabilities, skills) - throws AgentCardValidationError (with an optional field hint) on failure. Optional fields are deliberately left loose.

loadAgentCardFromFile is synchronous by design - it uses readFileSync and is meant for boot-time configuration. Do not call it on the request path.

CloudEvents v1.0 envelope

import {
  AGENT_EVENT_TYPE,
  createCloudEvent,
  DEFAULT_AGENT_EVENT_SOURCE,
} from '@inference-gateway/adk';

const evt = createCloudEvent({
  type: AGENT_EVENT_TYPE.TASK_STATUS_CHANGED,
  source: DEFAULT_AGENT_EVENT_SOURCE,
  data: { taskId, state: 'TASK_STATE_COMPLETED' },
});

AGENT_EVENT_TYPE is the canonical set of streaming event-type constants (DELTA, ITERATION_COMPLETED, TOOL_STARTED/COMPLETED/FAILED/RESULT, INPUT_REQUIRED, TASK_STATUS_CHANGED, TASK_INTERRUPTED, STREAM_FAILED) - identical to the Go ADK's Event* constants so a TS publisher and a Go consumer can interoperate without translation. Produces a CloudEvents v1.0-compliant envelope (CLOUDEVENTS_SPEC_VERSION = '1.0', served as CLOUDEVENTS_CONTENT_TYPE) - useful for forwarding agent events to event buses or webhook subscribers.

SSE streaming writer

SSEStreamWriter writes Server-Sent Events to a writable target, with a configurable heartbeat (DEFAULT_SSE_HEARTBEAT_MS) and the canonical SSE headers (SSE_HEADERS, SSE_CONTENT_TYPE). Useful for streaming task state transitions to long-lived HTTP clients.

Configuration

Most of the ADK is configured programmatically - via A2AServerConfig, A2AClientConfig, the handler option objects (MessageSendHandlerOptions, TaskGetHandlerOptions), and LoadAgentCardOptions. The only environment variables the library itself reads are the three build-metadata variables, plus the ${VAR} placeholders inside agent-card JSON.

Build-Time Agent Metadata

The ADK supports injecting agent name / description / version at build time, mirroring the Go ADK's BuildAgentName / BuildAgentDescription / BuildAgentVersion LD flags. Values are read from process.env once at first import and frozen into buildMetadata. An empty string means "not injected" - applyBuildMetadata(card) treats empty values as no-ops, so it is safe to call unconditionally.

| Variable | Default | Description | | ------------------------- | --------- | ---------------------------------------------------------------------- | | BUILD_AGENT_NAME | (empty) | Overrides card.name when non-empty (read once at module load) | | BUILD_AGENT_DESCRIPTION | (empty) | Overrides card.description when non-empty (read once at module load) | | BUILD_AGENT_VERSION | (empty) | Overrides card.version when non-empty (read once at module load) |

Two injection options:

  • Runtime - set BUILD_AGENT_NAME (etc.) in the environment before the module is first imported.

  • Bundle-time - use tsup's define option to substitute at build time:

    // tsup.config.ts
    import { defineConfig } from 'tsup';
    
    export default defineConfig({
      entry: ['src/index.ts'],
      format: ['esm'],
      define: {
        'process.env.BUILD_AGENT_NAME': JSON.stringify('weather-assistant'),
        'process.env.BUILD_AGENT_VERSION': JSON.stringify('1.2.3'),
      },
    });

Apply build metadata to a hand-written card:

import {
  applyBuildMetadata,
  createA2AServer,
  type AgentCard,
} from '@inference-gateway/adk';

const baseCard: AgentCard = {
  name: 'placeholder',
  description: 'placeholder',
  version: '0.0.0',
  protocolVersion: '0.3.0',
  url: 'http://127.0.0.1:8080',
  defaultInputModes: ['text/plain'],
  defaultOutputModes: ['text/plain'],
  capabilities: {
    streaming: false,
    pushNotifications: false,
    stateTransitionHistory: false,
  },
  skills: [],
};

const card = applyBuildMetadata(baseCard);
const server = createA2AServer({ card });

See the Container Image section for containerized builds that wire these variables through.

Agent-card ${VAR} placeholders

Inside an agent-card JSON file passed to loadAgentCardFromFile / loadAgentCardFromJSON, any string of the form ${SOME_ENV_VAR} is resolved against options.env (defaulting to process.env) at load time. A missing variable throws AgentCardLoadError - there is no silent fallback. Example:

{
  "name": "${A2A_AGENT_NAME}",
  "description": "Production agent in ${ENVIRONMENT}",
  "version": "0.1.0",
  "protocolVersion": "0.3.0",
  "url": "${A2A_AGENT_URL}",
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"],
  "capabilities": {
    "streaming": false,
    "pushNotifications": false,
    "stateTransitionHistory": false
  },
  "skills": []
}

For everything else - port, host, JSON-RPC path, agent-card cache-control, handler options, client retry / timeout / fetch - pass values directly to createA2AServer, createA2AClient, and the handler factories.

🔧 Advanced Usage

  • Custom JSON-RPC methods - call server.registerMethod(name, handler) with any MethodHandler to extend the server beyond the built-in message/send, tasks/get, and tasks/list. The MethodContext passed to handlers carries the JSON-RPC request id and an AbortSignal tied to the HTTP connection.
  • Custom task handlers - implement the TaskHandler (background) or StreamableTaskHandler (streaming) interface to ship arbitrary agent logic. See Custom task handlers below.
  • Custom storage backends - implement the TaskStorage interface and pass your implementation into createMessageSendHandler({ storage }), createTaskGetHandler({ storage }), and createTaskListHandler({ storage }). Anything that satisfies the interface - Redis, Postgres, S3-backed - drops in.
  • Tuning client behavior - A2AClientConfig exposes timeoutMs, retry (a partial RetryConfig or false), headers, fetch, userAgent, and overrides for jsonRpcPath / agentCardPath / healthPath. Call withRetry directly when you want to apply the same retry policy outside the client.
  • Bundle-time metadata injection - use tsup's define option to bake BUILD_AGENT_NAME / _DESCRIPTION / _VERSION into the bundled output instead of relying on the runtime environment. See Build-Time Agent Metadata above.
  • CloudEvents forwarding - wrap your task-state transitions in createCloudEvent and POST them to a webhook or message bus for downstream subscribers, using the spec-compliant CLOUDEVENTS_CONTENT_TYPE.

Custom task handlers

TaskHandler and StreamableTaskHandler let you plug arbitrary agent logic into the server via A2AServerBuilder. Both mirror the Go ADK's server/task_handler.go interfaces; the TypeScript variant uses an AbortSignal on the context for cancellation instead of Go's context.Context.

import {
  A2AServerBuilder,
  AGENT_EVENT_TYPE,
  BaseStreamableTaskHandler,
  BaseTaskHandler,
  TASK_STATE,
  createCloudEvent,
  transitionTask,
  type AgentCard,
  type CloudEvent,
  type ManagedTask,
  type Message,
  type TaskHandlerContext,
} from '@inference-gateway/adk';

// Background handler - return the updated task once processing finishes.
class EchoTaskHandler extends BaseTaskHandler {
  async handleTask(
    _ctx: TaskHandlerContext,
    task: ManagedTask,
    _message: Message
  ): Promise<ManagedTask> {
    let next = task;
    if (next.state === TASK_STATE.PENDING) {
      next = transitionTask(next, TASK_STATE.IN_PROGRESS);
    }
    return transitionTask(next, TASK_STATE.COMPLETED);
  }
}

// Streaming handler - yield CloudEvents as the task progresses.
class EchoStreamHandler extends BaseStreamableTaskHandler {
  async *handleStreamingTask(
    ctx: TaskHandlerContext,
    task: ManagedTask,
    message: Message
  ): AsyncIterable<CloudEvent> {
    if (ctx.signal.aborted) return;
    yield createCloudEvent({
      type: AGENT_EVENT_TYPE.DELTA,
      subject: task.id,
      data: {
        messageId: crypto.randomUUID(),
        role: 'ROLE_AGENT',
        contextId: task.contextId,
        taskId: task.id,
        parts: [{ text: 'echo: ' + (message.parts[0]?.text ?? '') }],
      },
    });
  }
}

const card: AgentCard = /* ... */;

const server = new A2AServerBuilder({})
  .withAgentCard(card)
  .withTaskHandler(new EchoTaskHandler())
  // Or, for a streaming agent card (capabilities.streaming === true):
  // .withStreamableTaskHandler(new EchoStreamHandler())
  .build();

await server.listen(8080, '127.0.0.1');

Handler contracts:

  • Both interfaces receive a TaskHandlerContext whose signal aborts when the originating request is cancelled (client disconnect, deadline, shutdown). Propagate it to LLM calls, tool dispatches, and fetches so cancellation actually unwinds.
  • TaskHandler.handleTask returns the updated ManagedTask. Use transitionTask to advance the state machine; terminal states (COMPLETED / FAILED / CANCELLED) tell the worker the task is done.
  • StreamableTaskHandler.handleStreamingTask yields raw CloudEvents that the framework forwards to the SSE response verbatim. The pipeline emits the initial IN_PROGRESS task.status.changed frame before your handler runs and the terminal status frame after it returns, so you only need to yield the in-flight payload events (AGENT_EVENT_TYPE.DELTA, TOOL_*, ITERATION_COMPLETED, etc.).
  • setAgent(agent) is called by the builder when an OpenAICompatibleAgent has been registered via withAgent(...) (now or later). BaseTaskHandler / BaseStreamableTaskHandler give you free setAgent / getAgent accessors so concrete subclasses only need to implement the handle*Task method.

🌐 A2A Ecosystem

This ADK is the TypeScript implementation of the Agent-to-Agent (A2A) protocol within the Inference Gateway ecosystem.

Related Projects

  • Inference Gateway - Unified API gateway for AI providers
  • Go ADK - Go A2A Development Kit (the most feature-complete sibling)
  • Rust ADK - Rust A2A Development Kit
  • Go SDK - Go client library for Inference Gateway
  • TypeScript SDK - TypeScript/JavaScript client library
  • Python SDK - Python client library
  • Rust SDK - Rust client library
  • Schemas - Canonical A2A JSON Schemas (source of truth for generated types)

A2A Agents

📋 Requirements

  • Node.js: 24 LTS or later
  • pnpm: 10.0 or later (10.18.0 is pinned via package.json#packageManager)
  • Dependencies: see package.json - runtime depends only on hono and @hono/node-server

📦 Container Image

A minimal multi-stage Dockerfile that produces an OCI-compliant container image for a TypeScript A2A agent built on top of this ADK. The same Dockerfile works with docker build, podman build, buildah, kaniko, or any other OCI-compatible build tool - there's nothing Docker-specific in it. Build-time agent metadata is injected via ARGENV, which the ADK reads on first import:

# --- Builder ---
FROM node:24-alpine AS builder

ARG AGENT_NAME="My A2A Agent"
ARG AGENT_DESCRIPTION="A custom A2A agent built with the TypeScript ADK"
ARG AGENT_VERSION="0.1.0"

WORKDIR /app

RUN corepack enable && corepack prepare [email protected] --activate

COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

COPY . .
ENV BUILD_AGENT_NAME=${AGENT_NAME} \
    BUILD_AGENT_DESCRIPTION=${AGENT_DESCRIPTION} \
    BUILD_AGENT_VERSION=${AGENT_VERSION}
RUN pnpm build

# --- Runtime ---
FROM node:24-alpine

ARG AGENT_NAME
ARG AGENT_DESCRIPTION
ARG AGENT_VERSION

RUN addgroup -g 1001 -S a2a && adduser -u 1001 -S agent -G a2a
WORKDIR /home/agent
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
RUN chown -R agent:a2a /home/agent
USER agent

ENV BUILD_AGENT_NAME=${AGENT_NAME} \
    BUILD_AGENT_DESCRIPTION=${AGENT_DESCRIPTION} \
    BUILD_AGENT_VERSION=${AGENT_VERSION}

CMD ["node", "dist/index.js"]

Build with custom metadata - docker build, podman build, etc. all take the same flags:

docker build \
  --build-arg AGENT_NAME="Weather Assistant" \
  --build-arg AGENT_DESCRIPTION="AI-powered weather forecasting agent" \
  --build-arg AGENT_VERSION="0.1.1" \
  -t my-a2a-agent .

📄 License

This project is licensed under the Apache 2.0 License. See the LICENSE file for details.

🤝 Contributing

Contributions are welcome - whether you're fixing bugs, adding features, improving documentation, or helping bring more of the Go ADK feature surface to TypeScript.

Please see the Contributing Guide for:

  • 🚀 Getting Started - Prerequisites, Flox/manual dev environment setup
  • 📋 Development Workflow - pnpm scripts, regenerating A2A types, the inner loop
  • 🎯 Coding Guidelines - TypeScript strictness flags, style, and comment conventions
  • 🛠️ Making Changes - Branch naming and Conventional Commit format
  • 🧪 Testing Guidelines - Vitest layout, running a single test, the drift check
  • 🔄 Continuous Integration - CI matrix and required status checks
  • 🚢 Releases - How semantic-release computes the next version
  • 🔄 Pull Request Process - Pre-submit checklist and review flow

Quick start for contributors:

# Fork the repo on GitHub, then:
git clone https://github.com/your-username/typescript-adk.git
cd typescript-adk
pnpm install
pnpm test

For questions or help getting started, please open a discussion or file an issue.

📞 Support

Issues & Questions

🔗 Resources

Documentation