@artinet/sdk
Version:
A TypeScript SDK for building collaborative AI agents.
305 lines (304 loc) • 12 kB
JavaScript
/**
* Copyright 2025 The Artinet Project
* SPDX-License-Identifier: Apache-2.0
*/
import { A2A } from "../types/index.js";
import { validateSchema } from "../utils/schema-validation.js";
import { logger } from "../config/index.js";
import * as describe from "../create/describe.js";
import { ClientFactory, ClientFactoryOptions, AgentCardResolver, } from '@a2a-js/sdk/client';
import { Runtime } from '@artinet/types';
import { z } from 'zod/v4';
class HeaderInterceptor {
_getCustomHeaders;
constructor(_getCustomHeaders) {
this._getCustomHeaders = _getCustomHeaders;
}
before(args) {
const options = args.options ?? { serviceParameters: {} };
const serviceParameters = options.serviceParameters;
options.serviceParameters = {
...serviceParameters,
...this._getCustomHeaders(),
};
args.options = options;
return Promise.resolve();
}
after() {
return Promise.resolve();
}
}
class NestedAgentCardResolver {
_options;
_fetchImpl;
constructor(_options) {
this._options = _options;
this._fetchImpl = _options.fetchImpl ?? fetch;
}
get options() {
return this._options;
}
async resolve(baseUrl, _path) {
const path = _path ?? this._options?.path;
const agentCard = await AgentCardResolver.default
.resolve(baseUrl, path)
.catch((error) => {
logger.debug('Failed to fetch agent card', { error });
return undefined;
});
if (agentCard) {
return agentCard;
}
logger.debug('Fetching agent card from', { baseUrl });
const response = await this._fetchImpl(baseUrl + (path ?? '/well-known/agent-card.json'));
if (!response.ok) {
throw new Error(`Failed to fetch Agent Card from ${baseUrl}: ${response.status}`);
}
return await validateSchema(A2A.AgentCardSchema, await response.json());
}
}
export const MessengerParamsSchema = Runtime.ServerConfigSchema.pick({
headers: true,
authToken: true,
}).extend({
baseUrl: z.url(),
fallbackPath: z.string().optional(),
});
/**
* Checks if the given parameters are a valid MessengerParams object.
* @param params The parameters to check.
* @returns True if the parameters are a valid MessengerParams object, false otherwise.
*/
export const isMessengerParams = (params) => {
return MessengerParamsSchema.safeParse(params).success;
};
/**
* Messenger is the main communication client for interacting with remote A2A-compatible services.
* It provides methods for sending messages, retrieving tasks, canceling operations, and handling streaming responses.
*/
class Messenger {
_headers;
_fallbackPath;
_baseUrl;
_factory;
clientPromise;
constructor(baseUrl, _headers = {}, _fallbackPath, factory = ClientFactoryOptions.default, config) {
this._headers = _headers;
this._fallbackPath = _fallbackPath;
this._baseUrl = typeof baseUrl === 'string' ? baseUrl : baseUrl.toString();
this._fallbackPath = _fallbackPath ?? '/agent.json';
this._factory = new ClientFactory(ClientFactoryOptions.createFrom(factory, {
clientConfig: {
...config,
interceptors: [...(config?.interceptors ?? []), new HeaderInterceptor(() => this.headers)],
},
}));
//RAII: to try and maintain backwards compatibility with the old client?
this.clientPromise = this.reset(this._baseUrl, this._fallbackPath);
}
async reset(baseUrl = this._baseUrl, fallbackPath = this._fallbackPath) {
this._baseUrl = typeof baseUrl === 'string' ? baseUrl : baseUrl.toString();
this._fallbackPath = fallbackPath ?? '/agent.json';
this.clientPromise = this._factory.createFromUrl(this._baseUrl).catch(async (error) => {
if (!this._fallbackPath) {
logger.debug('Messenger: Failed to create client, no fallback path provided', { error });
throw error;
}
logger.debug('Messenger: Failed to create client, falling back to fallback path: ', {
error,
fallbackPath: this._fallbackPath,
});
return await this._factory.createFromUrl(this._baseUrl, this._fallbackPath).catch(async (error) => {
logger.debug('Messenger: Failed to create client, at fallback path: ', {
error,
fallbackPath: this._fallbackPath,
});
throw error;
});
});
return this.clientPromise;
}
get baseUrl() {
return this._baseUrl;
}
get headers() {
return this._headers;
}
set headers(headers) {
this._headers = headers;
}
get _client() {
return this.clientPromise;
}
/**
* Retrieves the AgentCard from the A2A server.
* @returns A promise resolving to the AgentCard.
*/
async getAgentCard(requestOptions) {
const client = await this._client;
return await client.getAgentCard(requestOptions);
}
/**
* Sends a Message to an agent server.
* @param params The {@link A2A.MessageSendParams} for the message/send method.
* @param options The {@link RequestOptions} for the request.
* @returns A promise resolving to {@link A2A.SendMessageSuccessResult} from the agent server.
*/
async sendMessage(params, options) {
const client = await this._client;
return await validateSchema(A2A.SendMessageSuccessResultSchema, await client.sendMessage(describe.messageSendParams(params), options));
}
/**
* Sends a Message and returns a stream of status and artifact updates.
* @param params Task parameters for the request
* @returns An AsyncIterable that yields TaskStatusUpdateEvent/TaskArtifactUpdateEvent/Task/Message payloads.
*/
async *sendMessageStream(params, options) {
try {
const client = await this._client;
yield* client.sendMessageStream(describe.messageSendParams(params), options);
}
catch (error) {
logger.error('Messenger: Failed to send message stream', { error });
throw error;
}
}
/**
* Retrieves the current state of a task.
* @param params The parameters for the tasks/get method.
* @returns A promise resolving to the Task object or null.
*/
async getTask(params, options) {
const client = await this._client;
return await validateSchema(A2A.TaskSchema, await client.getTask(params, options));
}
/**
* Cancels a currently running task.
* @param params The parameters for the tasks/cancel method.
* @returns A promise resolving to the updated Task object (usually canceled state) or null.
*/
async cancelTask(params, options) {
const client = await this._client;
return await validateSchema(A2A.TaskSchema, await client.cancelTask(params, options));
}
/**
* Sets or updates the push notification config for a task.
* @param params The parameters for the tasks/pushNotificationConfig/set method (which is TaskPushNotificationConfig).
* @returns A promise resolving to the confirmed TaskPushNotificationConfig or null.
*/
async setTaskPushNotificationConfig(params, options) {
const client = await this._client;
return await validateSchema(A2A.TaskPushNotificationConfigSchema, await client.setTaskPushNotificationConfig(params, options));
}
/**
* Retrieves the currently configured push notification config for a task.
* @param params The parameters for the tasks/pushNotificationConfig/get method.
* @returns A promise resolving to the TaskPushNotificationConfig or null.
*/
async getTaskPushNotificationConfig(params, options) {
const client = await this._client;
return await validateSchema(A2A.TaskPushNotificationConfigSchema, await client.getTaskPushNotificationConfig(params, options));
}
async listTaskPushNotificationConfig(params, options) {
const client = await this._client;
return await validateSchema(A2A.ListTaskPushNotificationConfigResultSchema, await client.listTaskPushNotificationConfig(params, options));
}
async deleteTaskPushNotificationConfig(params, options) {
const client = await this._client;
return client.deleteTaskPushNotificationConfig(params, options);
}
/**
* Resubscribes to an existing task's update stream.
* @param params Parameters identifying the task to resubscribe to
* @returns An AsyncIterable that yields TaskStatusUpdateEvent or TaskArtifactUpdateEvent payloads.
*/
async *resubscribeTask(params, options) {
try {
const client = await this._client;
yield* client.resubscribeTask(params, options);
}
catch (error) {
logger.error('Messenger: Failed to resubscribe task', { error });
throw error;
}
}
/**
* Checks if the server supports a specific capability based on the agent card.
* @param capability The capability to check (e.g., 'streaming', 'pushNotifications').
* @returns A promise resolving to true if the capability is supported.
*/
async supports(capability) {
const card = await this.getAgentCard();
if (!card.capabilities) {
return false;
}
switch (capability) {
case 'streaming':
return !!card.capabilities.streaming;
case 'pushNotifications':
return !!card.capabilities.pushNotifications;
case 'stateTransitionHistory':
return !!card.capabilities.stateTransitionHistory;
case 'extensions':
return !!card.capabilities.extensions;
default:
return false;
}
}
/**
* Adds a single header to be included in all requests.
* @param name The header name.
* @param value The header value.
*/
addHeader(name, value) {
this.headers[name] = value;
}
/**
* Removes a header.
* @param name The header name to remove.
*/
removeHeader(name) {
delete this.headers[name];
}
static async create({ baseUrl, headers, fallbackPath, factory, config }) {
const _factory = {
...ClientFactoryOptions.default,
cardResolver: new NestedAgentCardResolver({
path: '/.well-known/agent-card.json',
}),
...factory,
};
const messenger = new Messenger(baseUrl, headers, fallbackPath, _factory, config);
const card = await messenger.getAgentCard();
/**Validate the agent card to ensure the target conforms to the A2A specification */
await validateSchema(A2A.AgentCardSchema, card).catch((error) => {
logger.warn('Messenger: Invalid agent card detected', { error });
});
return messenger;
}
}
/**
* Creates a new Messenger instance.
* @param baseUrl The base URL for the A2A server.
* @param headers Optional custom headers to include in all requests.
* @param fallbackPath Optional fallback path to use if the agent card is not found at the base URL.
* @example
* const messenger = createMessenger({
* baseUrl: "http://localhost:4000/a2a",
* });
* const card = await messenger.getAgentCard();
* console.log(card);
* @example
* const messenger = createMessenger({
* baseUrl: "http://localhost:4000/a2a",
* fallbackPath: "/agent-card",
* });
* const card = await messenger.getAgentCard();
* console.log(card);
*/
export const createMessenger = Messenger.create;
export const AgentMessenger = Messenger;
/**
* @deprecated Use {@link createMessenger} instead.
*/
export const A2AClient = Messenger;