Building a TypeScript SDK for Your AI Agent Platform: Types, Client, and Documentation
A practical guide to building a TypeScript SDK for an AI agent platform, covering package setup with tsup, strong type definitions, a fetch-based HTTP client, and JSDoc-powered inline documentation.
Project Setup and Build Tooling
A TypeScript SDK must ship both ESM and CommonJS so that every consumer — whether they use Next.js, Node.js with require, or Vite — can import it without issues. Use tsup for builds:
// tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
minify: false,
});
Your package.json exports should point to both builds:
{
"name": "@myagent/sdk",
"version": "0.1.0",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
}
}
}
This configuration ensures TypeScript users get full type inference, CommonJS users get a working require, and ESM users get tree-shakeable imports.
Defining Strong Types
The type definitions are the backbone of a TypeScript SDK. They serve triple duty: compile-time safety, IDE autocompletion, and living documentation.
// src/types.ts
export interface Agent {
id: string;
name: string;
model: string;
instructions: string;
tools: ToolRef[];
createdAt: string;
}
export interface ToolRef {
id: string;
name: string;
type: 'function' | 'retrieval' | 'code_interpreter';
}
export interface CreateAgentParams {
name: string;
model?: string;
instructions?: string;
toolIds?: string[];
}
export interface RunResult {
id: string;
status: 'queued' | 'in_progress' | 'completed' | 'failed' | 'cancelled';
output: string | null;
usage: TokenUsage;
toolCalls: ToolCallResult[];
createdAt: string;
completedAt: string | null;
}
export interface TokenUsage {
promptTokens: number;
completionTokens: number;
totalTokens: number;
}
export interface ToolCallResult {
id: string;
toolName: string;
arguments: Record<string, unknown>;
result: string;
}
/** Pagination wrapper for list endpoints. */
export interface PaginatedList<T> {
data: T[];
total: number;
hasMore: boolean;
}
Use union types for status fields rather than plain string. This catches typos at compile time and enables exhaustive switch checks.
The Fetch-Based HTTP Client
Build the client on the native fetch API. This avoids adding dependencies like axios and works in Node 18+, Deno, Bun, and the browser:
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
// src/client.ts
import type { Agent, CreateAgentParams, RunResult, PaginatedList } from './types';
import { AgentAPIError, AuthenticationError } from './errors';
export interface ClientOptions {
apiKey: string;
baseUrl?: string;
timeout?: number;
}
export class AgentClient {
private readonly apiKey: string;
private readonly baseUrl: string;
private readonly timeout: number;
/** Resource namespaces */
public readonly agents: AgentsResource;
public readonly runs: RunsResource;
constructor(options: ClientOptions) {
this.apiKey = options.apiKey;
this.baseUrl = options.baseUrl ?? 'https://api.myagent.ai/v1';
this.timeout = options.timeout ?? 30_000;
this.agents = new AgentsResource(this);
this.runs = new RunsResource(this);
}
/** @internal */
async request<T>(method: string, path: string, body?: unknown): Promise<T> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(`${this.baseUrl}${path}`, {
method,
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
'User-Agent': '@myagent/sdk 0.1.0',
},
body: body ? JSON.stringify(body) : undefined,
signal: controller.signal,
});
if (response.status === 401) {
throw new AuthenticationError('Invalid API key');
}
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new AgentAPIError(response.status, error.message ?? 'Unknown error');
}
return await response.json() as T;
} finally {
clearTimeout(timer);
}
}
}
Resource Classes with JSDoc
Each resource class groups related endpoints and provides detailed JSDoc that appears in IDE tooltips:
// src/resources/agents.ts
class AgentsResource {
constructor(private client: AgentClient) {}
/**
* Create a new AI agent.
*
* @param params - The agent configuration.
* @returns The created agent with a unique ID.
* @throws {AuthenticationError} If the API key is invalid.
* @throws {AgentAPIError} If the server rejects the request.
*
* @example
* const agent = await client.agents.create({
* name: 'Support Bot',
* model: 'gpt-4o',
* instructions: 'Answer customer questions.',
* });
*/
async create(params: CreateAgentParams): Promise<Agent> {
return this.client.request<Agent>('POST', '/agents', params);
}
/** Retrieve an agent by ID. */
async get(agentId: string): Promise<Agent> {
return this.client.request<Agent>('GET', `/agents/${agentId}`);
}
/** List agents with pagination. */
async list(limit = 20, offset = 0): Promise<PaginatedList<Agent>> {
return this.client.request<PaginatedList<Agent>>(
'GET',
`/agents?limit=${limit}&offset=${offset}`
);
}
/** Delete an agent by ID. */
async delete(agentId: string): Promise<void> {
await this.client.request('DELETE', `/agents/${agentId}`);
}
}
The JSDoc @example blocks are critical. They show up directly in VS Code hover tooltips, so developers see working code without ever opening a documentation site.
Error Types
A clean error hierarchy in TypeScript:
// src/errors.ts
export class MyAgentError extends Error {
constructor(message: string) {
super(message);
this.name = 'MyAgentError';
}
}
export class AgentAPIError extends MyAgentError {
constructor(
public readonly statusCode: number,
message: string,
) {
super(`[${statusCode}] ${message}`);
this.name = 'AgentAPIError';
}
}
export class AuthenticationError extends MyAgentError {
constructor(message: string) {
super(message);
this.name = 'AuthenticationError';
}
}
FAQ
Should I use fetch or axios for the HTTP layer?
Use native fetch. As of Node 18, fetch is built in and requires zero dependencies. Axios adds 30KB+ to your bundle and introduces a third-party dependency that needs its own security updates. The only exception is if you need automatic cookie handling or request interceptors — but for API SDKs, middleware patterns handle those concerns better.
How do I support both Node.js and browser environments?
Build on fetch and avoid Node-specific APIs like fs or process in your core client. If you need Node-specific features such as file uploads from disk, put them in a separate @myagent/sdk-node package that extends the base client. This keeps the core bundle browser-compatible.
How important are JSDoc examples in an SDK?
Extremely important. IDE-integrated documentation is where developers spend most of their time. A JSDoc @example block that shows a complete, runnable snippet is worth more than an entire documentation page because it appears exactly at the moment of need — when the developer is typing code.
#TypeScriptSDK #APIClient #DeveloperTools #AgenticAI #Npm #TypeScript #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.