Skip to content
Learn Agentic AI12 min read0 views

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

Share this article
C

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.