@mondaydotcomorg/atp-client
Version:
Client SDK for Agent Tool Protocol
203 lines (176 loc) • 4.83 kB
text/typescript
import type { ClientHooks } from './types.js';
import type { ClientToolDefinition } from '@mondaydotcomorg/atp-protocol';
export interface ISession {
init(
clientInfo?: { name?: string; version?: string; [key: string]: unknown },
tools?: ClientToolDefinition[],
services?: { hasLLM: boolean; hasApproval: boolean; hasEmbedding: boolean; hasTools: boolean }
): Promise<{
clientId: string;
token: string;
expiresAt: number;
tokenRotateAt: number;
}>;
getClientId(): string;
ensureInitialized(): Promise<void>;
getHeaders(): Record<string, string>;
getBaseUrl(): string;
updateToken(response: Response): void;
prepareHeaders(method: string, url: string, body?: unknown): Promise<Record<string, string>>;
}
export class ClientSession implements ISession {
private baseUrl: string;
private customHeaders: Record<string, string>;
private clientId?: string;
private clientToken?: string;
private initPromise?: Promise<void>;
private hooks?: ClientHooks;
constructor(baseUrl: string, headers?: Record<string, string>, hooks?: ClientHooks) {
this.baseUrl = baseUrl;
this.customHeaders = headers || {};
this.hooks = hooks;
}
/**
* Initializes the client session with the server.
* This MUST be called before any other operations.
* The server generates and returns a unique client ID and token.
* @param clientInfo - Optional client information
* @param tools - Optional client tool definitions to register with the server
* @param services - Optional client service capabilities (LLM, approval, embedding)
*/
async init(
clientInfo?: { name?: string; version?: string; [key: string]: unknown },
tools?: ClientToolDefinition[],
services?: { hasLLM: boolean; hasApproval: boolean; hasEmbedding: boolean; hasTools: boolean }
): Promise<{
clientId: string;
token: string;
expiresAt: number;
tokenRotateAt: number;
}> {
if (this.initPromise) {
await this.initPromise;
return {
clientId: this.clientId!,
token: this.clientToken!,
expiresAt: 0,
tokenRotateAt: 0,
};
}
this.initPromise = (async () => {
const url = `${this.baseUrl}/api/init`;
const body = JSON.stringify({
clientInfo,
tools: tools || [],
services,
});
const headers = await this.prepareHeaders('POST', url, body);
const response = await fetch(url, {
method: 'POST',
headers,
body,
});
if (!response.ok) {
throw new Error(`Client initialization failed: ${response.status} ${response.statusText}`);
}
const data = (await response.json()) as {
clientId: string;
token: string;
expiresAt: number;
tokenRotateAt: number;
};
this.clientId = data.clientId;
this.clientToken = data.token;
})();
await this.initPromise;
return {
clientId: this.clientId!,
token: this.clientToken!,
expiresAt: 0,
tokenRotateAt: 0,
};
}
/**
* Gets the unique client ID.
*/
getClientId(): string {
if (!this.clientId) {
throw new Error('Client not initialized. Call init() first.');
}
return this.clientId;
}
/**
* Ensures the client is initialized before making requests.
*/
async ensureInitialized(): Promise<void> {
if (!this.clientId) {
throw new Error('Client not initialized. Call init() first.');
}
}
/**
* Creates HTTP headers for requests.
*/
getHeaders(): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...this.customHeaders,
};
if (this.clientId) {
headers['X-Client-ID'] = this.clientId;
}
if (this.clientToken) {
headers['Authorization'] = `Bearer ${this.clientToken}`;
}
return headers;
}
getBaseUrl(): string {
return this.baseUrl;
}
/**
* Updates the client token from response headers (token refresh).
*/
updateToken(response: Response): void {
const newToken = response.headers.get('X-ATP-Token');
if (newToken) {
this.clientToken = newToken;
}
}
/**
* Prepares headers for a request, calling preRequest hook if configured
*/
async prepareHeaders(
method: string,
url: string,
body?: unknown
): Promise<Record<string, string>> {
let headers: Record<string, string> = {
'Content-Type': 'application/json',
...this.customHeaders,
};
if (this.clientId) {
headers['X-Client-ID'] = this.clientId;
}
if (this.clientToken) {
headers['Authorization'] = `Bearer ${this.clientToken}`;
}
if (this.hooks?.preRequest) {
try {
const result = await this.hooks.preRequest({
url,
method,
currentHeaders: headers,
body,
});
if (result.abort) {
throw new Error(result.abortReason || 'Request aborted by preRequest hook');
}
if (result.headers) {
headers = result.headers;
}
} catch (error) {
throw error;
}
}
return headers;
}
}