[![npm version](https://img.shields.io/npm/v/@artinet/sdk.svg)](https://www.npmjs.com/package/@artinet/sdk)
[![npm downloads](https://img.shields.io/npm/dt/@artinet/sdk.svg)](https://www.npmjs.com/package/@artinet/sdk)
[![Apache License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
[![Known Vulnerabilities](https://snyk.io/test/npm/@artinet/sdk/badge.svg)](https://snyk.io/test/npm/@artinet/sdk)

<!-- [![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) -->

# Artinet SDK

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/).

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.

## Table of Contents
- [Artinet SDK](#artinet-sdk)
  - [Table of Contents](#table-of-contents)
  - [Features](#features)
  - [Installation](#installation)
  - [Requirements](#requirements)
  - [Example](#example)
  - [Class Documentation](#class-documentation)
    - [Core Classes](#core-classes)
    - [Key Types \& Interfaces](#key-types--interfaces)
  - [Running Tests](#running-tests)
  - [Typescript](#typescript)
  - [Usage](#usage)
    - [Client](#client)
      - [Basic Client Usage](#basic-client-usage)
      - [Streaming Updates](#streaming-updates)
      - [Authentication](#authentication)
    - [Server](#server)
      - [Implementing an A2A Agent](#implementing-an-a2a-agent)
      - [Persistent Storage](#persistent-storage)
      - [Logging](#logging)
      - [Server Registration \& Discovery](#server-registration--discovery)
      - [Advanced Server Customization](#advanced-server-customization)
    - [Quick-Agents (Alpha)](#quick-agents-alpha)
  - [Contributing](#contributing)
  - [License](#license)
  - [Acknowledgements](#acknowledgements)

## Features

- **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`.
- **Enhanced Client:** `A2AClient` features refined error handling (`RpcError`), flexible header management for authentication, and clear separation of concerns.
- **TypeScript First:** Fully written in TypeScript with comprehensive type definitions for a robust developer experience.
- **Flexible Storage:** Offers built-in `InMemoryTaskStore` (development/testing) and `FileStore` (persistent), with the `TaskStore` interface allowing custom storage solutions.
- **Protocol Compliance:** Implements the complete A2A specification using the official JSON schema, ensuring interoperability.
- **Robust Streaming:** Reliable SSE support for `tasks/sendSubscribe` & `tasks/resubscribe` using `eventsource-parser`.
- **Configurable Logging:** Integrated structured logging via `pino`. Configurable levels using `configureLogger` and `LogLevel`.
- **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.
- **Comprehensive Testing:** Includes a suite of tests to ensure reliability and maintainability.
- **Code Deployment (Beta)** | Bundle, test, and (soon) deploy agent code. Includes bundler, task wrapper, and test deployment utility.           | `bundle`, `testDeployment`, `taskHandlerProxy`, `fetchResponseProxy`, `ServerDeploymentRequestParams`, `ServerDeploymentResponse` |

| Component/Feature   | Description                                                                 | Key Classes/Types                                                                            |
| :------------------ | :-------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- |
| **Client**          | Interact with A2A-compliant agents. Supports standard & streaming requests. | `A2AClient`, `RpcError`                                                                      |
| **Server**          | Host A2A-compliant agents. Handles protocol details, routing, SSE.          | `A2AServer`, `A2AServerParams`                                                               |
| **Task Handling**   | Define agent logic using async generators.                                  | `TaskHandler`, `TaskContext`, `TaskYieldUpdate`                                              |
| **Storage**         | Persist task state. In-memory and file-based options included.              | `TaskStore`, `InMemoryTaskStore`, `FileStore`                                                |
| **Streaming (SSE)** | Handle real-time updates via SSE for `tasks/sendSubscribe`/`resubscribe`.   | `TaskStatusUpdateEvent`, `TaskArtifactUpdateEvent`                                           |
| **Logging**         | Configure structured logging for debugging and monitoring.                  | `logger`, `configureLogger`, `LogLevel`                                                      |
| **Advanced Server** | Customize the underlying JSON-RPC server or integrate into existing apps.   | `JSONRPCServerFactory`, `CreateJSONRPCServerParams`, `createJSONRPCMethod`, A2A Method Types |
| **Core Types**      | Based on the official A2A JSON Schema.                                      | `AgentCard`, `Task`, `Message`, `Part`, `Artifact`, etc.                                     |

## Installation

```bash
npm install @artinet/sdk
```

## Requirements

- Node.js (v22.0.0 or higher recommended, check `package.json` engines for exact requirement)

## Example
 
A basic A2A server and client interaction. For more detailed examples, see the `examples/` directory.

**1. Server (`quick-server.ts`)**

```typescript
import {
  A2AServer,
  TaskContext,
  TaskHandler,
  InMemoryTaskStore,
} from "@artinet/sdk";

// Minimal agent logic: receive text, yield working state, yield completed state with echo
const quickAgentLogic: TaskHandler = async function* (context: TaskContext) {
  const userInput =
    context.userMessage.parts[0].type === "text"
      ? context.userMessage.parts[0].text
      : "";
  yield { state: "working" };
  // Simulate some work if needed, check context.isCancelled()
  yield {
    state: "completed",
    message: {
      role: "agent",
      parts: [{ type: "text", text: `You said: ${userInput}` }],
    },
  };
};

const server = new A2AServer({
  taskHandler: quickAgentLogic,
  taskStore: new InMemoryTaskStore(),
  port: 4000,
  basePath: "/a2a",
  card: {
    name: "QuickStart Agent",
    url: "http://localhost:4000/a2a",
    version: "0.1.0",
    capabilities: { streaming: true },
    skills: [{ id: "echo", name: "Echo Skill" }],
  },
});

server.start();
console.log("A2A Server running at http://localhost:4000/a2a");
```

**2. Client (`quick-client.ts`)**

```typescript
import { A2AClient, TaskStatusUpdateEvent } from "@artinet/sdk";

async function runClient() {
  const client = new A2AClient("http://localhost:4000/a2a");

   const message = {
    role: "user" as const,
    parts: [{ type: "text" as const, text: "Hello Quick Start!" }],
  };

  const stream = client.sendTaskSubscribe({ id: "quick-task-1", message });

  for await (const update of stream) {
    // process the update
    ...
  }
  console.log("Stream finished.");
}

runClient().catch(console.error);
```

## Class Documentation

The Artinet SDK provides several core classes and interfaces for building A2A clients and servers.

### Core Classes

| Class               | Description                                                         |
| :------------------ | :------------------------------------------------------------------ |
| `A2AClient`         | Client for interacting with A2A servers.                            |
| `A2AServer`         | Express-based server implementation for hosting A2A agents.         |
| `RpcError`          | Represents client-side errors encountered during A2A communication. |
| `InMemoryTaskStore` | Simple in-memory task persistence (ideal for development/testing).  |
| `FileStore`         | File-based task persistence (stores task data in the filesystem).   |

### Key Types & Interfaces

| Type/Interface                                            | Description                                                                                                                        |
| :-------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- |
| `TaskHandler`                                             | Async generator function defining the core agent logic (`async function*(context: TaskContext): AsyncGenerator<TaskYieldUpdate>`). |
| `TaskContext`                                             | Provides task details (`task`, `userMessage`, `history`, `isCancelled()`) to the `TaskHandler`.                                    |
| `TaskStore`                                               | Interface defining the contract for task persistence implementations (like `InMemoryTaskStore`, `FileStore`).                      |
| `TaskYieldUpdate`                                         | Union type for updates yielded by a `TaskHandler` (representing status changes or generated artifacts).                            |
| `A2AServerParams`                                         | Configuration object passed to the `A2AServer` constructor (port, store, card, basePath, handler, etc.).                           |
| `AgentCard`                                               | Describes the agent's capabilities, metadata, skills, and endpoint URL.                                                            |
| `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.    |
| `TaskStatusUpdateEvent`, `TaskArtifactUpdateEvent`        | Specific types for Server-Sent Events (SSE) received during streaming operations (`tasks/sendSubscribe`, `tasks/resubscribe`).     |
| `LogLevel`                                                | Enum defining logging levels (`error`, `warn`, `info`, `debug`, `trace`) used with the built-in logger.                            |
| `JSONRPCServerFactory`                                    | Function signature for providing a custom JSON-RPC server creation logic to `A2AServer` for advanced customization.                |
| `CreateJSONRPCServerParams`                               | Object containing dependencies provided _to_ a `JSONRPCServerFactory` function.                                                    |
| `SendTaskMethod`, `GetTaskMethod`, ...                    | Type signatures for specific A2A method handlers, used when implementing custom server logic with `createJSONRPCMethod`.           |

## Running Tests

```bash
npm test
```

To run tests with coverage:

```bash
npm run test:coverage
```

## Typescript

The Artinet SDK is written entirely in TypeScript and includes comprehensive type definitions, providing strong typing and enhanced developer experience.

## Usage

### Client

Interact with A2A-compliant agents using the `A2AClient`. See `examples/` for more.

#### Basic Client Usage

Send a task using `tasks/send`.

```typescript
import { A2AClient, Message } from "@artinet/sdk";

async function runBasicTask() {
  const client = new A2AClient("https://your-a2a-server.com/a2a");
  const message: Message = {
    role: "user",
    parts: [{ type: "text", text: "What is the capital of France?" }],
  };

  const task = await client.sendTask({ id: "basic-task-1", message });
  console.log("Task Completed:", task);
}
```

#### Streaming Updates

Receive real-time updates via SSE using `tasks/sendSubscribe` (recommended).

```typescript
import {
  A2AClient,
  Message,
  TaskStatusUpdateEvent,
  TaskArtifactUpdateEvent,
} from "@artinet/sdk";

async function runStreamingTask() {
  const client = new A2AClient("https://your-a2a-server.com/a2a");
  const message: Message = {
    role: "user",
    parts: [{ type: "text", text: "Tell me a short story." }],
  };

  const stream = client.sendTaskSubscribe({ id: "streaming-task-1", message });

  for await (const update of stream) {
    if ((update as TaskStatusUpdateEvent).status) {
      console.log("Status:", (update as TaskStatusUpdateEvent).status.state);
    } else if ((update as TaskArtifactUpdateEvent).artifact) {
      console.log(
        "Artifact:",
        (update as TaskArtifactUpdateEvent).artifact.name
      );
    }
  }
  console.log("Stream finished.");
}
```

#### Authentication

Add headers using `addHeader` or `setHeaders`.

```typescript
import { A2AClient } from "@artinet/sdk";

const client = new A2AClient("https://your-secure-a2a-server.com/a2a");

// Add a single header
client.addHeader("Authorization", "Bearer your-api-token");

// Set multiple headers (overwrites existing)
client.setHeaders({ Authorization: "Bearer ...", "X-Custom": "value" });
```

### Server

Host agents using `A2AServer`. Handles protocol details. See `examples/` for more.

#### Implementing an A2A Agent

Define agent behavior with an async generator `TaskHandler`.

```typescript
import {
  A2AServer,
  TaskContext,
  TaskHandler,
  InMemoryTaskStore,
} from "@artinet/sdk";


const myAgent: TaskHandler = async function* (context: TaskContext) {

  yield {
    state: "working",
    message: {
      role: "agent",
      parts: [{ type: "text", text: "Processing..." }],
    },
  };

  // Check context.isCancelled() if operation is long
  // await someAsyncTask();
  ...

  yield {
    name: "result.txt",
    mimeType: "text/plain",
    parts: [{ type: "text", text: "Report data" }],
  };

  yield {
    state: "completed",
    message: {
      role: "agent",
      parts: [{ type: "text", text: "Finished processing." }],
    },
  };
};

const myServer = new A2AServer({
  handler: myAgent,
  taskStore: new InMemoryTaskStore(),
  port: 3000,
  basePath: "/a2a",
  card: {
    name: "Example Agent",
    url: "http://localhost:3000/a2a",
    version: "1.0.0",
    capabilities: { streaming: true },
    skills: [{ id: "processor", name: "Text Processor" }],
  },
});

myServer.start();
console.log("A2A Server running on http://localhost:3000/a2a");
```

#### Persistent Storage

Use `FileStore` for file-based persistence. Ensure the directory exists.

```typescript
import path from "path";
import fs from "fs";

const dataDir = path.join(process.cwd(), "a2a-data");
if (!fs.existsSync(dataDir)) {
  fs.mkdirSync(dataDir, { recursive: true });
}

const myStore = new FileStore(dataDir);

const myServer = new A2AServer({
  taskStore: myStore,
  ...
});
```

#### Logging

Use the built-in `pino`-based logger. Configure with `configureLogger`.

```typescript
import { logger, configureLogger, LogLevel, logDebug } from "@artinet/sdk";

// Configure logging level (optional)
configureLogger({ level: "debug" });

logger.info("Server starting...");
//use helper functions
logDebug("LoggerTest", { taskId: "task-123" }, "Task status updated.");

// Create child logger with bound context
const taskLogger = logger.child({ taskId: "abc" });
taskLogger.info("Processing step X");
```

#### Server Registration & Discovery

The SDK includes features to help make your agent discoverable:

- **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.

```typescript
  const myServer = new A2AServer({
    ...
    card: {
      ...
      url: "http://my-public-domain:3000/my-agent", // Publicly accessible URL
      ...
    },
    register: true, // Enable automatic registration on start
  });
```

- **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`.

```typescript
  const myServer = new A2AServer({
    ...
    basePath: "/apiV2"
    fallbackPath: "/apiV2/custom-card-info", // Agent card available here
    ...
  });
// The AgentCard is now accessible at http://localhost:3001/apiV2/custom-card-info
```

#### Advanced Server Customization

Provide a custom `createJSONRPCServer` function (implementing `JSONRPCServerFactory`) for fine-grained control over the underlying RPC server.

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.

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.

See `src/server/lib/middleware/factory.ts` and `src/server/lib/middleware/a2a-methods.ts` for implementation details.

**Example:**

```typescript

const myCustomSendMethod: SendTaskMethod = (
  deps,
  requestParams,
  callback
) => {
  const { taskStore, taskHandler, createTaskContext } = deps;
  const taskId = extractTaskId(requestParams.id);
  const { message, sessionId, metadata } = requestParams;
  ...
  callback(null, ...);
};

const myCustomRPCServer: JSONRPCServerFactory = (
  params: CreateJSONRPCServerParams
): JSONRPCServerType => {
  //Use a custom task/send method
  const taskSendMethod = createJSONRPCMethod(params, myCustomSendMethod, "tasks/send");
  const taskGetMethod = createJSONRPCMethod(params, defaultGetTaskMethod, "tasks/get");
  const taskCancelMethod = createJSONRPCMethod(params, defaultCancelTaskMethod, "tasks/cancel");

  // Note: Push notifications are not fully implemented yet
  const taskPushNotificationSetMethod = createJSONRPCMethod(params, defaultSetTaskPushNotificationMethod, "tasks/pushNotification/set");
  const taskPushNotificationGetMethod = createJSONRPCMethod(params, defaultGetTaskPushNotificationMethod, "tasks/pushNotification/get");

  const rpcServer = new JSONRPCServer({
    "tasks/send": taskSendMethod,
    "tasks/get": taskGetMethod,
    "tasks/cancel": taskCancelMethod,
    "tasks/pushNotification/set": taskPushNotificationSetMethod,
    "tasks/pushNotification/get": taskPushNotificationGetMethod,
  });

  return rpcServer;
};


const server = new A2AServer({
  createJSONRPCServer: myCustomRPCServer,
  ...
});
```

**Using the Custom Factory**

Pass your factory function via the `createJSONRPCServer` option during `A2AServer` initialization.

**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.

### Quick-Agents (Alpha)

We are excited to introduce new capabilities for deploying agents directly onto the artinet. 

We've added a `testDeployment` utility which is available for all users letting you bundle and test your agent logic in a temporary sandboxed environment. 

**QUICK-AGENTS ARE HERE!** Use the new `fullDeployment` utility, which allows direct deployment of your bundled agent code and `AgentCard` to the Artinet platform (requires an `ARTINET_API_KEY`).

To join the beta waitlist, please email us at humans@artinet.io and stay tuned for more updates!

Key features include:

-   **Easy Agent Bundling:** Bundle your agent's code and dependencies into a single file using the `bundle` utility.
    ```typescript
    import { bundle } from "@artinet/sdk";

    const bundledCode = await bundle(new URL('./your-agent.ts', import.meta.url));
    ```

-   **Sandboxed Enviroments:** Utilities like `taskHandlerProxy` and `fetchResponseProxy` streamline agent logic for quick and easy deployments.
    ```typescript
    import { taskHandlerProxy, fetchResponseProxy } from "@artinet/sdk/";

    async function myAgent(context) {
      //call any other agent in the artinet with fetchResponseProxy
      const friendResponse = await fetchResponseProxy("MyFriendlyAgent", 
        [{ role: "user", content: "Tell me a joke."}]
      );
      yield { state: "completed", message: { role: "agent", parts: [{type: "text", text: freiendResponse.choices[0].message.content }] } };
    }

    await taskHandlerProxy(myAgent);
    ```

-   **Test-Agents (Experimental):** Simulate and test your agents @ agents.artinet.io/test/deploy using the `testDeployment` tool.
    ```typescript
    import { testDeployment, ServerDeploymentRequestParams, SendTaskRequest } from "@artinet/sdk";

    const deploymentParams: ServerDeploymentRequestParams = {
      code: "/* bundled code string */",
    };
    //create a list of tasks for your agent to complete once deployed
    const testRequests: SendTaskRequest[] = [
      { id: "t1", message: { role: "user", parts: [{ type: "text", text: "Hi!" }] } }
    ];

    for await (const result of testDeployment(deploymentParams, testRequests)) {
      console.log(result); //process the task completion requests as they come in to confirm your agents logic
    }
    ```

-   **Full Deployment (Experimental):** Deploy your agent to the Artinet platform using the `fullDeployment` utility.
    ```typescript
    import { fullDeployment, ServerDeploymentRequestParams } from "@artinet/sdk";

    const deploymentParams: ServerDeploymentRequestParams = {
      name: "My Awesome Agent",
      agentCard: { /* your agent card */ },
      code: "/* bundled code string */",
    };

    const deploymentResult = await fullDeployment(deploymentParams); // Requires an ARTINET_API_KEY environment variable
    console.log("Deployment Result:", deploymentResult);
    ```

-   **Dedicated Endpoints:** Once deployed your agent will be available On-Demand at its dedicated enpoint. (e.g. "https://agents.stage.artinet.io/agentId=0xabf698845743538727a81352bfcfdb724e5c2bbe3113a26362482248f9f3e5fa/.well-known/agent.json")
-   **New Types:** To support these features, new types for server deployment requests and responses (such as `ServerDeploymentRequestParams`, `ServerDeploymentResponse`) have been added to `src/types/extended-schema.ts`.

-   **QUICK-AGENT FAQs**
-   Test-Agents expire after 60s (need more time? let us know @humans@artinet.io)
-   Quick-Agents do not have access to a filesystem or networking (limited persistance & networking capabalities are on the project roadmap).
-   Quick-Agents v0 does not support streaming, push notifications or state transition history (these capabilities are on the project roadmap).
-   Larger deployments can take significant time to deploy which may cause `fullDeployment` to timeout. In such cases wait to see if the listing has been added to your account before trying to deploy again.
-   Quick-Agent logic is public, therefore the artinet project is not liable for any sensitive material held within a deployment.
-   v0.0.5c of the artinet platform will enable search by registrationId/agentId.
  
Sign-up at [artinet.io](https://artinet.io/) to deploy your Quick-Agent today!

## Contributing

Contributions are welcome! Please open an issue or submit a Pull Request on [GitHub](https://github.com/the-artinet-project/artinet-sdk).

Ensure code adheres to the project style and passes linting (`npm run lint`) and tests (`npm test`).

## License

This project is licensed under the Apache License 2.0 - see the `LICENSE` file for details.

## Acknowledgements

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.

Libraries used include:

- [Express.js](https://expressjs.com/) for the server framework.
- [Jayson](https://github.com/tedeh/jayson) for JSON-RPC handling.
- [Pino](https://getpino.io/) for logging.
- [EventSource Parser](https://github.com/rexxars/eventsource-parser) for SSE streaming.
