agent-sdk-scratch
v1.0.2
Published
A lightweight OpenAI agent harness with tools, builder API, and a ReAct-style JSON pipeline.
Readme
agent-sdk-scratch
Build tool-using OpenAI agents in Node.js with a small builder API.
The agent follows a JSON step loop (INITAL → THINK → TOOL_REQUEST → ANALYSE → OUTPUT). When the model requests a tool, your executor runs and the result is fed back until the agent returns OUTPUT.
You must bring your own OpenAI API key. This package does not include or proxy model access.
Requirements
- Node.js 18+
- An OpenAI API key
- ESM project (
"type": "module"in yourpackage.json), or use.mjsfiles
1. Install
npm i agent-sdk-scratch2. Set your API key
export OPENAI_API_KEY=sk-...Or pass it in code when you call .build() (shown below).
3. Create a tool
Every tool needs a unique name, a human-readable description (so the model knows when to use it), optional doc, and an executor that receives a string and returns Promise<string>.
import type { ITool } from "agent-sdk-scratch";
const weatherTool: ITool = {
name: "fetchWeatherInfo",
description: "Fetches realtime weather for a city name",
doc: "fetchWeatherInfo(cityName: string): string",
async executor(cityName) {
const res = await fetch(
`https://wttr.in/${encodeURIComponent(cityName)}?format=%C+%t`,
);
const text = await res.text();
return JSON.stringify({ cityName, weatherInfo: text });
},
};Tips
- Keep
namestable — the model calls tools by this exact name. - Put argument format in
doc/description(e.g."pass only the city name"). - Always return a string from
executor(useJSON.stringifyfor objects).
4. Build and run an agent
import { Agent, type ITool } from "agent-sdk-scratch";
const weatherTool: ITool = {
name: "fetchWeatherInfo",
description: "Fetches realtime weather for a city name",
doc: "fetchWeatherInfo(cityName: string): string",
async executor(cityName) {
const res = await fetch(
`https://wttr.in/${encodeURIComponent(cityName)}?format=%C+%t`,
);
return JSON.stringify({ cityName, weatherInfo: await res.text() });
},
};
async function main() {
const agent = Agent.builder()
.setInstructions(
"You are a weather assistant. Use fetchWeatherInfo when the user asks about weather.",
)
.tool(weatherTool)
// chain more: .tool(anotherTool)
.build({
// optional if OPENAI_API_KEY is set
apiKey: process.env.OPENAI_API_KEY,
// optional, default: "gpt-4o"
model: "gpt-4o",
});
// Optional: watch every assistant / tool message
agent.attachInterceptor((message) => {
console.log(`[${message.role}]`, message.content);
});
const history = await agent.run("What is the weather in Goa?");
// Last message is usually the final OUTPUT step
console.log(history?.at(-1));
}
main().catch(console.error);Save as app.mjs / app.ts and run with Node or tsx.
5. Minimal project layout (consumer)
my-app/
package.json # "type": "module"
app.ts # your agent codepackage.json:
{
"type": "module",
"dependencies": {
"agent-sdk-scratch": "^1.0.0"
}
}TypeScript consumers can import types directly:
import {
Agent,
AgentBuilder,
type AgentOptions,
type IMessage,
type ITool,
type Interceptor,
} from "agent-sdk-scratch";How the agent loop works
- You call
agent.run(userQuery). - The model replies with one JSON object per turn, for example:
or{ "step": "THINK", "text": "I should call the weather tool" }{ "step": "TOOL_REQUEST", "functionName": "fetchWeatherInfo", "input": "Goa" } - On
TOOL_REQUEST, the matching toolexecutorruns withinput. - The tool result is appended to history as a
developermessage. - Loop continues until
stepisOUTPUT(or max 30 turns).
run() returns the full message history (IMessage[]), or undefined if it hits the loop limit without OUTPUT.
API reference
Agent.builder()
Starts a fluent builder.
| Method | Description |
| --- | --- |
| .setInstructions(text) | System role / behavior for this agent |
| .tool(tool) | Register an ITool (call multiple times) |
| .build(options?) | Create the Agent |
AgentOptions
| Field | Default | Description |
| --- | --- | --- |
| apiKey | process.env.OPENAI_API_KEY | OpenAI secret key |
| model | "gpt-4o" | Chat model id |
Throws if no API key is available.
Agent instance
| Method | Description |
| --- | --- |
| run(query) | Run one user task; returns message history |
| attachInterceptor(fn) | Called on each assistant / tool message |
| printSystemPrompt() | Debug: print the full system prompt |
ITool
interface ITool {
name: string;
description: string;
doc?: string;
executor: (input: string) => Promise<string>;
}IMessage
interface IMessage {
role: "user" | "assistant" | "developer";
content: string;
}Multiple tools / multiple agents
const agent = Agent.builder()
.setInstructions("You are a coding agent. Prefer execCli for shell tasks.")
.tool(cliTool)
.tool(readFileTool)
.build();
const weatherAgent = Agent.builder()
.setInstructions("You only answer weather questions.")
.tool(weatherTool)
.build();Each agent has its own tools and history. Create a new agent (or new run) per independent task if you need a clean conversation.
Common issues
| Problem | Fix |
| --- | --- |
| OpenAI API key is required | Set OPENAI_API_KEY or pass apiKey in .build() |
| ERR_REQUIRE_ESM / import errors | Use "type": "module" or .mjs |
| Model never calls your tool | Improve description / doc; mention the tool name in instructions |
| JSON.parse errors in the loop | Model returned non-JSON; tighten instructions or retry the query |
| Tool not found errors in logs | functionName from the model must match ITool.name exactly |
Develop this package
npm install
npm run typecheck
npm run build- Library source:
src/ - Published entry:
dist/ - Examples are not published; they stay in the repo for local testing
