@artinet/sdk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +35 -4
  2. package/LICENSE +201 -21
  3. package/README.md +281 -413
  4. package/bin/artinet-metadata-validator-0.0.1.tgz +0 -0
  5. package/dist/client/a2a-client.d.ts +3 -4
  6. package/dist/client/a2a-client.d.ts.map +1 -1
  7. package/dist/client/a2a-client.js +9 -9
  8. package/dist/client/a2a-client.js.map +1 -1
  9. package/dist/client/interfaces/client.d.ts +95 -0
  10. package/dist/client/interfaces/client.d.ts.map +1 -1
  11. package/dist/server/a2a-server.d.ts +7 -0
  12. package/dist/server/a2a-server.d.ts.map +1 -1
  13. package/dist/server/a2a-server.js +34 -4
  14. package/dist/server/a2a-server.js.map +1 -1
  15. package/dist/server/interfaces/params.d.ts +46 -9
  16. package/dist/server/interfaces/params.d.ts.map +1 -1
  17. package/dist/server/interfaces/params.js.map +1 -1
  18. package/dist/server/interfaces/server.d.ts +1 -0
  19. package/dist/server/interfaces/server.d.ts.map +1 -1
  20. package/dist/server/lib/express-server.d.ts.map +1 -1
  21. package/dist/server/lib/express-server.js +2 -2
  22. package/dist/server/lib/express-server.js.map +1 -1
  23. package/dist/server/lib/json-middleware.d.ts +2 -18
  24. package/dist/server/lib/json-middleware.d.ts.map +1 -1
  25. package/dist/server/lib/json-middleware.js +2 -179
  26. package/dist/server/lib/json-middleware.js.map +1 -1
  27. package/dist/server/lib/middleware/a2a-methods.d.ts +7 -0
  28. package/dist/server/lib/middleware/a2a-methods.d.ts.map +1 -0
  29. package/dist/server/lib/middleware/a2a-methods.js +103 -0
  30. package/dist/server/lib/middleware/a2a-methods.js.map +1 -0
  31. package/dist/server/lib/middleware/factory.d.ts +12 -0
  32. package/dist/server/lib/middleware/factory.d.ts.map +1 -0
  33. package/dist/server/lib/middleware/factory.js +59 -0
  34. package/dist/server/lib/middleware/factory.js.map +1 -0
  35. package/dist/server/lib/storage/file.js +1 -0
  36. package/dist/server/lib/storage/file.js.map +1 -1
  37. package/dist/server/lib/storage/memory.js +1 -3
  38. package/dist/server/lib/storage/memory.js.map +1 -1
  39. package/dist/transport/streaming/event-stream.d.ts.map +1 -1
  40. package/dist/transport/streaming/event-stream.js +31 -14
  41. package/dist/transport/streaming/event-stream.js.map +1 -1
  42. package/dist/types/extended-schema.d.ts +2 -1
  43. package/dist/types/extended-schema.d.ts.map +1 -1
  44. package/dist/types/extended-schema.js.map +1 -1
  45. package/dist/utils/api/register.d.ts +25 -0
  46. package/dist/utils/api/register.d.ts.map +1 -0
  47. package/dist/utils/api/register.js +96 -0
  48. package/dist/utils/api/register.js.map +1 -0
  49. package/dist/utils/common/errors.d.ts +13 -13
  50. package/dist/utils/common/errors.d.ts.map +1 -1
  51. package/dist/utils/common/errors.js +10 -2
  52. package/dist/utils/common/errors.js.map +1 -1
  53. package/dist/utils/common/utils.d.ts +1 -0
  54. package/dist/utils/common/utils.d.ts.map +1 -1
  55. package/dist/utils/common/utils.js +9 -0
  56. package/dist/utils/common/utils.js.map +1 -1
  57. package/dist/utils/index.d.ts +1 -0
  58. package/dist/utils/index.d.ts.map +1 -1
  59. package/dist/utils/index.js +1 -0
  60. package/dist/utils/index.js.map +1 -1
  61. package/dist/utils/logging/logger.d.ts +2 -2
  62. package/dist/utils/logging/logger.d.ts.map +1 -1
  63. package/dist/utils/logging/logger.js +1 -1
  64. package/dist/utils/logging/logger.js.map +1 -1
  65. package/package.json +13 -11
