@artinet/sdk 0.2.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.
- package/CHANGELOG.md +45 -0
- package/LICENSE +21 -0
- package/README.md +618 -0
- package/dist/client/a2a-client.d.ts +99 -0
- package/dist/client/a2a-client.d.ts.map +1 -0
- package/dist/client/a2a-client.js +171 -0
- package/dist/client/a2a-client.js.map +1 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/interfaces/client.d.ts +19 -0
- package/dist/client/interfaces/client.d.ts.map +1 -0
- package/dist/client/interfaces/client.js +2 -0
- package/dist/client/interfaces/client.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/server/a2a-server.d.ts +144 -0
- package/dist/server/a2a-server.d.ts.map +1 -0
- package/dist/server/a2a-server.js +384 -0
- package/dist/server/a2a-server.js.map +1 -0
- package/dist/server/index.d.ts +9 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +9 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/interfaces/context.d.ts +41 -0
- package/dist/server/interfaces/context.d.ts.map +1 -0
- package/dist/server/interfaces/context.js +2 -0
- package/dist/server/interfaces/context.js.map +1 -0
- package/dist/server/interfaces/params.d.ts +72 -0
- package/dist/server/interfaces/params.d.ts.map +1 -0
- package/dist/server/interfaces/params.js +3 -0
- package/dist/server/interfaces/params.js.map +1 -0
- package/dist/server/interfaces/server.d.ts +34 -0
- package/dist/server/interfaces/server.d.ts.map +1 -0
- package/dist/server/interfaces/server.js +2 -0
- package/dist/server/interfaces/server.js.map +1 -0
- package/dist/server/interfaces/store.d.ts +30 -0
- package/dist/server/interfaces/store.d.ts.map +1 -0
- package/dist/server/interfaces/store.js +2 -0
- package/dist/server/interfaces/store.js.map +1 -0
- package/dist/server/lib/express-server.d.ts +13 -0
- package/dist/server/lib/express-server.d.ts.map +1 -0
- package/dist/server/lib/express-server.js +51 -0
- package/dist/server/lib/express-server.js.map +1 -0
- package/dist/server/lib/json-middleware.d.ts +19 -0
- package/dist/server/lib/json-middleware.d.ts.map +1 -0
- package/dist/server/lib/json-middleware.js +180 -0
- package/dist/server/lib/json-middleware.js.map +1 -0
- package/dist/server/lib/state.d.ts +28 -0
- package/dist/server/lib/state.d.ts.map +1 -0
- package/dist/server/lib/state.js +108 -0
- package/dist/server/lib/state.js.map +1 -0
- package/dist/server/lib/storage/file.d.ts +62 -0
- package/dist/server/lib/storage/file.d.ts.map +1 -0
- package/dist/server/lib/storage/file.js +148 -0
- package/dist/server/lib/storage/file.js.map +1 -0
- package/dist/server/lib/storage/memory.d.ts +21 -0
- package/dist/server/lib/storage/memory.d.ts.map +1 -0
- package/dist/server/lib/storage/memory.js +40 -0
- package/dist/server/lib/storage/memory.js.map +1 -0
- package/dist/transport/index.d.ts +5 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +5 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/rpc/parser.d.ts +12 -0
- package/dist/transport/rpc/parser.d.ts.map +1 -0
- package/dist/transport/rpc/parser.js +37 -0
- package/dist/transport/rpc/parser.js.map +1 -0
- package/dist/transport/rpc/rpc-client.d.ts +77 -0
- package/dist/transport/rpc/rpc-client.d.ts.map +1 -0
- package/dist/transport/rpc/rpc-client.js +182 -0
- package/dist/transport/rpc/rpc-client.js.map +1 -0
- package/dist/transport/streaming/event-stream.d.ts +22 -0
- package/dist/transport/streaming/event-stream.d.ts.map +1 -0
- package/dist/transport/streaming/event-stream.js +79 -0
- package/dist/transport/streaming/event-stream.js.map +1 -0
- package/dist/transport/streaming/stream.d.ts +42 -0
- package/dist/transport/streaming/stream.d.ts.map +1 -0
- package/dist/transport/streaming/stream.js +128 -0
- package/dist/transport/streaming/stream.js.map +1 -0
- package/dist/types/extended-schema.d.ts +52 -0
- package/dist/types/extended-schema.d.ts.map +1 -0
- package/dist/types/extended-schema.js +7 -0
- package/dist/types/extended-schema.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/schema.d.ts +729 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +23 -0
- package/dist/types/schema.js.map +1 -0
- package/dist/utils/common/constants.d.ts +6 -0
- package/dist/utils/common/constants.d.ts.map +1 -0
- package/dist/utils/common/constants.js +28 -0
- package/dist/utils/common/constants.js.map +1 -0
- package/dist/utils/common/errors.d.ts +26 -0
- package/dist/utils/common/errors.d.ts.map +1 -0
- package/dist/utils/common/errors.js +59 -0
- package/dist/utils/common/errors.js.map +1 -0
- package/dist/utils/common/utils.d.ts +30 -0
- package/dist/utils/common/utils.d.ts.map +1 -0
- package/dist/utils/common/utils.js +67 -0
- package/dist/utils/common/utils.js.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logging/log.d.ts +30 -0
- package/dist/utils/logging/log.d.ts.map +1 -0
- package/dist/utils/logging/log.js +58 -0
- package/dist/utils/logging/log.js.map +1 -0
- package/dist/utils/logging/logger.d.ts +16 -0
- package/dist/utils/logging/logger.d.ts.map +1 -0
- package/dist/utils/logging/logger.js +38 -0
- package/dist/utils/logging/logger.js.map +1 -0
- package/package.json +83 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the @artinet/sdk package will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.2.0] - 2025-04-25
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Fixed TypeScript errors related to the `eventsource-parser` package imports
|
|
15
|
+
- Updated imports to use the correct types from `eventsource-parser` v1.1.1
|
|
16
|
+
- Properly typed the `EventSourceMessage` as `ParsedEvent`
|
|
17
|
+
- Refactored `createParser` implementation to match the package's API
|
|
18
|
+
- Fixed streaming response handler to use the correct event type checking
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Updated `tsconfig.json` to add `isolatedModules: true` for better compatibility with `ts-jest`
|
|
23
|
+
- Modified `package.json` test scripts to include `NODE_OPTIONS=--experimental-vm-modules` flag to support ES modules in Jest tests
|
|
24
|
+
|
|
25
|
+
### Improved
|
|
26
|
+
|
|
27
|
+
- Expanded test suite to achieve 95% code coverage
|
|
28
|
+
- Added tests for all client methods
|
|
29
|
+
- Added robust error handling tests
|
|
30
|
+
- Added tests for streaming functionality
|
|
31
|
+
- Added tests for push notification configuration
|
|
32
|
+
- Added tests for edge cases in agent card fetching and capability detection
|
|
33
|
+
|
|
34
|
+
## [0.1.0] - 2025-04-22
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- Initial release of the @artinet/sdk package
|
|
39
|
+
- Implementation of the Agent2Agent (A2A) Protocol client
|
|
40
|
+
- Support for sending tasks, retrieving statuses, and canceling operations
|
|
41
|
+
- Support for streaming responses and push notifications
|
|
42
|
+
- Comprehensive test suite and documentation
|
|
43
|
+
|
|
44
|
+
[0.2.0]: https://github.com/artinet/sdk/compare/v0.1.0...v0.2.0
|
|
45
|
+
[0.1.0]: https://github.com/artinet/sdk/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Artinet
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
# Artinet SDK: A Production-Ready A2A Protocol Implementation
|
|
2
|
+
|
|
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.
|
|
4
|
+
|
|
5
|
+
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
|
+
|
|
7
|
+
## Why Choose Artinet SDK?
|
|
8
|
+
|
|
9
|
+
While the official A2A repository provides basic samples, the Artinet SDK is engineered for real-world applications:
|
|
10
|
+
|
|
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.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @artinet/sdk
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
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.
|
|
49
|
+
|
|
50
|
+
**1. Server (`quick-server.ts`)**
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import {
|
|
54
|
+
A2AServer,
|
|
55
|
+
TaskContext,
|
|
56
|
+
TaskHandler,
|
|
57
|
+
InMemoryTaskStore,
|
|
58
|
+
logger,
|
|
59
|
+
configureLogger,
|
|
60
|
+
} from "@artinet/sdk";
|
|
61
|
+
|
|
62
|
+
// Set the log level to info to see startup messages
|
|
63
|
+
configureLogger({ level: "info" });
|
|
64
|
+
|
|
65
|
+
// Define the simplest possible agent logic
|
|
66
|
+
const quickAgentLogic: TaskHandler = async function* (context: TaskContext) {
|
|
67
|
+
const userInput =
|
|
68
|
+
context.userMessage.parts[0].type === "text"
|
|
69
|
+
? context.userMessage.parts[0].text
|
|
70
|
+
: "";
|
|
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
|
|
77
|
+
yield {
|
|
78
|
+
state: "completed",
|
|
79
|
+
message: {
|
|
80
|
+
role: "agent",
|
|
81
|
+
parts: [{ type: "text", text: `You said: ${userInput}` }],
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
logger.info(`Quick server responded.`);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Configure and start the server
|
|
88
|
+
const server = new A2AServer({
|
|
89
|
+
taskHandler: quickAgentLogic,
|
|
90
|
+
taskStore: new InMemoryTaskStore(),
|
|
91
|
+
port: 4000,
|
|
92
|
+
basePath: "/a2a",
|
|
93
|
+
card: {
|
|
94
|
+
name: "QuickStart Agent",
|
|
95
|
+
url: "http://localhost:4000/a2a",
|
|
96
|
+
version: "0.1.0",
|
|
97
|
+
capabilities: { streaming: true }, // Our handler uses yield
|
|
98
|
+
skills: [{ id: "echo", name: "Echo Skill" }],
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
server.start();
|
|
103
|
+
logger.info("Quick Start A2A Server running on http://localhost:4000/a2a");
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**2. Client (`quick-client.ts`)**
|
|
107
|
+
|
|
108
|
+
```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" });
|
|
113
|
+
|
|
114
|
+
async function runClient() {
|
|
115
|
+
const client = new A2AClient("http://localhost:4000/a2a");
|
|
116
|
+
const message = {
|
|
117
|
+
role: "user" as const,
|
|
118
|
+
parts: [{ type: "text" as const, text: "Hello Quick Start!" }],
|
|
119
|
+
};
|
|
120
|
+
|
|
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);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
runClient();
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**3. Run It**
|
|
144
|
+
|
|
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`
|
|
149
|
+
|
|
150
|
+
You'll see the server start, the client send a message, and the server respond with status updates.
|
|
151
|
+
|
|
152
|
+
## Client Usage
|
|
153
|
+
|
|
154
|
+
### Basic Client Usage
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { A2AClient, Message, Part, TaskSendParams } from "@artinet/sdk";
|
|
158
|
+
|
|
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
|
|
161
|
+
|
|
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);
|
|
166
|
+
|
|
167
|
+
// Example: Check if the agent supports streaming before using it
|
|
168
|
+
const supportsStreaming = await client.supports("streaming");
|
|
169
|
+
console.log("Supports Streaming:", supportsStreaming);
|
|
170
|
+
|
|
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
|
+
};
|
|
176
|
+
|
|
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
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Streaming Updates
|
|
197
|
+
|
|
198
|
+
Leverage real-time updates for long-running tasks if the agent supports it.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import {
|
|
202
|
+
A2AClient,
|
|
203
|
+
Message,
|
|
204
|
+
RpcError,
|
|
205
|
+
TaskStatusUpdateEvent,
|
|
206
|
+
TaskArtifactUpdateEvent,
|
|
207
|
+
} from "@artinet/sdk";
|
|
208
|
+
|
|
209
|
+
const client = new A2AClient("https://your-a2a-server.com/a2a");
|
|
210
|
+
|
|
211
|
+
async function runStreamingTask() {
|
|
212
|
+
if (!(await client.supports("streaming"))) {
|
|
213
|
+
console.log("Agent does not support streaming.");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const message: Message = {
|
|
218
|
+
role: "user",
|
|
219
|
+
parts: [
|
|
220
|
+
{ type: "text", text: "Generate a short story and a cover image." },
|
|
221
|
+
],
|
|
222
|
+
};
|
|
223
|
+
|
|
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}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
runStreamingTask();
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Authentication
|
|
277
|
+
|
|
278
|
+
Pass authentication credentials easily.
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { A2AClient } from "@artinet/sdk";
|
|
282
|
+
|
|
283
|
+
const client = new A2AClient("https://your-secure-a2a-server.com/a2a");
|
|
284
|
+
|
|
285
|
+
// Add a single header (e.g., Bearer token)
|
|
286
|
+
client.addHeader("Authorization", "Bearer your-api-token");
|
|
287
|
+
|
|
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(...)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Server Usage
|
|
299
|
+
|
|
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.
|
|
301
|
+
|
|
302
|
+
### Implementing an A2A Agent
|
|
303
|
+
|
|
304
|
+
Define your agent's core logic using an async generator `TaskHandler`.
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import {
|
|
308
|
+
A2AServer,
|
|
309
|
+
TaskContext,
|
|
310
|
+
TaskHandler
|
|
311
|
+
InMemoryTaskStore,
|
|
312
|
+
logger, // Use the built-in logger
|
|
313
|
+
} from "@artinet/sdk";
|
|
314
|
+
import path from "path";
|
|
315
|
+
|
|
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
|
+
|
|
322
|
+
// 1. Acknowledge receipt and indicate work is starting
|
|
323
|
+
yield {
|
|
324
|
+
state: "working",
|
|
325
|
+
message: {
|
|
326
|
+
role: "agent",
|
|
327
|
+
parts: [{ type: "text", text: "Got it! Processing your request..." }],
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// Simulate some work
|
|
332
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
333
|
+
|
|
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
|
+
yield {
|
|
354
|
+
// Artifact details
|
|
355
|
+
name: "report.txt",
|
|
356
|
+
mimeType: "text/plain",
|
|
357
|
+
parts: [{ type: "text", text: artifactContent }],
|
|
358
|
+
};
|
|
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
|
+
|
|
367
|
+
// 5. Yield the final response
|
|
368
|
+
const finalResponse = `Finished processing: "${userRequest}". See attached report.`;
|
|
369
|
+
yield {
|
|
370
|
+
state: "completed",
|
|
371
|
+
message: { role: "agent", parts: [{ type: "text", text: finalResponse }] },
|
|
372
|
+
};
|
|
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();
|
|
378
|
+
|
|
379
|
+
// Configure and create the A2A server instance
|
|
380
|
+
const server = new A2AServer({
|
|
381
|
+
myAgentLogic,
|
|
382
|
+
taskStore: store,
|
|
383
|
+
port: 3000,
|
|
384
|
+
basePath: "/a2a", // The path where A2A routes will be mounted
|
|
385
|
+
// Customize the agent card served at /.well-known/agent.json
|
|
386
|
+
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
|
|
390
|
+
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
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
|
|
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
|
+
);
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Persistent Storage
|
|
418
|
+
|
|
419
|
+
Easily switch to file-based persistence. Ensure the data directory exists and is writable.
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
import { A2AServer, FileStore, logger } from "@artinet/sdk";
|
|
423
|
+
import path from "path";
|
|
424
|
+
import fs from "fs";
|
|
425
|
+
|
|
426
|
+
// Assume myAgentLogic is defined as above
|
|
427
|
+
|
|
428
|
+
const dataDir = path.join(process.cwd(), "a2a-data");
|
|
429
|
+
// Ensure the directory exists
|
|
430
|
+
if (!fs.existsSync(dataDir)) {
|
|
431
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
432
|
+
logger.info(`Created data directory: ${dataDir}`);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Create a file-based store
|
|
436
|
+
const store = new FileStore(dataDir);
|
|
437
|
+
logger.info(`Using FileStore at ${dataDir}`);
|
|
438
|
+
|
|
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
|
+
},
|
|
451
|
+
});
|
|
452
|
+
server.start();
|
|
453
|
+
logger.info(
|
|
454
|
+
`Persistent A2A Server started on http://localhost:3001${server.options.basePath}`
|
|
455
|
+
);
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## Logging
|
|
459
|
+
|
|
460
|
+
Leverage the built-in `pino` logger for structured, performant logging.
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
import { logger, configureLogger, LogLevel } from "@artinet/sdk";
|
|
464
|
+
|
|
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
|
+
});
|
|
470
|
+
|
|
471
|
+
// Use the logger throughout your application
|
|
472
|
+
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");
|
|
478
|
+
|
|
479
|
+
// Create child loggers for specific modules
|
|
480
|
+
const clientLogger = logger.child({ component: "A2AClient" });
|
|
481
|
+
clientLogger.info("Sending request to agent...");
|
|
482
|
+
|
|
483
|
+
// Set level via environment variable (e.g., LOG_LEVEL=debug) for flexibility
|
|
484
|
+
// configureLogger(); // Reads from process.env.LOG_LEVEL if set
|
|
485
|
+
|
|
486
|
+
// In production, typically use 'info' or 'warn'
|
|
487
|
+
// configureLogger({ level: "info" });
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
## Advanced Server Customization
|
|
491
|
+
|
|
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:
|
|
493
|
+
|
|
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.
|
|
497
|
+
|
|
498
|
+
The SDK allows you to provide your own function to create the JSON-RPC server instance via the `createJSONRPCServer` option in `A2AServerOptions`.
|
|
499
|
+
|
|
500
|
+
### Custom `createJSONRPCServer` Function
|
|
501
|
+
|
|
502
|
+
To provide your own server, implement a function that adheres to the `JSONRPCServerFactory` type defined by the SDK:
|
|
503
|
+
|
|
504
|
+
```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
|
+
|
|
512
|
+
// Your function implements the JSONRPCServerFactory interface
|
|
513
|
+
const myCustomCreateServer: JSONRPCServerFactory = (
|
|
514
|
+
params: CreateJSONRPCServerParams
|
|
515
|
+
): 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
|
+
);
|
|
549
|
+
|
|
550
|
+
return jaysonServer;
|
|
551
|
+
};
|
|
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
|
+
|
|
567
|
+
const store = new InMemoryTaskStore();
|
|
568
|
+
|
|
569
|
+
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
|
+
},
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
server.start(); // This will now use your custom server setup
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
This provides maximum flexibility for integrating the A2A protocol handling into diverse server environments.
|
|
588
|
+
|
|
589
|
+
## API Reference
|
|
590
|
+
|
|
591
|
+
### Core Classes
|
|
592
|
+
|
|
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.
|
|
607
|
+
|
|
608
|
+
## Contributing
|
|
609
|
+
|
|
610
|
+
Contributions are welcome! Please open an issue or submit a Pull Request.
|
|
611
|
+
|
|
612
|
+
## License
|
|
613
|
+
|
|
614
|
+
This project is licensed under the MIT License - see the `LICENSE` file for details.
|
|
615
|
+
|
|
616
|
+
## Acknowledgements
|
|
617
|
+
|
|
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.
|