Yes I build a wrapper, and this age of AI building something like this maybe not an achievement , but if you are a Developer , You have every right to build anything , and nobody can stop you
Now, back to the story, building a wrapper SDK around an API is very simple; you need TWO things for that
- Request
- Response
Simple isnt it?
Now I build it using Typescript, Reason: typesafety, you can build using JavaScript, but I would recommend using zod for that
Request
A message sent to the server by the client to perform an action or get information
example :
curl --request GET \
--url http://localhost:3773/agent/skills/{skillId}
Response
A message sent by the server replying the request made by client
example:
{
"id": "pdf-processing-v1",
"name": "pdf-processing",
"description": "Extract text, fill forms, and extract tables from PDF documents.\nHandles both standard text-based PDFs and scanned documents with OCR.\n",
"tags": [
"pdf",
"documents",
"extraction",
"forms",
"tables"
],
"input_modes": [
"application/pdf"
],
"output_modes": [
"text/plain",
"application/json",
"application/pdf"
],
"version": "1.0.0",
"examples": [
"Extract text from this PDF document",
"Fill out this PDF form with the provided data",
"Extract tables from this invoice PDF"
],
"performance": {
"avg_processing_time_ms": 2000,
"max_file_size_mb": 50,
"max_pages": 500,
"concurrent_requests": 5
},
"allowed_tools": [
"Read",
"Write",
"Execute"
],
"has_documentation": true
}
By looking on request and response, you can build :
- A class with a methods
- Types and abstraction
This way, we can wrap the functionality and property, and expose only the required content to the client
But first, imagine if you are the client, what you want when you code
By looking at the following example:
const client = new SkillClient({ url: "http://localhost:3773" });
await client.agent.getSkillById("<skill-id>")
Now we have a clear picture, so let's start building. First:
Abstraction and Type
- Type
Start with a request Type, We can see it has only one type, string in request URL endpoint, we can leave it, but I would like to have a Type for everything
export interface GetSkillParams {
skillId: string;
}
And return a response, which also needs a Type
export interface SkillDetail {
id: string;
name: string;
description: string;
tags: string[];
input_modes: string[];
output_modes: string[];
version: string;
examples: string[];
capabilities_detail: Record<string, unknown>;
performance: SkillPerformance;
allowed_tools: string[];
has_documentation: boolean;
}
export interface SkillPerformance {
avg_processing_time_ms: number;
max_file_size_mb: number;
max_pages: number;
concurrent_requests: number;
}
- Abstraction
Now, let's build an abstraction for implementing in class. For abstraction, we can use an
abstract classBut in Typescript, we haveinterfacewhich can be better for a simplified version
export interface SkillsType {
getSkill(params: GetSkillParams): Promise<SkillDetail>;
}
Class and Method
Let's build a class and methods that will be exposed to the client
export class SkillsClient implements SkillsType {
private readonly baseUrl: string;
constructor(url: string) {
this.baseUrl = url;
}
async getSkillById(params: GetSkillParams): Promise<SkillDetail> {
const url = new URL(`/agent/skills/${params.skillId}`, this.baseUrl).toString();
let response: Response;
try {
response = await fetch(url, { method: "GET" });
} catch (error) {
throw new NetworkError(0, "Failed to reach the skill detail endpoint.", error);
}
if (!response.ok) {
const json = (await response.json()) as SkillErrorResponse;
throw new SkillDetailError(response.status, json.error, json.details);
}
return (await response.json()) as SkillDetail;
}
}
In my case, I have defined specific Error type , so I build an custom error, It is convenient to have a custom error directory
export interface SkillErrorResponse {
error: string;
details?: string;
}
export abstract class BaseError extends Error {
constructor(
public code: number,
message: string,
public data?: unknown,
) {
super(message);
this.name = "BaseError";
}
}
export class NetworkError extends BaseError {
constructor(code: number, message: string, data?: unknown) {
super(code, message, data);
this.name = "NetworkError";
}
}
And that's all : )
Now export everything from the main index file
export * from "./skill"
export * from "./type"
export * from "./error"
and define your export in package json file
{
"name": "@skill/core",
"version": "0.1.0",
"description": "TypeScript SDK for skill",
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"lint": "eslint src --ext .ts",
"typecheck": "tsc --noEmit"
},
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"dependencies": {},
"devDependencies": {
"typescript": "^5.4.0",
"vitest": "^1.0.0",
"@types/node": "^20.0.0"
},
"keywords": ["agent", "sdk", "typescript"]
}
now publish your package
https://dev.to/leopold/build-and-publish-your-npm-package-48mb
https://docs.npmjs.com/creating-and-publishing-scoped-public-packages/
And now you are an official Software Developer.
just kidding we need to learn more
Happy coding
Top comments (0)