package/README.md CHANGED
@@ -1,19 +1,65 @@
1
- # Artinet SDK: A Production-Ready A2A Protocol Implementation
1
+ [![npm version](https://img.shields.io/npm/v/@artinet/sdk.svg)](https://www.npmjs.com/package/@artinet/sdk)
2
+ [![npm downloads](https://img.shields.io/npm/dt/@artinet/sdk.svg)](https://www.npmjs.com/package/@artinet/sdk)
3
+ [![Apache License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
4
+ [![Known Vulnerabilities](https://snyk.io/test/npm/@artinet/sdk/badge.svg)](https://snyk.io/test/npm/@artinet/sdk)
2
5
 
3
- A robust, feature-rich TypeScript client and server library for the [Agent2Agent (A2A) Protocol](https://github.com/google/A2A) - an open protocol for communication between AI agents.
6
+ <!-- [![Coverage Status](https://coveralls.io/repos/github/the-artinet-project/artinet-sdk/badge.svg?branch=main)](https://coveralls.io/github/the-artinet-project/artinet-sdk?branch=main) -->
7
+
8
+ # Artinet SDK
9
+
10
+ Artinet SDK is a [Agent2Agent (A2A) Protocol](https://github.com/google/A2A) compliant server and client written in TypeScript for [node.js](https://nodejs.org/) that aims to simplify the creation of interoperable AI agents. Learn more at [the artinet project](https://artinet.io/).
4
11
 
5
12
  This SDK significantly enhances the foundational A2A concepts and samples provided by Google, offering a production-ready solution with a focus on developer experience, reliability, and comprehensive features.
6
13
 
7
- ## Why Choose Artinet SDK?
14
+ ## Table of Contents
15
+ - [Artinet SDK](#artinet-sdk)
16
+ - [Table of Contents](#table-of-contents)
17
+ - [Features](#features)
18
+ - [Installation](#installation)
19
+ - [Requirements](#requirements)
20
+ - [Example](#example)
21
+ - [Class Documentation](#class-documentation)
22
+ - [Core Classes](#core-classes)
23
+ - [Key Types \& Interfaces](#key-types--interfaces)
24
+ - [Running Tests](#running-tests)
25
+ - [Typescript](#typescript)
26
+ - [Usage](#usage)
27
+ - [Client](#client)
28
+ - [Basic Client Usage](#basic-client-usage)
29
+ - [Streaming Updates](#streaming-updates)
30
+ - [Authentication](#authentication)
31
+ - [Server](#server)
32
+ - [Implementing an A2A Agent](#implementing-an-a2a-agent)
33
+ - [Persistent Storage](#persistent-storage)
34
+ - [Logging](#logging)
35
+ - [Server Registration \& Discovery](#server-registration--discovery)
36
+ - [Advanced Server Customization](#advanced-server-customization)
37
+ - [Contributing](#contributing)
38
+ - [License](#license)
39
+ - [Acknowledgements](#acknowledgements)
8
40
 
9
- While the official A2A repository provides basic samples, the Artinet SDK is engineered for real-world applications:
41
+ ## Features
10
42
 
11
- - **Plug-and-Play Server:** Built on Express.js, the `A2AServer` handles JSON-RPC complexity, routing, protocol compliance, and streaming mechanics automatically. Just provide your core agent logic (`TaskHandler`) and basic configuration for a fully functional A2A endpoint with minimal boilerplate.
12
- - **Enhanced Client:** Features refined error handling, flexible header management for authentication, and clear separation of concerns.
13
- - **Comprehensive Testing:** Ensuring reliability and maintainability. Includes tests for core logic, streaming, error conditions, and edge cases.
14
- - **Simplified Developer Experience:** Start quickly with clear TypeScript types, intuitive APIs, and minimal setup. The SDK handles the underlying protocol details, letting you focus _solely_ on your agent's unique capabilities.
15
- - **Flexible Storage:** Offers built-in `InMemoryTaskStore` for development/testing and `FileStore` for persistent task storage, easily extensible for custom storage solutions.
16
- - **Full Protocol Compliance:** Implements the complete A2A specification using the official JSON schema, ensuring interoperability.
43
+ - **Plug-and-Play Server:** Built on Express.js, the `A2AServer` handles JSON-RPC complexity, routing, protocol compliance, and Server-Sent Events (SSE) streaming mechanics automatically. Just provide your core agent logic (`TaskHandler`) and configuration via `A2AServerParams`.
44
+ - **Enhanced Client:** `A2AClient` features refined error handling (`RpcError`), flexible header management for authentication, and clear separation of concerns.
45
+ - **TypeScript First:** Fully written in TypeScript with comprehensive type definitions for a robust developer experience.
46
+ - **Flexible Storage:** Offers built-in `InMemoryTaskStore` (development/testing) and `FileStore` (persistent), with the `TaskStore` interface allowing custom storage solutions.
47
+ - **Protocol Compliance:** Implements the complete A2A specification using the official JSON schema, ensuring interoperability.
48
+ - **Robust Streaming:** Reliable SSE support for `tasks/sendSubscribe` & `tasks/resubscribe` using `eventsource-parser`.
49
+ - **Configurable Logging:** Integrated structured logging via `pino`. Configurable levels using `configureLogger` and `LogLevel`.
50
+ - **Advanced Customization:** Allows providing a custom `JSONRPCServerFactory` for fine-grained control over the JSON-RPC server, enabling integration with existing Express apps or adding custom methods.
51
+ - **Comprehensive Testing:** Includes a suite of tests to ensure reliability and maintainability.
52
+
53
+ | Component/Feature | Description | Key Classes/Types |
54
+ | :------------------ | :-------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- |
55
+ | **Client** | Interact with A2A-compliant agents. Supports standard & streaming requests. | `A2AClient`, `RpcError` |
56
+ | **Server** | Host A2A-compliant agents. Handles protocol details, routing, SSE. | `A2AServer`, `A2AServerParams` |
57
+ | **Task Handling** | Define agent logic using async generators. | `TaskHandler`, `TaskContext`, `TaskYieldUpdate` |
58
+ | **Storage** | Persist task state. In-memory and file-based options included. | `TaskStore`, `InMemoryTaskStore`, `FileStore` |
59
+ | **Streaming (SSE)** | Handle real-time updates via SSE for `tasks/sendSubscribe`/`resubscribe`. | `TaskStatusUpdateEvent`, `TaskArtifactUpdateEvent` |
60
+ | **Logging** | Configure structured logging for debugging and monitoring. | `logger`, `configureLogger`, `LogLevel` |
61
+ | **Advanced Server** | Customize the underlying JSON-RPC server or integrate into existing apps. | `JSONRPCServerFactory`, `CreateJSONRPCServerParams`, `createJSONRPCMethod`, A2A Method Types |
62
+ | **Core Types** | Based on the official A2A JSON Schema. | `AgentCard`, `Task`, `Message`, `Part`, `Artifact`, etc. |
17
63
 
18
64
  ## Installation
19
65
 
@@ -21,31 +67,13 @@ While the official A2A repository provides basic samples, the Artinet SDK is eng
21
67
  npm install @artinet/sdk
22
68
  ```
23
69
 
24
- ## Features
70
+ ## Requirements
25
71
 
26
- - Full implementation of the A2A Protocol using the official Google schema
27
- - Strongly-typed TypeScript interfaces for all protocol structures
28
- - **Client Features:**
29
- - Reliable streaming support (`tasks/sendSubscribe`) with robust handling of Server-Sent Events (SSE) using `eventsource-parser`.
30
- - Push notification configuration support (`tasks/pushNotification/set`, `tasks/pushNotification/get`).
31
- - Built-in, informative error classes (`RpcError`).
32
- - Easy-to-use methods for `agentCard` discovery and capability checking (`supports`).
33
- - Flexible header management for authentication (`addHeader`, `setHeaders`).
34
- - **Server Features:**
35
- - Simplified Express.js server setup (`A2AServer`).
36
- - Abstracted `TaskHandler` interface for easy integration of agent logic using async generators.
37
- * Automatic handling of JSON-RPC request parsing, validation, and routing.
38
- - Pluggable task storage (`InMemoryTaskStore`, `FileStore`, or custom).
39
- - Full support for streaming responses and artifact updates.
40
- - Standardized error handling (`A2AError`) mapped to JSON-RPC error codes.
41
- - Configurable agent card and capabilities.
42
- - **Developer Tools:**
43
- - Integrated structured logging via `pino`, configurable levels (`configureLogger`).
44
- - Exhaustive test suite (`jest`) ensuring high reliability.
45
-
46
- ## Quick Start
47
-
48
- Get a basic A2A server running and interact with it using the client in just a few lines of code.
72
+ - Node.js (v22.0.0 or higher recommended, check `package.json` engines for exact requirement)
73
+
74
+ ## Example
75
+
76
+ A basic A2A server and client interaction. For more detailed examples, see the `examples/` directory.
49
77
 
50
78
  **1. Server (`quick-server.ts`)**
51
79
 
@@ -55,25 +83,16 @@ import {
55
83
  TaskContext,
56
84
  TaskHandler,
57
85
  InMemoryTaskStore,
58
- logger,
59
- configureLogger,
60
86
  } from "@artinet/sdk";
61
87
 
62
- // Set the log level to info to see startup messages
63
- configureLogger({ level: "info" });
64
-
65
- // Define the simplest possible agent logic
88
+ // Minimal agent logic: receive text, yield working state, yield completed state with echo
66
89
  const quickAgentLogic: TaskHandler = async function* (context: TaskContext) {
67
90
  const userInput =
68
91
  context.userMessage.parts[0].type === "text"
69
92
  ? context.userMessage.parts[0].text
70
93
  : "";
71
- logger.info(`Quick server received: ${userInput}`);
72
- yield {
73
- state: "working",
74
- message: { role: "agent", parts: [{ type: "text", text: "Thinking..." }] },
75
- };
76
- await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate work
94
+ yield { state: "working" };
95
+ // Simulate some work if needed, check context.isCancelled()
77
96
  yield {
78
97
  state: "completed",
79
98
  message: {
@@ -81,10 +100,8 @@ const quickAgentLogic: TaskHandler = async function* (context: TaskContext) {
81
100
  parts: [{ type: "text", text: `You said: ${userInput}` }],
82
101
  },
83
102
  };
84
- logger.info(`Quick server responded.`);
85
103
  };
86
104
 
87
- // Configure and start the server
88
105
  const server = new A2AServer({
89
106
  taskHandler: quickAgentLogic,
90
107
  taskStore: new InMemoryTaskStore(),
@@ -94,525 +111,376 @@ const server = new A2AServer({
94
111
  name: "QuickStart Agent",
95
112
  url: "http://localhost:4000/a2a",
96
113
  version: "0.1.0",
97
- capabilities: { streaming: true }, // Our handler uses yield
114
+ capabilities: { streaming: true },
98
115
  skills: [{ id: "echo", name: "Echo Skill" }],
99
116
  },
100
117
  });
101
118
 
102
119
  server.start();
103
- logger.info("Quick Start A2A Server running on http://localhost:4000/a2a");
120
+ console.log("A2A Server running at http://localhost:4000/a2a");
104
121
  ```
105
122
 
106
123
  **2. Client (`quick-client.ts`)**
107
124
 
108
125
  ```typescript
109
- import { A2AClient, logger, configureLogger } from "@artinet/sdk";
110
-
111
- // Set the log level to info to see client messages
112
- configureLogger({ level: "info" });
126
+ import { A2AClient, TaskStatusUpdateEvent } from "@artinet/sdk";
113
127
 
114
128
  async function runClient() {
115
129
  const client = new A2AClient("http://localhost:4000/a2a");
116
- const message = {
130
+
131
+ const message = {
117
132
  role: "user" as const,
118
133
  parts: [{ type: "text" as const, text: "Hello Quick Start!" }],
119
134
  };
120
135
 
121
- try {
122
- logger.info("Sending task to quick server...");
123
- const stream = client.sendTaskSubscribe({ id: "quick-task-1", message });
124
-
125
- for await (const update of stream) {
126
- if ("status" in update && update.status.message) {
127
- const agentText = update.status.message.parts
128
- .filter((p) => p.type === "text")
129
- .map((p: any) => p.text)
130
- .join(" ");
131
- logger.info(`Client Received: [${update.status.state}] ${agentText}`);
132
- }
133
- }
134
- logger.info("Client finished.");
135
- } catch (error) {
136
- logger.error("Client Error:", error);
136
+ const stream = client.sendTaskSubscribe({ id: "quick-task-1", message });
137
+
138
+ for await (const update of stream) {
139
+ // process the update
140
+ ...
137
141
  }
142
+ console.log("Stream finished.");
138
143
  }
139
144
 
140
- runClient();
145
+ runClient().catch(console.error);
141
146
  ```
142
147
 
143
- **3. Run It**
148
+ ## Class Documentation
144
149
 
145
- - Save the files above.
146
- - Install the SDK: `npm install @artinet/sdk`
147
- - Run the server: `npx tsx quick-server.ts`
148
- - In another terminal, run the client: `npx tsx quick-client.ts`
150
+ The Artinet SDK provides several core classes and interfaces for building A2A clients and servers.
149
151
 
150
- You'll see the server start, the client send a message, and the server respond with status updates.
152
+ ### Core Classes
151
153
 
152
- ## Client Usage
154
+ | Class | Description |
155
+ | :------------------ | :------------------------------------------------------------------ |
156
+ | `A2AClient` | Client for interacting with A2A servers. |
157
+ | `A2AServer` | Express-based server implementation for hosting A2A agents. |
158
+ | `RpcError` | Represents client-side errors encountered during A2A communication. |
159
+ | `InMemoryTaskStore` | Simple in-memory task persistence (ideal for development/testing). |
160
+ | `FileStore` | File-based task persistence (stores task data in the filesystem). |
153
161
 
154
- ### Basic Client Usage
162
+ ### Key Types & Interfaces
155
163
 
156
- ```typescript
157
- import { A2AClient, Message, Part, TaskSendParams } from "@artinet/sdk";
164
+ | Type/Interface | Description |
165
+ | :-------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- |
166
+ | `TaskHandler` | Async generator function defining the core agent logic (`async function*(context: TaskContext): AsyncGenerator<TaskYieldUpdate>`). |
167
+ | `TaskContext` | Provides task details (`task`, `userMessage`, `history`, `isCancelled()`) to the `TaskHandler`. |
168
+ | `TaskStore` | Interface defining the contract for task persistence implementations (like `InMemoryTaskStore`, `FileStore`). |
169
+ | `TaskYieldUpdate` | Union type for updates yielded by a `TaskHandler` (representing status changes or generated artifacts). |
170
+ | `A2AServerParams` | Configuration object passed to the `A2AServer` constructor (port, store, card, basePath, handler, etc.). |
171
+ | `AgentCard` | Describes the agent's capabilities, metadata, skills, and endpoint URL. |
172
+ | `Message`, `Part`, `Artifact`, `Task`, `TaskStatus`, etc. | Core types mirroring the structures defined in the A2A JSON Schema specification. Used for requests, responses, and task state. |
173
+ | `TaskStatusUpdateEvent`, `TaskArtifactUpdateEvent` | Specific types for Server-Sent Events (SSE) received during streaming operations (`tasks/sendSubscribe`, `tasks/resubscribe`). |
174
+ | `LogLevel` | Enum defining logging levels (`error`, `warn`, `info`, `debug`, `trace`) used with the built-in logger. |
175
+ | `JSONRPCServerFactory` | Function signature for providing a custom JSON-RPC server creation logic to `A2AServer` for advanced customization. |
176
+ | `CreateJSONRPCServerParams` | Object containing dependencies provided _to_ a `JSONRPCServerFactory` function. |
177
+ | `SendTaskMethod`, `GetTaskMethod`, ... | Type signatures for specific A2A method handlers, used when implementing custom server logic with `createJSONRPCMethod`. |
178
+
179
+ ## Running Tests
158
180
 
159
- // Create a new client instance targeting the agent's A2A endpoint
160
- const client = new A2AClient("https://your-a2a-server.com/a2a"); // Ensure '/a2a' or your server's basePath is included
181
+ ```bash
182
+ npm test
183
+ ```
161
184
 
162
- // Discover agent capabilities via its card (often at /.well-known/agent.json relative to the base URL)
163
- // Note: The client needs the *direct* A2A endpoint URL. Card discovery might be a separate step.
164
- // const agentCard = await client.agentCard(); // Assuming agentCard is fetched by other means or URL is known
165
- // console.log(`Agent capabilities:`, agentCard.capabilities);
185
+ To run tests with coverage:
166
186
 
167
- // Example: Check if the agent supports streaming before using it
168
- const supportsStreaming = await client.supports("streaming");
169
- console.log("Supports Streaming:", supportsStreaming);
187
+ ```bash
188
+ npm run test:coverage
189
+ ```
170
190
 
171
- // Create a message following the A2A schema
172
- const message: Message = {
173
- role: "user",
174
- parts: [{ type: "text", text: "What is the weather in London?" }],
175
- };
191
+ ## Typescript
176
192
 
177
- // Send a task using the standard request/response pattern
178
- try {
179
- const task = await client.sendTask({
180
- id: "task-" + Date.now(), // Generate a unique task ID
181
- message,
182
- });
183
- console.log(`Task ${task?.id} status: ${task?.status.state}`);
184
-
185
- // Fetch task status later if needed
186
- const updatedTask = await client.getTask({ id: task!.id });
187
- console.log(`Updated Task Status: ${updatedTask?.status.state}`);
188
- } catch (error) {
189
- console.error("A2A Client Error:", error);
190
- if (error instanceof RpcError) {
191
- console.error(`RPC Error Code: ${error.code}, Message: ${error.message}`);
192
- }
193
+ The Artinet SDK is written entirely in TypeScript and includes comprehensive type definitions, providing strong typing and enhanced developer experience.
194
+
195
+ ## Usage
196
+
197
+ ### Client
198
+
199
+ Interact with A2A-compliant agents using the `A2AClient`. See `examples/` for more.
200
+
201
+ #### Basic Client Usage
202
+
203
+ Send a task using `tasks/send`.
204
+
205
+ ```typescript
206
+ import { A2AClient, Message } from "@artinet/sdk";
207
+
208
+ async function runBasicTask() {
209
+ const client = new A2AClient("https://your-a2a-server.com/a2a");
210
+ const message: Message = {
211
+ role: "user",
212
+ parts: [{ type: "text", text: "What is the capital of France?" }],
213
+ };
214
+
215
+ const task = await client.sendTask({ id: "basic-task-1", message });
216
+ console.log("Task Completed:", task);
193
217
  }
194
218
  ```
195
219
 
196
- ### Streaming Updates
220
+ #### Streaming Updates
197
221
 
198
- Leverage real-time updates for long-running tasks if the agent supports it.
222
+ Receive real-time updates via SSE using `tasks/sendSubscribe` (recommended).
199
223
 
200
224
  ```typescript
201
225
  import {
202
226
  A2AClient,
203
227
  Message,
204
- RpcError,
205
228
  TaskStatusUpdateEvent,
206
229
  TaskArtifactUpdateEvent,
207
230
  } from "@artinet/sdk";
208
231
 
209
- const client = new A2AClient("https://your-a2a-server.com/a2a");
210
-
211
232
  async function runStreamingTask() {
212
- if (!(await client.supports("streaming"))) {
213
- console.log("Agent does not support streaming.");
214
- return;
215
- }
216
-
233
+ const client = new A2AClient("https://your-a2a-server.com/a2a");
217
234
  const message: Message = {
218
235
  role: "user",
219
- parts: [
220
- { type: "text", text: "Generate a short story and a cover image." },
221
- ],
236
+ parts: [{ type: "text", text: "Tell me a short story." }],
222
237
  };
223
238
 
224
- try {
225
- const stream = client.sendTaskSubscribe({
226
- id: "streaming-task-" + Date.now(),
227
- message,
228
- });
229
-
230
- console.log("Subscribed to task updates...");
231
-
232
- for await (const update of stream) {
233
- if ((update as TaskStatusUpdateEvent).status) {
234
- // Type guard for status updates
235
- const statusUpdate = update as TaskStatusUpdateEvent;
236
- console.log(`Task Status: ${statusUpdate.status.state}`);
237
- if (statusUpdate.status.message) {
238
- const text = statusUpdate.status.message.parts
239
- .filter((p) => p.type === "text")
240
- .map((p: any) => p.text)
241
- .join(" ");
242
- if (text) console.log("Agent Message:", text);
243
- }
244
- if (statusUpdate.final) {
245
- console.log("Task stream finished.");
246
- }
247
- } else if ((update as TaskArtifactUpdateEvent).artifact) {
248
- // Type guard for artifact updates
249
- const artifactUpdate = update as TaskArtifactUpdateEvent;
250
- console.log(
251
- `Received Artifact: ${artifactUpdate.artifact.name ?? "Unnamed"}`
252
- );
253
- // Process artifact parts (e.g., save file, display data)
254
- artifactUpdate.artifact.parts.forEach((part) => {
255
- if (part.type === "text")
256
- console.log(` -> Text Part: ${part.text.substring(0, 50)}...`);
257
- if (part.type === "file")
258
- console.log(
259
- ` -> File Part: ${part.file.name} (${part.file.mimeType})`
260
- );
261
- // Add logic to handle file bytes or URI
262
- });
263
- }
264
- }
265
- } catch (error) {
266
- console.error("A2A Streaming Error:", error);
267
- if (error instanceof RpcError) {
268
- console.error(`RPC Error Code: ${error.code}, Message: ${error.message}`);
239
+ const stream = client.sendTaskSubscribe({ id: "streaming-task-1", message });
240
+
241
+ for await (const update of stream) {
242
+ if ((update as TaskStatusUpdateEvent).status) {
243
+ console.log("Status:", (update as TaskStatusUpdateEvent).status.state);
244
+ } else if ((update as TaskArtifactUpdateEvent).artifact) {
245
+ console.log(
246
+ "Artifact:",
247
+ (update as TaskArtifactUpdateEvent).artifact.name
248
+ );
269
249
  }
270
250
  }
251
+ console.log("Stream finished.");
271
252
  }
272
-
273
- runStreamingTask();
274
253
  ```
275
254
 
276
- ### Authentication
255
+ #### Authentication
277
256
 
278
- Pass authentication credentials easily.
257
+ Add headers using `addHeader` or `setHeaders`.
279
258
 
280
259
  ```typescript
281
260
  import { A2AClient } from "@artinet/sdk";
282
261
 
283
262
  const client = new A2AClient("https://your-secure-a2a-server.com/a2a");
284
263
 
285
- // Add a single header (e.g., Bearer token)
264
+ // Add a single header
286
265
  client.addHeader("Authorization", "Bearer your-api-token");
287
266
 
288
- // Or set multiple headers at once
289
- client.setHeaders({
290
- Authorization: "Bearer your-api-token",
291
- "X-Custom-Header": "value",
292
- });
293
-
294
- // Now make requests as usual
295
- // await client.sendTask(...)
267
+ // Set multiple headers (overwrites existing)
268
+ client.setHeaders({ Authorization: "Bearer ...", "X-Custom": "value" });
296
269
  ```
297
270
 
298
- ## Server Usage
271
+ ### Server
299
272
 
300
- Setting up an A2A-compliant agent server is remarkably straightforward with the Artinet SDK. The `A2AServer` class abstracts away the complexities of the A2A protocol and underlying JSON-RPC communication, allowing you to focus purely on implementing your agent's behavior.
273
+ Host agents using `A2AServer`. Handles protocol details. See `examples/` for more.
301
274
 
302
- ### Implementing an A2A Agent
275
+ #### Implementing an A2A Agent
303
276
 
304
- Define your agent's core logic using an async generator `TaskHandler`.
277
+ Define agent behavior with an async generator `TaskHandler`.
305
278
 
306
279
  ```typescript
307
280
  import {
308
281
  A2AServer,
309
282
  TaskContext,
310
- TaskHandler
283
+ TaskHandler,
311
284
  InMemoryTaskStore,
312
- logger, // Use the built-in logger
313
285
  } from "@artinet/sdk";
314
- import path from "path";
315
286
 
316
- // Define your agent's logic
317
- async function* myAgentLogic(
318
- context: TaskContext
319
- ): TaskHandler {
320
- logger.info({ taskId: context.taskId }, "Received new task");
321
287
 
322
- // 1. Acknowledge receipt and indicate work is starting
288
+ const myAgent: TaskHandler = async function* (context: TaskContext) {
289
+
323
290
  yield {
324
291
  state: "working",
325
292
  message: {
326
293
  role: "agent",
327
- parts: [{ type: "text", text: "Got it! Processing your request..." }],
294
+ parts: [{ type: "text", text: "Processing..." }],
328
295
  },
329
296
  };
330
297
 
331
- // Simulate some work
332
- await new Promise((resolve) => setTimeout(resolve, 1000));
298
+ // Check context.isCancelled() if operation is long
299
+ // await someAsyncTask();
300
+ ...
333
301
 
334
- // 2. Check for cancellation periodically during long operations
335
- if (context.isCancelled()) {
336
- logger.warn({ taskId: context.taskId }, "Task cancelled by client");
337
- yield { state: "canceled" };
338
- return; // Stop processing
339
- }
340
-
341
- // 3. Process the user's message
342
- const userRequest = context.userMessage.parts
343
- .filter((part) => part.type === "text")
344
- .map((part: any) => part.text)
345
- .join(" ");
346
- logger.debug(
347
- { taskId: context.taskId, request: userRequest },
348
- "Processing user text"
349
- );
350
-
351
- // 4. (Optional) Yield an artifact
352
- const artifactContent = `This is a generated report for: "${userRequest}"`;
353
302
  yield {
354
- // Artifact details
355
- name: "report.txt",
303
+ name: "result.txt",
356
304
  mimeType: "text/plain",
357
- parts: [{ type: "text", text: artifactContent }],
305
+ parts: [{ type: "text", text: "Report data" }],
358
306
  };
359
- logger.info({ taskId: context.taskId }, "Generated artifact report.txt");
360
-
361
- // Simulate more work
362
- await new Promise((resolve) => setTimeout(resolve, 1500));
363
- if (context.isCancelled()) {
364
- /* ... check again ... */
365
- }
366
307
 
367
- // 5. Yield the final response
368
- const finalResponse = `Finished processing: "${userRequest}". See attached report.`;
369
308
  yield {
370
309
  state: "completed",
371
- message: { role: "agent", parts: [{ type: "text", text: finalResponse }] },
310
+ message: {
311
+ role: "agent",
312
+ parts: [{ type: "text", text: "Finished processing." }],
313
+ },
372
314
  };
373
- logger.info({ taskId: context.taskId }, "Task completed successfully");
374
- }
375
-
376
- // Create a task store (in-memory for this example)
377
- const store = new InMemoryTaskStore();
315
+ };
378
316
 
379
- // Configure and create the A2A server instance
380
- const server = new A2AServer({
381
- myAgentLogic,
382
- taskStore: store,
317
+ const myServer = new A2AServer({
318
+ taskHandler: myAgent,
319
+ taskStore: new InMemoryTaskStore(),
383
320
  port: 3000,
384
- basePath: "/a2a", // The path where A2A routes will be mounted
385
- // Customize the agent card served at /.well-known/agent.json
321
+ basePath: "/a2a",
386
322
  card: {
387
- name: "Artinet Example Agent",
388
- description: "A robust A2A agent built with Artinet SDK",
389
- url: "http://localhost:3000/a2a", // Must match your server's accessible A2A endpoint
323
+ name: "Example Agent",
324
+ url: "http://localhost:3000/a2a",
390
325
  version: "1.0.0",
391
- capabilities: {
392
- streaming: true, // This agent handler supports streaming via yields
393
- pushNotifications: false, // This agent doesn't implement push logic
394
- },
395
- skills: [
396
- // Define agent skills
397
- {
398
- id: "text-processing",
399
- name: "Text Processor",
400
- description: "Processes text requests and generates reports.",
401
- },
402
- ],
403
- // Add provider, auth details etc. as needed
326
+ capabilities: { streaming: true },
327
+ skills: [{ id: "processor", name: "Text Processor" }],
404
328
  },
405
329
  });
406
330
 
407
- // Start the server
408
- server.start();
409
- logger.info(
410
- `A2A Server started on http://localhost:3000${server.options.basePath}`
411
- );
412
- logger.info(
413
- `Agent Card available at http://localhost:3000/.well-known/agent.json`
414
- );
331
+ myServer.start();
332
+ console.log("A2A Server running on http://localhost:3000/a2a");
415
333
  ```
416
334
 
417
- ### Persistent Storage
335
+ #### Persistent Storage
418
336
 
419
- Easily switch to file-based persistence. Ensure the data directory exists and is writable.
337
+ Use `FileStore` for file-based persistence. Ensure the directory exists.
420
338
 
421
339
  ```typescript
422
- import { A2AServer, FileStore, logger } from "@artinet/sdk";
423
340
  import path from "path";
424
341
  import fs from "fs";
425
342
 
426
- // Assume myAgentLogic is defined as above
427
-
428
343
  const dataDir = path.join(process.cwd(), "a2a-data");
429
- // Ensure the directory exists
430
344
  if (!fs.existsSync(dataDir)) {
431
345
  fs.mkdirSync(dataDir, { recursive: true });
432
- logger.info(`Created data directory: ${dataDir}`);
433
346
  }
434
347
 
435
- // Create a file-based store
436
- const store = new FileStore(dataDir);
437
- logger.info(`Using FileStore at ${dataDir}`);
348
+ const myStore = new FileStore(dataDir);
438
349
 
439
- // Use the file store with your server
440
- const server = new A2AServer(myAgentLogic, {
441
- taskStore: store,
442
- port: 3001,
443
- basePath: "/a2a-persistent",
444
- card: {
445
- /* ... configure card ... */ name: "Persistent Agent",
446
- url: "http://localhost:3001/a2a-persistent",
447
- version: "1.0.1",
448
- capabilities: { streaming: true },
449
- skills: [{ id: "persistent-skill", name: "Persistent Task Handler" }],
450
- },
350
+ const myServer = new A2AServer({
351
+ taskStore: myStore,
352
+ ...
451
353
  });
452
- server.start();
453
- logger.info(
454
- `Persistent A2A Server started on http://localhost:3001${server.options.basePath}`
455
- );
456
354
  ```
457
355
 
458
- ## Logging
356
+ #### Logging
459
357
 
460
- Leverage the built-in `pino` logger for structured, performant logging.
358
+ Use the built-in `pino`-based logger. Configure with `configureLogger`.
461
359
 
462
360
  ```typescript
463
- import { logger, configureLogger, LogLevel } from "@artinet/sdk";
361
+ import { logger, configureLogger, LogLevel, logDebug } from "@artinet/sdk";
464
362
 
465
- // Configure logging (optional, defaults to 'error' level)
466
- configureLogger({
467
- level: "debug", // 'silent', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'
468
- name: "MyA2AApp", // Optional logger name
469
- });
363
+ // Configure logging level (optional)
364
+ configureLogger({ level: "debug" });
470
365
 
471
- // Use the logger throughout your application
472
366
  logger.info("Server starting...");
473
- logger.error(
474
- { err: new Error("Config Error"), detail: "Missing API key" },
475
- "Initialization failed"
476
- );
477
- logger.debug({ taskId: "task-123", status: "working" }, "Processing task");
367
+ //use helper functions
368
+ logDebug("LoggerTest", { taskId: "task-123" }, "Task status updated.");
369
+
370
+ // Create child logger with bound context
371
+ const taskLogger = logger.child({ taskId: "abc" });
372
+ taskLogger.info("Processing step X");
373
+ ```
374
+
375
+ #### Server Registration & Discovery
478
376
 
479
- // Create child loggers for specific modules
480
- const clientLogger = logger.child({ component: "A2AClient" });
481
- clientLogger.info("Sending request to agent...");
377
+ The SDK includes features to help make your agent discoverable:
482
378
 
483
- // Set level via environment variable (e.g., LOG_LEVEL=debug) for flexibility
484
- // configureLogger(); // Reads from process.env.LOG_LEVEL if set
379
+ - **Automatic Registration:** You can configure your `A2AServer` to automatically register your `AgentCard` with the [A2A Registry](https://artinet.io) upon startup by setting `register: true` (default: `false`) in the server parameters.
485
380
 
486
- // In production, typically use 'info' or 'warn'
487
- // configureLogger({ level: "info" });
381
+ ```typescript
382
+ const myServer = new A2AServer({
383
+ ...
384
+ card: {
385
+ ...
386
+ url: "http://my-public-domain:3000/my-agent", // Publicly accessible URL
387
+ ...
388
+ },
389
+ register: true, // Enable automatic registration on start
390
+ });
488
391
  ```
489
392
 
490
- ## Advanced Server Customization
393
+ - **Custom Agent Card Path:** By default, the server exposes its `AgentCard` at `/.well-known/agent.json` [RFC8615](https://datatracker.ietf.org/doc/html/rfc8615) and a fallback at `/agent-card`. You can specify a different fallback path using the `fallbackPath` option in `A2AServerParams`.
491
394
 
492
- While the `A2AServer` provides a convenient setup using a default [Jayson](https://github.com/tedeh/jayson) JSON-RPC server, you might need more control for specific use cases, such as:
395
+ ```typescript
396
+ const myServer = new A2AServer({
397
+ ...
398
+ basePath: "/apiV2"
399
+ fallbackPath: "/apiV2/custom-card-info", // Agent card available here
400
+ ...
401
+ });
402
+ // The AgentCard is now accessible at http://localhost:3001/apiV2/custom-card-info
403
+ ```
493
404
 
494
- - Integrating A2A methods into an existing Express application with custom middleware.
495
- - Adding non-A2A custom JSON-RPC methods alongside the standard A2A ones.
496
- - Using a different JSON-RPC library or implementation.
405
+ #### Advanced Server Customization
497
406
 
498
- The SDK allows you to provide your own function to create the JSON-RPC server instance via the `createJSONRPCServer` option in `A2AServerOptions`.
407
+ Provide a custom `createJSONRPCServer` function (implementing `JSONRPCServerFactory`) for fine-grained control over the underlying RPC server.
499
408
 
500
- ### Custom `createJSONRPCServer` Function
409
+ This factory function receives objects of type `CreateJSONRPCServerParams` & `RequestParams` containing the necessary SDK dependencies (`taskHandler`, `taskStore`, `agentCard`, `activeCancellations`, `createTaskContext`, `closeStreamsForTask`) and the specific method paramaters (i.e. `SendTaskRequest["params"]`). You can use these dependencies to configure the standard A2A methods and add your own custom JSON-RPC methods.
501
410
 
502
- To provide your own server, implement a function that adheres to the `JSONRPCServerFactory` type defined by the SDK:
411
+ The SDK exports default handlers for the standard A2A methods (e.g., `defaultSendTaskMethod`), create your own using dedicated A2A method types(`SendTaskMethod`) and use `createJSONRPCMethod` to easily wrap these methods with dependency injection and error handling.
412
+
413
+ See `src/server/lib/middleware/factory.ts` and `src/server/lib/middleware/a2a-methods.ts` for implementation details.
414
+
415
+ **Example:**
503
416
 
504
417
  ```typescript
505
- import {
506
- CreateJSONRPCServerParams,
507
- JSONRPCServerType,
508
- JSONRPCServerFactory, // Import the factory type
509
- } from "@artinet/sdk";
510
- import jayson from "jayson"; // Assuming Jayson is used
511
418
 
512
- // Your function implements the JSONRPCServerFactory interface
513
- const myCustomCreateServer: JSONRPCServerFactory = (
419
+ const myCustomSendMethod: SendTaskMethod = (
420
+ deps,
421
+ requestParams,
422
+ callback
423
+ ) => {
424
+ const { taskStore, taskHandler, createTaskContext } = deps;
425
+ const taskId = extractTaskId(requestParams.id);
426
+ const { message, sessionId, metadata } = requestParams;
427
+ ...
428
+ callback(null, ...);
429
+ };
430
+
431
+ const myCustomRPCServer: JSONRPCServerFactory = (
514
432
  params: CreateJSONRPCServerParams
515
433
  ): JSONRPCServerType => {
516
- // 'params' contains everything needed: taskStore, card, taskHandler, etc.
517
- // 'JSONRPCServerType' is expected to be a Jayson server instance by default
518
-
519
- // 1. Create your Jayson server instance (or adapt for another library)
520
- const jaysonServer = new jayson.Server(
521
- {
522
- // 2. Define A2A methods using the provided params (taskStore, taskHandler, etc.)
523
- "tasks/send": async (args: any, callback: jayson.JSONRPCCallback) => {
524
- // Implement tasks/send logic, potentially calling helper functions
525
- // from the SDK or using params.taskStore, params.taskHandler directly.
526
- // Remember to handle errors and use the callback.
527
- console.log("Custom tasks/send called!");
528
- // ... implementation ...
529
- callback(null, { id: args.id, status: { state: "submitted" } }); // Example response
530
- },
531
- "tasks/get": (/* ... */) => {
532
- /* ... */
533
- },
534
- "tasks/cancel": (/* ... */) => {
535
- /* ... */
536
- },
537
- // ... other A2A methods ...
538
-
539
- // 3. Add any custom methods
540
- myCustomMethod: (args: any, callback: jayson.JSONRPCCallback) => {
541
- console.log("My custom method called with:", args);
542
- callback(null, { result: "Custom success!" });
543
- },
544
- },
545
- {
546
- // Jayson server options
547
- }
548
- );
434
+ //Use a custom task/send method
435
+ const taskSendMethod = createJSONRPCMethod(params, myCustomSendMethod, "tasks/send");
436
+ const taskGetMethod = createJSONRPCMethod(params, defaultGetTaskMethod, "tasks/get");
437
+ const taskCancelMethod = createJSONRPCMethod(params, defaultCancelTaskMethod, "tasks/cancel");
438
+
439
+ // Note: Push notifications are not fully implemented yet
440
+ const taskPushNotificationSetMethod = createJSONRPCMethod(params, defaultSetTaskPushNotificationMethod, "tasks/pushNotification/set");
441
+ const taskPushNotificationGetMethod = createJSONRPCMethod(params, defaultGetTaskPushNotificationMethod, "tasks/pushNotification/get");
442
+
443
+ const rpcServer = new JSONRPCServer({
444
+ "tasks/send": taskSendMethod,
445
+ "tasks/get": taskGetMethod,
446
+ "tasks/cancel": taskCancelMethod,
447
+ "tasks/pushNotification/set": taskPushNotificationSetMethod,
448
+ "tasks/pushNotification/get": taskPushNotificationGetMethod,
449
+ });
549
450
 
550
- return jaysonServer;
451
+ return rpcServer;
551
452
  };
552
- ```
553
-
554
- Refer to the `src/server/lib/json-middleware.ts` file for the default implementation (`defaultCreateJSONRPCServer`) as a reference.
555
-
556
- ### Using the Custom Function
557
-
558
- Pass your function during `A2AServer` initialization:
559
-
560
- ```typescript
561
- import {
562
- A2AServer,
563
- InMemoryTaskStore /* ... other imports ... */,
564
- } from "@artinet/sdk";
565
- // Assume myAgentLogic and myCustomCreateServer are defined as above
566
453
 
567
- const store = new InMemoryTaskStore();
568
454
 
569
455
  const server = new A2AServer({
570
- taskHandler: myAgentLogic,
571
- taskStore: store,
572
- port: 3002,
573
- basePath: "/a2a-custom",
574
- createJSONRPCServer: myCustomCreateServer, // Pass your custom function here
575
- card: {
576
- /* ... configure card ... */ name: "Custom RPC Agent",
577
- url: "http://localhost:3002/a2a-custom",
578
- version: "1.1.0",
579
- capabilities: { streaming: true }, // Ensure capabilities match your implementation
580
- skills: [{ id: "custom-skill", name: "Custom RPC Handler" }],
581
- },
456
+ createJSONRPCServer: myCustomRPCServer,
457
+ ...
582
458
  });
583
-
584
- server.start(); // This will now use your custom server setup
585
459
  ```
586
460
 
587
- This provides maximum flexibility for integrating the A2A protocol handling into diverse server environments.
588
-
589
- ## API Reference
461
+ **Using the Custom Factory**
590
462
 
591
- ### Core Classes
463
+ Pass your factory function via the `createJSONRPCServer` option during `A2AServer` initialization.
592
464
 
593
- - `A2AClient`: Client for interacting with A2A servers.
594
- - `A2AServer`: Express-based server implementation.
595
- - `RpcError`: Client-side A2A protocol errors.
596
- - `A2AError`: Server-side A2A protocol errors (used internally).
597
- - `InMemoryTaskStore`: Simple in-memory task persistence.
598
- - `FileStore`: File-based task persistence.
599
-
600
- ### Key Types & Interfaces
601
-
602
- - `TaskHandler`: Async generator function defining agent logic (`async function*(context: TaskContext): AsyncGenerator<TaskYieldUpdate>`).
603
- - `TaskContext`: Provides task details (`taskId`, `userMessage`, `isCancelled`, `metadata`, `store`) to the `TaskHandler`.
604
- - `TaskYieldUpdate`: Union type for updates yielded by `TaskHandler` (status changes or artifacts).
605
- - `A2AServerOptions`: Configuration for `A2AServer` (port, store, card, basePath, etc.).
606
- - `AgentCard`, `Message`, `Part`, `Artifact`, `Task`, `TaskStatus`, etc.: Types mirroring the A2A JSON Schema.
465
+ **Important:** The default `A2AServer` setup automatically adds Express middleware to handle Server-Sent Events (SSE) for `tasks/sendSubscribe` and `tasks/resubscribe`, as well as the `/agent/card` (and `/.well-known/agent.json`) GET endpoints. If you are **not** using `A2AServer` and integrating the Jayson server middleware into your own Express application, you **must** implement these SSE and card endpoints yourself to maintain full A2A compliance, especially for streaming functionality. See `src/server/lib/express-server.ts` for how the default server handles these routes.
607
466
 
608
467
  ## Contributing
609
468
 
610
- Contributions are welcome! Please open an issue or submit a Pull Request.
469
+ Contributions are welcome! Please open an issue or submit a Pull Request on [GitHub](https://github.com/the-artinet-project/artinet-sdk).
470
+
471
+ Ensure code adheres to the project style and passes linting (`npm run lint`) and tests (`npm test`).
611
472
 
612
473
  ## License
613
474
 
614
- This project is licensed under the MIT License - see the `LICENSE` file for details.
475
+ This project is licensed under the Apache License 2.0 - see the `LICENSE` file for details.
615
476
 
616
477
  ## Acknowledgements
617
478
 
618
- This SDK builds upon the [official A2A Protocol schema](https://github.com/google/A2A/blob/main/samples/js/src/schema.ts) created by Google, providing a robust and developer-friendly implementation suitable for production environments.
479
+ This SDK builds upon the concepts and specifications of the [Agent2Agent (A2A) Protocol](https://github.com/google/A2A) initiated by Google. It utilizes the official [A2A JSON Schema](https://github.com/google/A2A/tree/main/specification/json) for protocol compliance.
480
+
481
+ Libraries used include:
482
+
483
+ - [Express.js](https://expressjs.com/) for the server framework.
484
+ - [Jayson](https://github.com/tedeh/jayson) for JSON-RPC handling.
485
+ - [Pino](https://getpino.io/) for logging.
486
+ - [EventSource Parser](https://github.com/rexxars/eventsource-parser) for SSE streaming.