{"id":50172306,"url":"https://github.com/inference-gateway/typescript-adk","last_synced_at":"2026-06-10T15:00:37.711Z","repository":{"id":360510956,"uuid":"1029184165","full_name":"inference-gateway/typescript-adk","owner":"inference-gateway","description":"An Agent Development Kit (ADK) allowing for seamless creation of A2A-compatible agents written in TypeScript","archived":false,"fork":false,"pushed_at":"2026-06-03T17:05:31.000Z","size":809,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-03T17:13:37.382Z","etag":null,"topics":["a2a","a2a-protocol","adk","inference-gateway","typescript"],"latest_commit_sha":null,"homepage":"https://docs.inference-gateway.com/typescript-adk","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/inference-gateway.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-30T16:40:44.000Z","updated_at":"2026-06-03T15:42:31.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/inference-gateway/typescript-adk","commit_stats":null,"previous_names":["inference-gateway/typescript-adk"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/inference-gateway/typescript-adk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inference-gateway%2Ftypescript-adk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inference-gateway%2Ftypescript-adk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inference-gateway%2Ftypescript-adk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inference-gateway%2Ftypescript-adk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/inference-gateway","download_url":"https://codeload.github.com/inference-gateway/typescript-adk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inference-gateway%2Ftypescript-adk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34157453,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["a2a","a2a-protocol","adk","inference-gateway","typescript"],"created_at":"2026-05-25T00:00:30.325Z","updated_at":"2026-06-10T15:00:37.704Z","avatar_url":"https://github.com/inference-gateway.png","language":"TypeScript","funding_links":[],"categories":["🛠️ Tools \u0026 Libraries"],"sub_categories":["🏗️ Frameworks \u0026 SDKs"],"readme":"\u003ch1 align=\"center\"\u003eAgent Development Kit (ADK) - TypeScript\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eBuild powerful, interoperable AI agents with the Agent-to-Agent (A2A) protocol - in TypeScript.\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003e ⚠️ **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`](./CHANGELOG.md) before upgrading.\n\n\u003cp align=\"center\"\u003e\n  \u003c!-- CI Status Badge --\u003e\n  \u003ca href=\"https://github.com/inference-gateway/typescript-adk/actions/workflows/ci.yml?query=branch%3Amain\"\u003e\n    \u003cimg src=\"https://github.com/inference-gateway/typescript-adk/actions/workflows/ci.yml/badge.svg?branch=main\" alt=\"CI Status\"/\u003e\n  \u003c/a\u003e\n  \u003c!-- npm Version --\u003e\n  \u003ca href=\"https://www.npmjs.com/package/@inference-gateway/adk\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/@inference-gateway/adk?color=blue\u0026style=flat-square\" alt=\"npm version\"/\u003e\n  \u003c/a\u003e\n  \u003c!-- License Badge --\u003e\n  \u003ca href=\"./LICENSE\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/l/@inference-gateway/adk?color=blue\u0026style=flat-square\" alt=\"License\"/\u003e\n  \u003c/a\u003e\n  \u003c!-- Node Version --\u003e\n  \u003cimg src=\"https://img.shields.io/node/v/@inference-gateway/adk?style=flat-square\" alt=\"Node version\"/\u003e\n\u003c/p\u003e\n\n---\n\n## Table of Contents\n\n- [Overview](#overview)\n  - [What is A2A?](#what-is-a2a)\n- [🚀 Quick Start](#-quick-start)\n  - [Installation](#installation)\n  - [Hello, A2A](#hello-a2a)\n  - [Examples](#examples)\n- [✨ Key Features](#-key-features)\n- [📖 API Reference](#-api-reference)\n  - [Core Components](#core-components)\n  - [Configuration](#configuration)\n    - [Build-Time Agent Metadata](#build-time-agent-metadata)\n    - [Agent-card `${VAR}` placeholders](#agent-card-var-placeholders)\n- [🔧 Advanced Usage](#-advanced-usage)\n- [🌐 A2A Ecosystem](#-a2a-ecosystem)\n- [📋 Requirements](#-requirements)\n- [📦 Container Image](#-container-image)\n- [📄 License](#-license)\n- [🤝 Contributing](#-contributing)\n- [📞 Support](#-support)\n- [🔗 Resources](#-resources)\n\n---\n\n## Overview\n\nThe **TypeScript ADK (Agent Development Kit)** is a Node.js library that simplifies building [Agent-to-Agent (A2A) protocol](https://github.com/inference-gateway/schemas/tree/main/a2a) compatible agents. A2A enables seamless communication between AI agents, allowing them to collaborate, delegate tasks, and share capabilities across different systems and providers.\n\nIt is the TypeScript sibling of the [Go ADK](https://github.com/inference-gateway/adk) and the [Rust ADK](https://github.com/inference-gateway/rust-adk), and ships as the npm package [`@inference-gateway/adk`](https://www.npmjs.com/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.\n\n### What is A2A?\n\nAgent-to-Agent (A2A) is a standardized protocol that enables AI agents to:\n\n- **Communicate** with each other using a unified JSON-RPC interface\n- **Delegate tasks** to specialized agents with specific capabilities\n- **Stream responses** in real-time for better user experience\n- **Discover capabilities** through standardized agent cards\n\n## 🚀 Quick Start\n\n### Installation\n\n```sh\npnpm add @inference-gateway/adk\n# or\nnpm install @inference-gateway/adk\n# or\nyarn add @inference-gateway/adk\n```\n\nRequires **Node.js 24 LTS or newer**. The package is ESM-only.\n\n### Hello, A2A\n\nA 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/`](./examples/minimal/).\n\n**`server.ts`** - boot an A2A server with built-in `message/send`, `tasks/get`, and `tasks/list` handlers, plus an inline echo worker:\n\n```ts\nimport {\n  createA2AServer,\n  createMessageSendHandler,\n  createTaskGetHandler,\n  createTaskListHandler,\n  InMemoryTaskStorage,\n  MESSAGE_SEND_METHOD,\n  TASK_GET_METHOD,\n  TASK_LIST_METHOD,\n  TASK_STATE,\n  transitionTask,\n  type AgentCard,\n} from '@inference-gateway/adk';\n\nconst card: AgentCard = {\n  name: 'hello-agent',\n  description: 'Echoes every message it receives.',\n  version: '0.1.0',\n  protocolVersion: '0.3.0',\n  url: 'http://127.0.0.1:8080',\n  defaultInputModes: ['text/plain'],\n  defaultOutputModes: ['text/plain'],\n  capabilities: {\n    streaming: false,\n    pushNotifications: false,\n    stateTransitionHistory: false,\n  },\n  skills: [\n    { id: 'echo', name: 'Echo', description: 'Echo input.', tags: ['echo'] },\n  ],\n};\n\nconst storage = new InMemoryTaskStorage();\nconst server = createA2AServer({ card });\nserver.registerMethod(\n  MESSAGE_SEND_METHOD,\n  createMessageSendHandler({ storage })\n);\nserver.registerMethod(TASK_GET_METHOD, createTaskGetHandler({ storage }));\nserver.registerMethod(TASK_LIST_METHOD, createTaskListHandler({ storage }));\n\nvoid runWorker();\nawait server.listen(8080, '127.0.0.1');\nconsole.log('listening on http://127.0.0.1:8080');\n\nasync function runWorker(): Promise\u003cvoid\u003e {\n  while (true) {\n    const task = await storage.dequeue();\n    const running = transitionTask(task, TASK_STATE.IN_PROGRESS);\n    storage.updateActive(running);\n    storage.storeDeadLetter(transitionTask(running, TASK_STATE.COMPLETED));\n  }\n}\n```\n\n**`client.ts`** - send a message and poll `tasks/get` until the task reaches a terminal state:\n\n```ts\nimport {\n  createA2AClient,\n  isTerminal,\n  type ManagedTaskState,\n} from '@inference-gateway/adk';\n\nconst client = createA2AClient({ baseURL: 'http://127.0.0.1:8080' });\n\nlet task = await client.sendMessage({\n  message: {\n    messageId: crypto.randomUUID(),\n    role: 'ROLE_USER',\n    parts: [{ text: 'hello, agent' }],\n  },\n});\n\nwhile (!isTerminal(task.status.state as ManagedTaskState)) {\n  await new Promise((r) =\u003e setTimeout(r, 200));\n  task = await client.getTask(task.id);\n}\n\nconsole.log(task);\n```\n\n### Examples\n\nComplete, runnable examples live under [`examples/`](./examples/):\n\n- **[`examples/minimal/`](./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/`](https://github.com/inference-gateway/adk/tree/main/examples/minimal).\n- **[`examples/streaming/`](./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/`](https://github.com/inference-gateway/adk/tree/main/examples/streaming).\n- **[`examples/input-required/`](./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/`](https://github.com/inference-gateway/adk/tree/main/examples/input-required).\n- **[`examples/ai-powered/`](./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/`](https://github.com/inference-gateway/adk/tree/main/examples/ai-powered).\n- **[`examples/queue-storage/`](./examples/queue-storage/)** - Two variants of the same echo agent showing how to swap storage backends: [`in-memory/`](./examples/queue-storage/in-memory/) uses `InMemoryTaskStorage` (zero ops, no persistence) and [`redis/`](./examples/queue-storage/redis/) uses `RedisTaskStorage.connect()` with a bundled `docker-compose.yml` for local Redis (queue and dead-letter survive restarts, multi-instance fan-out via `BRPOP`). Mirrors the Go ADK's [`examples/queue-storage/`](https://github.com/inference-gateway/adk/tree/main/examples/queue-storage).\n- **[`examples/usage-metadata/`](./examples/usage-metadata/)** - Per-task token usage and execution stats serialized into `task.metadata.usage` / `task.metadata.execution_stats` on completion. Exercises both `DefaultBackgroundTaskHandler` and `DefaultStreamingTaskHandler` with `setEnableUsageMetadata(true)`; the client prints the resulting metadata from both `tasks/get` and the terminal SSE status frame. Mirrors the Go ADK's [`examples/usage-metadata/`](https://github.com/inference-gateway/adk/tree/main/examples/usage-metadata).\n- **[`examples/tls-server/`](./examples/tls-server/)** - A2A server + client over HTTPS with a self-signed cert. Boots `A2AServer` with `tls: loadServerTLSConfigFromEnv()`, drives the client over HTTPS via `tls: { caPath }`, and includes a `generate-certs.sh` helper plus Docker / Kubernetes cert-mount recipes. Mirrors the Go ADK's [`examples/tls-server/`](https://github.com/inference-gateway/adk/tree/main/examples/tls-server).\n\nEach example ships its own README with setup instructions.\n\n## ✨ Key Features\n\n### Core Capabilities\n\n- 🤖 **A2A Protocol Compliance** - JSON-RPC 2.0 endpoint, agent-card discovery at `/.well-known/agent-card.json`, and `/health` liveness probe\n- 📬 **Built-in Handlers** - Drop-in `message/send`, `tasks/get`, and `tasks/list` JSON-RPC handlers backed by any `TaskStorage`\n- 🔌 **Extensible JSON-RPC** - Register custom methods on the per-server `MethodRegistry`\n- 🔁 **Task Lifecycle** - Strict state machine (`SUBMITTED → WORKING → {INPUT_REQUIRED | COMPLETED | FAILED | CANCELLED}`) with `TaskTransitionError` on invalid transitions\n- 🗄️ **Pluggable Storage** - Small `TaskStorage` interface with `InMemoryTaskStorage` included; queue / active / dead-letter semantics out of the box, plus a `runTaskStorageConformance` test factory (exported from `@inference-gateway/adk/testing`) so any backend can verify itself against the contract\n- 🛰️ **A2A Client** - `A2AClient` with `sendMessage`, `getTask`, `getAgentCard`, `getHealth`, configurable timeout, retry with exponential backoff, and a typed error taxonomy\n- 📇 **AgentCard Loading** - Load from file or JSON with `${VAR}` env-placeholder resolution, optional shallow overrides, and required-field validation\n- 🏷️ **Build-Time Metadata** - Inject `name` / `description` / `version` at bundle or runtime (mirrors the Go ADK's `BuildAgent*` LD flags)\n- ☁️ **CloudEvents v1.0** - `createCloudEvent` helper for wrapping agent events in a spec-compliant envelope\n- 📡 **SSE Streaming** - `SSEStreamWriter` for emitting Server-Sent Events with a configurable heartbeat\n- 🔒 **TLS** - Server-side HTTPS termination via `tls: { certPath, keyPath }` (driven by `TLS_ENABLE` / `TLS_CERT_PATH` / `TLS_KEY_PATH` env vars); outbound `ClientTLSConfig` on both `A2AClient` and `OpenAICompatibleLLMClient` for self-signed or mTLS peers. Built on `node:https` / `node:tls` - no third-party TLS dependency.\n\n### Developer Experience\n\n- 📦 **ESM-only** - Modern ES2024 bundle via `tsup`, targeted at Node 24 LTS+\n- 🛡️ **Strict TypeScript** - `verbatimModuleSyntax`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `isolatedModules`\n- 📚 **Generated A2A Types** - Types generated from the canonical [`inference-gateway/schemas`](https://github.com/inference-gateway/schemas) at a pinned commit SHA, with a drift check enforced in CI\n- 🧪 **Well Tested** - Vitest suite covering the public surface; a dedicated drift test guards the generated A2A types\n- 🪶 **Minimal Dependencies** - Only `hono` + `@hono/node-server` at runtime\n\n### Status \u0026 Roadmap\n\nThe 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 and Redis-backed storage, the retrying client, CloudEvents, and SSE. Capabilities that exist in the [Go ADK](https://github.com/inference-gateway/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`), file artifacts (filesystem \u0026 MinIO), OIDC/OAuth authentication, push notifications, and OpenTelemetry-based observability. The TS ADK tracks the Go ADK as the long-term feature target - contributions toward parity are welcome.\n\n## 📖 API Reference\n\n### Core Components\n\n#### `A2AServer` / `createA2AServer`\n\nThe 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`.\n\n```ts\nconst server = createA2AServer({ card });\nawait server.listen(8080, '127.0.0.1');\n// ...\nawait server.close();\n```\n\nA freshly constructed server already serves discovery and health - no method registration is required just to be reachable.\n\n#### `MethodRegistry` and JSON-RPC dispatch\n\nJSON-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.\n\n#### Built-in handlers\n\n- **`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.\n- **`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.\n- **`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)`.\n\nThese handlers are pure adapters between the JSON-RPC surface and a `TaskStorage` - no business logic lives in them.\n\n#### Task lifecycle\n\nTasks are managed by an explicit state machine. Use:\n\n- `createTask(input)` to construct a new managed task\n- `transitionTask(task, nextState, options?)` to move it forward (throws `TaskTransitionError` on invalid transitions; `options.message` attaches an agent reply to `status.message`)\n- `canTransition(from, to)` to probe a transition without applying it\n- `isTerminal(state)` / `isPaused(state)` for state-class predicates\n- `toWireTask(task)` to convert from the internal `ManagedTask` to the wire-format `Task` returned over JSON-RPC\n- The `TASK_STATE` const for the canonical state literals\n\n#### `TaskStorage` and `InMemoryTaskStorage`\n\n`TaskStorage` is a queue-centric interface where tasks live in one of three locations:\n\n1. **Queue** - enqueued, waiting to be dequeued by a worker.\n2. **Active** - enqueued or in-flight (after `dequeue`, before terminal).\n3. **Dead letter** - terminal tasks (`COMPLETED` / `FAILED` / `CANCELLED`) retained for audit and lookup.\n\nThe included `InMemoryTaskStorage` is suitable for tests, local development, and single-instance deployments. Key methods:\n\n- `enqueue(task)` / `dequeue(signal?)` - FIFO queue, with optional `AbortSignal` for cancellation\n- `updateActive(task)` - persist a state transition without re-enqueueing\n- `storeDeadLetter(task)` - move a terminal task out of the active map\n- `getTask(id)` / `listTasks(filter?)` - read across active + dead-letter\n- `getStats()` - snapshot of storage health (counts by state, queue length, etc.)\n- `cleanupCompleted()`, `deleteContext(id)` - cleanup helpers\n\nTo plug in a different backend (Postgres, S3-backed, ...), implement `TaskStorage` and pass your implementation to the message-send and task-get handlers. A Redis-backed implementation ships out of the box - see below.\n\n#### `RedisTaskStorage`\n\nFor multi-instance deployments that need a shared queue, the package also exports `RedisTaskStorage`, a `TaskStorage` implementation backed by Redis 6+ via [`ioredis`](https://github.com/redis/ioredis) (declared as an optional peer dependency - install it alongside the ADK to use this backend).\n\nBecause the `TaskStorage` interface is synchronous (`dequeue` aside), the Redis backend keeps an in-memory write-through mirror: sync reads serve from memory while writes update both memory and Redis, and the blocking `BRPOP` loop is the cross-instance shared-queue source of truth. Single-instance deployments get persistence across restarts (state is hydrated from Redis on `connect`); multi-instance deployments get a shared queue plus eventually-consistent state visibility across replicas.\n\n```ts\nimport {\n  RedisTaskStorage,\n  redisConnectOptionsFromEnv,\n} from '@inference-gateway/adk';\n\nconst storage = await RedisTaskStorage.connect(redisConnectOptionsFromEnv());\n// ...wire `storage` into the same handlers as InMemoryTaskStorage\nawait storage.disconnect(); // on shutdown\n```\n\nConfiguration is read from `REDIS_URL` (preferred) or `REDIS_HOST` / `REDIS_PORT` / `REDIS_PASSWORD` / `REDIS_DB`. Pass a `keyPrefix` to isolate multiple ADK deployments on a shared Redis (defaults to `\"a2a:\"` to match the Go ADK).\n\n#### Writing a custom storage backend\n\n`TaskStorage` is the contract. Anything that satisfies the interface drops into the built-in handlers. A skeleton implementation looks like:\n\n```ts\nimport type {\n  ManagedTask,\n  PushNotificationConfig,\n  StoredPushNotificationConfig,\n  TaskListFilter,\n  TaskStorage,\n  TaskStorageStats,\n} from '@inference-gateway/adk';\n\nexport class MyTaskStorage implements TaskStorage {\n  enqueue(task: ManagedTask): void {\n    /* push onto the FIFO queue + register as active */\n  }\n  dequeue(signal?: AbortSignal): Promise\u003cManagedTask\u003e {\n    /* await the head of the queue, abort on signal */\n  }\n  queueLength(): number {\n    /* current FIFO length */\n  }\n  removeFromQueue(taskId: string): boolean {\n    /* drop a PENDING task before it is picked up */\n  }\n\n  createActive(task: ManagedTask): void {\n    /* register without enqueueing; throw if id is already active */\n  }\n  getActive(taskId: string): ManagedTask | undefined {\n    /* look up an active task */\n  }\n  updateActive(task: ManagedTask): void {\n    /* persist a state transition; throw if id is unknown */\n  }\n  storeDeadLetter(task: ManagedTask): void {\n    /* move out of active and into dead-letter */\n  }\n\n  getTask(taskId: string): ManagedTask | undefined {\n    /* read across active + dead-letter */\n  }\n  listTasks(filter?: TaskListFilter): ManagedTask[] {\n    /* FIFO-ordered by createdAt, offset/limit pagination */\n  }\n\n  getContexts(): string[] {\n    /* every context with at least one task */\n  }\n  deleteContext(contextId: string): number {\n    /* cascade-delete queue + active + dead-letter; return count */\n  }\n  cleanupCompleted(): number {\n    /* drop terminal dead-letter tasks; return count */\n  }\n\n  getStats(): TaskStorageStats {\n    /* counts grouped by state, queue length, context stats */\n  }\n\n  setPushConfig(\n    taskId: string,\n    config: PushNotificationConfig\n  ): StoredPushNotificationConfig {\n    /* persist; mint UUID when caller omits config.id */\n  }\n  getPushConfig(\n    taskId: string,\n    configId: string\n  ): StoredPushNotificationConfig | undefined {\n    /* ... */\n  }\n  listPushConfigs(taskId: string): StoredPushNotificationConfig[] {\n    /* fresh array, insertion order */\n  }\n  deletePushConfig(taskId: string, configId: string): boolean {\n    /* return whether anything was removed */\n  }\n}\n```\n\nThree contract details that are easy to miss when porting from another stack:\n\n1. **Enqueue registers the task as active.** A single `enqueue(task)` call must make the task visible to `getActive(task.id)` _and_ to `dequeue()`. The dequeued task stays active until the caller transitions it to a terminal state and calls `storeDeadLetter`.\n2. **`dequeue` blocks.** When the queue is empty, return a `Promise` that resolves on the next `enqueue`. Multiple parked waiters must be handed off in FIFO arrival order. An aborted waiter must not consume the next enqueued task. The included `InMemoryTaskStorage` shows the pattern.\n3. **`setPushConfig` assigns ids.** If the caller omits `config.id` (or passes an empty string), generate a UUID and populate it on the returned value. Callers that need the generated id read it from the return value.\n\nThen verify against the conformance suite. The `@inference-gateway/adk/testing` subpath exports a `runTaskStorageConformance` factory that the in-memory backend runs against itself; any custom backend can run the same suite:\n\n```ts\nimport { describe } from 'vitest';\nimport { runTaskStorageConformance } from '@inference-gateway/adk/testing';\nimport { MyTaskStorage } from './my-task-storage.js';\n\ndescribe('MyTaskStorage - conformance', () =\u003e {\n  runTaskStorageConformance({\n    createStorage: () =\u003e new MyTaskStorage(),\n    // Optional - close connections, flush state, etc.\n    cleanup: (storage) =\u003e (storage as MyTaskStorage).close(),\n  });\n});\n```\n\n`createStorage` is called from `beforeEach`, so each test gets a fresh, empty storage. The factory may be async to allow opening a Redis or Postgres connection before returning. `vitest` is a peer dependency of the testing subpath - install it alongside `@inference-gateway/adk` to use the conformance suite.\n\nPlug your backend into the built-in handlers exactly like the in-memory one:\n\n```ts\nconst storage = new MyTaskStorage();\nserver.registerMethod(\n  MESSAGE_SEND_METHOD,\n  createMessageSendHandler({ storage })\n);\nserver.registerMethod(TASK_GET_METHOD, createTaskGetHandler({ storage }));\nserver.registerMethod(TASK_LIST_METHOD, createTaskListHandler({ storage }));\n```\n\n#### `A2AClient` / `createA2AClient`\n\nA typed client for calling A2A servers:\n\n```ts\nconst client = createA2AClient({ baseURL: 'http://localhost:8080' });\n\nconst task = await client.sendMessage({ message });\nconst refresh = await client.getTask(task.id, { historyLength: 10 });\nconst card = await client.getAgentCard();\nconst health = await client.getHealth();\n```\n\nThe 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.\n\nErrors are categorized for handling:\n\n- `A2AHTTPError` - non-2xx HTTP response\n- `A2AJSONRPCError` - JSON-RPC envelope `error` field\n- `A2ATimeoutError` - per-attempt timeout fired\n- `A2ANetworkError` - DNS / connection / unexpected fetch failure\n- `A2AAbortError` - caller-supplied `signal` aborted\n\nAll extend `A2AClientError`.\n\n#### AgentCard loading\n\n```ts\nimport {\n  loadAgentCardFromFile,\n  loadAgentCardFromJSON,\n} from '@inference-gateway/adk';\n\nconst card = loadAgentCardFromFile('./agent.json', {\n  env: process.env,\n  overrides: { url: 'https://prod.example.com' },\n});\n```\n\nThe loader runs a four-step pipeline:\n\n1. Parse JSON.\n2. Resolve `${VAR}` placeholders against `options.env` (defaults to `process.env`). **A missing env var throws `AgentCardLoadError`** rather than silently substituting an empty string.\n3. Shallow-merge `options.overrides` over the resolved object (overrides win).\n4. 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.\n\n`loadAgentCardFromFile` is **synchronous by design** - it uses `readFileSync` and is meant for boot-time configuration. Do not call it on the request path.\n\n#### CloudEvents v1.0 envelope\n\n```ts\nimport {\n  AGENT_EVENT_TYPE,\n  createCloudEvent,\n  DEFAULT_AGENT_EVENT_SOURCE,\n} from '@inference-gateway/adk';\n\nconst evt = createCloudEvent({\n  type: AGENT_EVENT_TYPE.TASK_STATUS_CHANGED,\n  source: DEFAULT_AGENT_EVENT_SOURCE,\n  data: { taskId, state: 'TASK_STATE_COMPLETED' },\n});\n```\n\n`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](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md)-compliant envelope (`CLOUDEVENTS_SPEC_VERSION = '1.0'`, served as `CLOUDEVENTS_CONTENT_TYPE`) - useful for forwarding agent events to event buses or webhook subscribers.\n\n#### SSE streaming writer\n\n`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.\n\n### Configuration\n\nMost 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.\n\n#### Build-Time Agent Metadata\n\nThe 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.\n\n| Variable                  | Default   | Description                                                            |\n| ------------------------- | --------- | ---------------------------------------------------------------------- |\n| `BUILD_AGENT_NAME`        | _(empty)_ | Overrides `card.name` when non-empty (read once at module load)        |\n| `BUILD_AGENT_DESCRIPTION` | _(empty)_ | Overrides `card.description` when non-empty (read once at module load) |\n| `BUILD_AGENT_VERSION`     | _(empty)_ | Overrides `card.version` when non-empty (read once at module load)     |\n\nTwo injection options:\n\n- **Runtime** - set `BUILD_AGENT_NAME` (etc.) in the environment before the module is first imported.\n- **Bundle-time** - use `tsup`'s `define` option to substitute at build time:\n\n  ```ts\n  // tsup.config.ts\n  import { defineConfig } from 'tsup';\n\n  export default defineConfig({\n    entry: ['src/index.ts'],\n    format: ['esm'],\n    define: {\n      'process.env.BUILD_AGENT_NAME': JSON.stringify('weather-assistant'),\n      'process.env.BUILD_AGENT_VERSION': JSON.stringify('1.2.3'),\n    },\n  });\n  ```\n\n**Apply build metadata to a hand-written card:**\n\n```ts\nimport {\n  applyBuildMetadata,\n  createA2AServer,\n  type AgentCard,\n} from '@inference-gateway/adk';\n\nconst baseCard: AgentCard = {\n  name: 'placeholder',\n  description: 'placeholder',\n  version: '0.0.0',\n  protocolVersion: '0.3.0',\n  url: 'http://127.0.0.1:8080',\n  defaultInputModes: ['text/plain'],\n  defaultOutputModes: ['text/plain'],\n  capabilities: {\n    streaming: false,\n    pushNotifications: false,\n    stateTransitionHistory: false,\n  },\n  skills: [],\n};\n\nconst card = applyBuildMetadata(baseCard);\nconst server = createA2AServer({ card });\n```\n\nSee the [Container Image](#-container-image) section for containerized builds that wire these variables through.\n\n#### Agent-card `${VAR}` placeholders\n\nInside 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:\n\n```json\n{\n  \"name\": \"${A2A_AGENT_NAME}\",\n  \"description\": \"Production agent in ${ENVIRONMENT}\",\n  \"version\": \"0.1.0\",\n  \"protocolVersion\": \"0.3.0\",\n  \"url\": \"${A2A_AGENT_URL}\",\n  \"defaultInputModes\": [\"text/plain\"],\n  \"defaultOutputModes\": [\"text/plain\"],\n  \"capabilities\": {\n    \"streaming\": false,\n    \"pushNotifications\": false,\n    \"stateTransitionHistory\": false\n  },\n  \"skills\": []\n}\n```\n\nFor 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.\n\n## 🔧 Advanced Usage\n\n- **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.\n- **Custom task handlers** - implement the `TaskHandler` (background) or `StreamableTaskHandler` (streaming) interface to ship arbitrary agent logic. See [Custom task handlers](#custom-task-handlers) below.\n- **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.\n- **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.\n- **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](#build-time-agent-metadata) above.\n- **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`.\n- **TLS termination** - boot the server over HTTPS by passing `tls: { certPath, keyPath }` into `createA2AServer` (or `loadServerTLSConfigFromEnv()` to read the same paths from `TLS_ENABLE` / `TLS_CERT_PATH` / `TLS_KEY_PATH`). For mTLS, add `caPath` and `requestCert: true`. See [TLS](#tls) below.\n\n### TLS\n\n#### Server: HTTPS termination\n\nPass a `tls` config to `createA2AServer` and the server listens over HTTPS instead of plaintext HTTP. The cert / key files are read synchronously during construction - a missing path fails fast with `TLSConfigError` before `listen()` is called.\n\n```ts\nimport {\n  createA2AServer,\n  loadServerTLSConfigFromEnv,\n  type AgentCard,\n} from '@inference-gateway/adk';\n\nconst card: AgentCard = /* ... */;\n\nconst server = createA2AServer({\n  card,\n  tls: loadServerTLSConfigFromEnv(), // reads TLS_ENABLE / TLS_CERT_PATH / TLS_KEY_PATH\n});\n\nawait server.listen(8443, '0.0.0.0');\n```\n\n`loadServerTLSConfigFromEnv()` returns `undefined` when `TLS_ENABLE` is falsy, so the same code drops back to plaintext for local dev without a branch in the caller. The recognized env vars are:\n\n| Env var           |    Required     | Purpose                                                                |\n| ----------------- | :-------------: | ---------------------------------------------------------------------- |\n| `TLS_ENABLE`      | (master toggle) | Truthy: `true`, `1`, `yes`, `on`. Anything else returns `undefined`.   |\n| `TLS_CERT_PATH`   |   ✓ (when on)   | Server certificate (PEM).                                              |\n| `TLS_KEY_PATH`    |   ✓ (when on)   | Server private key (PEM).                                              |\n| `TLS_CA_PATH`     |                 | CA bundle used to verify client certs (mTLS only).                     |\n| `TLS_PASSPHRASE`  |                 | Passphrase unlocking the private key.                                  |\n| `TLS_CLIENT_AUTH` |                 | When truthy, request + require a client cert. Pair with `TLS_CA_PATH`. |\n\nFor container deployments, mount cert / key files from a secrets backend rather than baking them into the image:\n\n```sh\ndocker run --rm -p 8443:8443 \\\n  -v /etc/tls/cert.pem:/run/secrets/tls/cert.pem:ro \\\n  -v /etc/tls/key.pem:/run/secrets/tls/key.pem:ro \\\n  -e TLS_ENABLE=true \\\n  -e TLS_CERT_PATH=/run/secrets/tls/cert.pem \\\n  -e TLS_KEY_PATH=/run/secrets/tls/key.pem \\\n  my-agent:latest\n```\n\nThe [`examples/tls-server/`](./examples/tls-server/) example ships a `generate-certs.sh` helper and a runnable end-to-end demo (server + client over HTTPS with a self-signed cert), plus a Kubernetes `Secret` + `volumeMount` recipe.\n\n#### Client: outbound TLS for `A2AClient` and `OpenAICompatibleLLMClient`\n\nBoth clients accept a `tls?: ClientTLSConfig` field. Setting it routes outbound HTTPS through a `node:https.Agent` configured with the supplied cert / key / CA, so you can talk to a self-signed or private-CA-signed peer without disabling system trust.\n\n```ts\nimport {\n  createA2AClient,\n  loadClientTLSConfigFromEnv,\n} from '@inference-gateway/adk';\n\nconst client = createA2AClient({\n  baseURL: 'https://peer-agent.internal:8443',\n  tls: loadClientTLSConfigFromEnv() ?? {\n    caPath: '/etc/internal-ca.pem',\n  },\n});\n```\n\n`ClientTLSConfig` fields:\n\n- `caPath` - bundle to trust (for self-signed / private-CA peers)\n- `certPath` + `keyPath` - client cert for mTLS (both must be set together)\n- `passphrase` - unlocks an encrypted private key\n- `insecureSkipVerify` - **dev only.** Disables cert verification; vulnerable to MITM. Use `caPath` in production.\n- `servername` - override the SNI hostname\n\n`tls` and `fetch` are mutually exclusive on both clients - passing both throws (`A2AClientError` / `LLMConfigurationError`). To layer your own fetch wrapper over TLS, build the inner fetch yourself via `createTLSFetch(config)` and pass it via `fetch`.\n\n### Custom task handlers\n\n`TaskHandler` and `StreamableTaskHandler` let you plug arbitrary agent logic into the server via `A2AServerBuilder`. Both mirror the [Go ADK's `server/task_handler.go`](https://github.com/inference-gateway/adk/blob/main/server/task_handler.go) interfaces; the TypeScript variant uses an `AbortSignal` on the context for cancellation instead of Go's `context.Context`.\n\n```ts\nimport {\n  A2AServerBuilder,\n  AGENT_EVENT_TYPE,\n  BaseStreamableTaskHandler,\n  BaseTaskHandler,\n  TASK_STATE,\n  createCloudEvent,\n  transitionTask,\n  type AgentCard,\n  type CloudEvent,\n  type ManagedTask,\n  type Message,\n  type TaskHandlerContext,\n} from '@inference-gateway/adk';\n\n// Background handler - return the updated task once processing finishes.\nclass EchoTaskHandler extends BaseTaskHandler {\n  async handleTask(\n    _ctx: TaskHandlerContext,\n    task: ManagedTask,\n    _message: Message\n  ): Promise\u003cManagedTask\u003e {\n    let next = task;\n    if (next.state === TASK_STATE.PENDING) {\n      next = transitionTask(next, TASK_STATE.IN_PROGRESS);\n    }\n    return transitionTask(next, TASK_STATE.COMPLETED);\n  }\n}\n\n// Streaming handler - yield CloudEvents as the task progresses.\nclass EchoStreamHandler extends BaseStreamableTaskHandler {\n  async *handleStreamingTask(\n    ctx: TaskHandlerContext,\n    task: ManagedTask,\n    message: Message\n  ): AsyncIterable\u003cCloudEvent\u003e {\n    if (ctx.signal.aborted) return;\n    yield createCloudEvent({\n      type: AGENT_EVENT_TYPE.DELTA,\n      subject: task.id,\n      data: {\n        messageId: crypto.randomUUID(),\n        role: 'ROLE_AGENT',\n        contextId: task.contextId,\n        taskId: task.id,\n        parts: [{ text: 'echo: ' + (message.parts[0]?.text ?? '') }],\n      },\n    });\n  }\n}\n\nconst card: AgentCard = /* ... */;\n\nconst server = new A2AServerBuilder({})\n  .withAgentCard(card)\n  .withTaskHandler(new EchoTaskHandler())\n  // Or, for a streaming agent card (capabilities.streaming === true):\n  // .withStreamableTaskHandler(new EchoStreamHandler())\n  .build();\n\nawait server.listen(8080, '127.0.0.1');\n```\n\nHandler contracts:\n\n- 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.\n- `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.\n- `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.).\n- `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.\n\n## 🌐 A2A Ecosystem\n\nThis ADK is the TypeScript implementation of the Agent-to-Agent (A2A) protocol within the Inference Gateway ecosystem.\n\n### Related Projects\n\n- **[Inference Gateway](https://github.com/inference-gateway/inference-gateway)** - Unified API gateway for AI providers\n- **[Go ADK](https://github.com/inference-gateway/adk)** - Go A2A Development Kit (the most feature-complete sibling)\n- **[Rust ADK](https://github.com/inference-gateway/rust-adk)** - Rust A2A Development Kit\n- **[Go SDK](https://github.com/inference-gateway/go-sdk)** - Go client library for Inference Gateway\n- **[TypeScript SDK](https://github.com/inference-gateway/typescript-sdk)** - TypeScript/JavaScript client library\n- **[Python SDK](https://github.com/inference-gateway/python-sdk)** - Python client library\n- **[Rust SDK](https://github.com/inference-gateway/rust-sdk)** - Rust client library\n- **[Schemas](https://github.com/inference-gateway/schemas)** - Canonical A2A JSON Schemas (source of truth for generated types)\n\n### A2A Agents\n\n- **[Awesome A2A](https://github.com/inference-gateway/awesome-a2a)** - Curated list of A2A-compatible agents\n- **[Browser Agent](https://github.com/inference-gateway/browser-agent)** - Web browser automation and interaction agent\n- **[Documentation Agent](https://github.com/inference-gateway/documentation-agent)** - Documentation generation and management agent\n- **[Google Calendar Agent](https://github.com/inference-gateway/google-calendar-agent)** - Google Calendar integration agent\n- **[n8n Agent](https://github.com/inference-gateway/n8n-agent)** - n8n workflow automation integration agent\n\n## 📋 Requirements\n\n- **Node.js**: 24 LTS or later\n- **pnpm**: 10.0 or later (10.18.0 is pinned via `package.json#packageManager`)\n- **Dependencies**: see [`package.json`](./package.json) - runtime depends only on `hono` and `@hono/node-server`\n\n## 📦 Container Image\n\nA minimal multi-stage `Dockerfile` that produces an [OCI-compliant](https://opencontainers.org/) 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 `ARG` → `ENV`, which the ADK reads on first import:\n\n```dockerfile\n# --- Builder ---\nFROM node:24-alpine AS builder\n\nARG AGENT_NAME=\"My A2A Agent\"\nARG AGENT_DESCRIPTION=\"A custom A2A agent built with the TypeScript ADK\"\nARG AGENT_VERSION=\"0.1.0\"\n\nWORKDIR /app\n\nRUN corepack enable \u0026\u0026 corepack prepare pnpm@10.18.0 --activate\n\nCOPY package.json pnpm-lock.yaml ./\nRUN pnpm install --frozen-lockfile\n\nCOPY . .\nENV BUILD_AGENT_NAME=${AGENT_NAME} \\\n    BUILD_AGENT_DESCRIPTION=${AGENT_DESCRIPTION} \\\n    BUILD_AGENT_VERSION=${AGENT_VERSION}\nRUN pnpm build\n\n# --- Runtime ---\nFROM node:24-alpine\n\nARG AGENT_NAME\nARG AGENT_DESCRIPTION\nARG AGENT_VERSION\n\nRUN addgroup -g 1001 -S a2a \u0026\u0026 adduser -u 1001 -S agent -G a2a\nWORKDIR /home/agent\nCOPY --from=builder /app/dist ./dist\nCOPY --from=builder /app/node_modules ./node_modules\nCOPY --from=builder /app/package.json ./\nRUN chown -R agent:a2a /home/agent\nUSER agent\n\nENV BUILD_AGENT_NAME=${AGENT_NAME} \\\n    BUILD_AGENT_DESCRIPTION=${AGENT_DESCRIPTION} \\\n    BUILD_AGENT_VERSION=${AGENT_VERSION}\n\nCMD [\"node\", \"dist/index.js\"]\n```\n\n**Build with custom metadata** - `docker build`, `podman build`, etc. all take the same flags:\n\n```sh\ndocker build \\\n  --build-arg AGENT_NAME=\"Weather Assistant\" \\\n  --build-arg AGENT_DESCRIPTION=\"AI-powered weather forecasting agent\" \\\n  --build-arg AGENT_VERSION=\"0.1.1\" \\\n  -t my-a2a-agent .\n```\n\n## 📄 License\n\nThis project is licensed under the Apache 2.0 License. See the [LICENSE](./LICENSE) file for details.\n\n## 🤝 Contributing\n\nContributions are welcome - whether you're fixing bugs, adding features, improving documentation, or helping bring more of the [Go ADK](https://github.com/inference-gateway/adk) feature surface to TypeScript.\n\n**Please see the [Contributing Guide](./CONTRIBUTING.md) for:**\n\n- 🚀 **Getting Started** - Prerequisites, Flox/manual dev environment setup\n- 📋 **Development Workflow** - pnpm scripts, regenerating A2A types, the inner loop\n- 🎯 **Coding Guidelines** - TypeScript strictness flags, style, and comment conventions\n- 🛠️ **Making Changes** - Branch naming and Conventional Commit format\n- 🧪 **Testing Guidelines** - Vitest layout, running a single test, the drift check\n- 🔄 **Continuous Integration** - CI matrix and required status checks\n- 🚢 **Releases** - How semantic-release computes the next version\n- 🔄 **Pull Request Process** - Pre-submit checklist and review flow\n\n**Quick start for contributors:**\n\n```sh\n# Fork the repo on GitHub, then:\ngit clone https://github.com/your-username/typescript-adk.git\ncd typescript-adk\npnpm install\npnpm test\n```\n\nFor questions or help getting started, please [open a discussion](https://github.com/inference-gateway/typescript-adk/discussions) or [file an issue](https://github.com/inference-gateway/typescript-adk/issues).\n\n## 📞 Support\n\n### Issues \u0026 Questions\n\n- **Bug Reports**: [GitHub Issues](https://github.com/inference-gateway/typescript-adk/issues)\n- **Documentation**: [Official Docs](https://docs.inference-gateway.com)\n\n## 🔗 Resources\n\n### Documentation\n\n- [A2A Protocol Specification](https://github.com/inference-gateway/schemas/tree/main/a2a)\n- [API Documentation](https://docs.inference-gateway.com/a2a)\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/inference-gateway\"\u003eGitHub\u003c/a\u003e •\n  \u003ca href=\"https://docs.inference-gateway.com\"\u003eDocumentation\u003c/a\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finference-gateway%2Ftypescript-adk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finference-gateway%2Ftypescript-adk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finference-gateway%2Ftypescript-adk/lists"}