Building a Multi-Provider AI Agent in TypeScript — No SDKs, Just Fetch
I built ai-agent-starter — a lightweight TypeScript library for calling OpenAI, Anthropic, and Ollama through one unified API. No SDK dependencies. Just fetch.
Here's how it works.
The Problem
Most AI SDKs are either:
- Provider-locked (only OpenAI, or only Anthropic)
- Heavy on dependencies (
openaialone pulls 15+ packages) - Difficult to swap providers without rewriting
I wanted: one API, any provider, zero heavy dependencies.
The Design
// One interface for all providers
interface AIProvider {
chat(messages: AIMessage[], options?: ProviderOptions): Promise<AIResponse>;
stream?(messages: AIMessage[], options?: ProviderOptions): AsyncIterable<StreamChunk>;
}
Three implementations: OpenAIProvider, AnthropicProvider, OllamaProvider. All use native fetch.
Real Function Calling (Not String Matching)
The old version used regex to parse [TOOL:name(args)] from the response text. LLMs don't naturally output this format. It barely worked.
The fixed version uses the native tool/function calling API:
// OpenAI
body.tools = tools.map(t => ({
type: 'function',
function: { name: t.name, description: "t.description, parameters: t.parameters }"
}));
body.tool_choice = 'auto';
// Anthropic
body.tools = tools.map(t => ({
name: t.name,
description: "t.description,"
input_schema: { type: 'object', properties: t.parameters.properties }
}));
The provider returns toolCalls in the response, and the Agent handles the execution loop — feeding results back to the LLM for the final answer.
Streaming with SSE
All three providers support streaming. The API is simple:
for await (const chunk of agent.stream("Tell me a story")) {
process.stdout.write(chunk);
}
Under the hood, each provider's stream() method is an async generator that parses SSE events — data: {...} lines — and yields content chunks.
Memory Management
The MemoryManager keeps conversation history within a token budget:
const mem = new MemoryManager({ maxTokens: 8000 });
mem.add({ role: 'user', content: 'Hello' });
mem.tokenCount(); // ~2
// When it exceeds the limit, old messages drop automatically
// System prompt is always preserved
CLI
# Interactive chat
npx ai-agent-starter chat
# One-shot
npx ai-agent-starter run "Explain monads"
# HTTP API
npx ai-agent-starter serve --port 3000
curl -X POST localhost:3000/chat -d '{"messages":"hello"}'
Try It
npm install ai-agent-starter
import { Agent, AIClient, OpenAIProvider } from 'ai-agent-starter';
const agent = new Agent(
new AIClient(new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! })),
{ systemPrompt: 'Be helpful.' }
);
console.log(await agent.run('Hello!'));
GitHub: github.com/GrahamduesCN/ai-agent-starter
Also check out dev-cli-kit and ai-chat-saas — built in the same sprint.
Top comments (0)