A model-agnostic TypeScript-based coding agent that works with multiple LLM providers (OpenAI, Anthropic) through a unified abstraction layer. The agent receives natural language prompts, executes bash commands using function calling, and provides results interactively.
src/
├── agent.ts # Main model-agnostic agent loop & CLI
├── models/ # LLM provider implementations
│ ├── types.ts # Unified type definitions (ModelProvider interface)
│ ├── index.ts # Provider factory function
│ ├── openai.ts # OpenAI SDK implementation
│ └── anthropic.ts # Anthropic SDK implementation
├── tools/ # Tool implementations
│ ├── index.ts # Tool exports
│ └── bash.ts # Shell command execution with pipe/redirection support
└── utils/
└── logger.ts # Enhanced logging system (file/console, log levels)
All model providers implement a unified ModelProvider interface:
interface ModelProvider {
initialize(): Promise<void>;
chat(
messages: Message[],
systemPrompt: string,
tools: Tool[]
): Promise<ModelResponse>;
}- AnthropicProvider (
models/anthropic.ts): Uses@anthropic-ai/sdk - OpenAIProvider (
models/openai.ts): UsesopenaiSDK
Both providers translate between unified types and their respective SDK formats.
- Bash Tool (
tools/bash.ts): Executes shell commands with support for:- Pipelines (
|) - Command chaining (
&&) - File redirects (
>,>>) - Heredoc syntax (
<<)
- Pipelines (
Enhanced logging with configurable levels and file output:
enum LogLevel { DEBUG, INFO, WARN, ERROR }
class Logger {
logApiRequest(provider: string, endpoint: string, payload: any): void;
logApiResponse(provider: string, response: any, duration: number): void;
logToolExecution(provider?: string, toolName: string, data?: any): void;
logAgentInteraction(step: string, data: any, provider?: string): void;
error(component: string, error: any): void;
}user input → append to history → model.chat()
↓
if tool_use: execute bash command → capture output
↓
append result to history → loop continues
↓
else (text response): display and break loop
npm run startnpm run start:anthropicMODEL_PROVIDER=openai npm run start
MODEL_PROVIDER=anthropic npm run startnpm run dev # Watch TypeScript compilation
npm run build # Build onceEnvironment variables in .env:
# ============================================================================
# Logging Configuration
# ============================================================================
LOG_LEVEL=DEBUG # DEBUG, INFO, WARN, ERROR
LOG_FILE=false # Enable file logging
LOG_DIR=./logs # Log directory
PROVIDER_LOGGING=true # Separate files per provider
# ============================================================================
# Model Provider Selection (default: openai)
# ============================================================================
MODEL_PROVIDER=openai
# ============================================================================
# OpenAI Configuration
# ============================================================================
OPENAI_API_KEY=your_key
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4
# ============================================================================
# Anthropic Configuration
# ============================================================================
ANTHROPIC_API_KEY=your_key
ANTHROPIC_BASE_URL=https://api.anthropic.com
ANTHROPIC_MODEL=claude-3-sonnet-20240229- Console: Color-coded output with ANSI escape codes
- File: JSON Lines format (one JSON object per line)
- Files:
./logs/agent.logor./logs/agent_{provider}.log
- Create
src/models/yourprovider.tsimplementingModelProviderinterface - Register in
src/models/index.tsgetModelProviderfunction - Add environment variables for configuration
Example template:
import type { ModelProvider, Message, Tool, ModelResponse } from "./types.js";
export class YourProvider implements ModelProvider {
async initialize(): Promise<void> { }
async chat(
messages: Message[],
systemPrompt: string,
tools: Tool[]
): Promise<ModelResponse> {
// Translate messages to your SDK format
// Call your model
// Translate response back to unified format
return {
content: "...",
toolCalls: [],
stopReason: "end_turn",
};
}
}✅ Provider Independence: Agent logic is completely independent of SDK choice ✅ Easy Testing: Mock providers for unit tests ✅ Extensible: Add new providers without changing agent code ✅ Type-Safe: Full TypeScript support with unified types ✅ Debuggable: Comprehensive logging with configurable levels and file output