Command Palette

Search for a command to run...

UnylyUnyly
Browse all

Coderun Agent

FreeMaintained

Standalone, multi-provider AI Agent framework in plain JavaScript

GitHubEmbed

About

Standalone, multi-provider AI Agent framework in plain JavaScript

README

Lightweight, multi-provider AI Agent framework in plain JavaScript. Build autonomous, tool-augmented AI agents with real-time streaming, subagent delegation, and Human-in-the-Loop (HITL) permission controls.

npm version license Documentation


📚 Documentation

Read the complete API reference, provider examples, MCP integration guide, and usage documentation on the live documentation site:

https://nbsgr.github.io/agentframework/

The documentation is published from the repository's docs folder using GitHub Pages.

Source code and issue tracking are available in the GitHub repository:

https://github.com/nbsgr/agentframework


✨ Features

  • Lightweight & Pure JavaScript: ES Modules (ESM) written in clean, robust JavaScript. Zero TypeScript compilation needed.
  • 🌐 Universal Multi-Provider Support: Built on strict openai-compatible (Ollama, Gemini, OpenCode Zen, Groq, OpenRouter, OpenAI, DeepSeek) and anthropic provider adapters.
  • 🛡️ Guardrail Pipelines: Configurable, multi-step inspection pipelines for user input (inputGuardrails), tool execution (toolGuardrails), and model output (outputGuardrails).
  • 📐 Structured Output Enforcement: Enforce guaranteed JSON output matching any Zod or JSON schema (outputSchema) with automatic LLM self-correction.
  • 💬 Real-Time Streaming: Stream reasoning/thinking tokens (evt.type === 'thinking'), response text (evt.type === 'stream'), and tool execution events in real time.
  • 🛡️ Human-in-the-Loop (HITL) Safety: Built-in permission control layer (needsApproval + permissionHandler). Pause execution for user approval (CLI prompt, HTML Modal, or React UI) before running sensitive operations.
  • 📦 Universal Tool Support: Seamlessly accepts custom tools via tool({...}), Zod schemas, JSON schemas, tools from coderun-tools, MCP tools, or subagent instances.
  • 🤖 Subagents & Parallel Delegation: Delegate tasks to subagents as tools with parallel execution, token aggregation, and event bubbling.

📦 Installation

npm install coderun-agent

# Optional filesystem and terminal tools
npm install coderun-tools

# Optional MCP client support
npm install @modelcontextprotocol/client

🚀 Quick Start

Build and run your first AI agent in 5 lines of code:

import { createAgent } from 'coderun-agent';

var agent = createAgent({
  name: 'Assistant',
  instructions: 'You are a helpful software engineering assistant.',
  provider: 'openai-compatible',
  baseurl: 'http://localhost:11434/v1',
  apikey: 'ollama',
  model: 'qwen2.5-coder:7b'
});

var result = await agent.run('Write a JavaScript function to reverse a string.');

console.log(result.content);

🛠️ Defining Tools with tool({...})

Create custom tools using standard JSON schemas or Zod schemas:

Tool arguments are checked against the declared schema before execution. Invalid arguments are returned to the model as a failed tool result and are never passed to your tool handler.

🔌 Plug-and-Play MCP Servers

Connect existing MCP servers, including filesystem, GitHub, and other stdio or Streamable HTTP servers, without changing the agent loop:

var agent = createAgent({
  provider: 'openai-compatible',
  baseurl: 'http://localhost:11434/v1',
  apikey: 'ollama',
  model: 'minimax-m3:cloud'
});

await agent.connectMcp({
  name: 'filesystem',
  transport: 'stdio',
  command: 'npx',
  args: [ '-y', '@modelcontextprotocol/server-filesystem', process.cwd() ]
});

var result = await agent.run('List the files in the project directory.');
await agent.closeMcp();

For a remote MCP server, use transport: 'streamable-http', a url, and optional headers. MCP tools are discovered automatically, converted to the agent tool format, validated, permission-checked, and executed through the existing tool loop. The MCP client package is optional and is loaded only when connectMcp() is called.

import { createAgent, tool } from 'coderun-agent';
import { z } from 'zod';

// 1. Weather Tool with Zod Schema
var getWeather = tool({
  name: 'get_weather',
  description: 'Get current weather for a city',
  parameters: z.object({
    city: z.string().describe('City name e.g. Tokyo')
  }),
  async execute({ city }) {
    return `Weather in ${city} is sunny 25°C.`;
  }
});

// 2. Currency Tool with JSON Schema
var convertCurrency = tool({
  name: 'convert_currency',
  description: 'Convert currency amount',
  parameters: {
    type: 'object',
    properties: {
      amount: { type: 'number' },
      from: { type: 'string' },
      to: { type: 'string' }
    },
    required: ['amount', 'from', 'to']
  },
  async execute({ amount, from, to }) {
    return `${amount} ${from} = ${(amount * 0.92).toFixed(2)} ${to}`;
  }
});

// 3. Initialize Agent with tools:
var agent = createAgent({
  name: 'Multi-Tool Agent',
  provider: 'openai-compatible',
  baseurl: 'https://opencode.ai/zen/v1',
  apikey: 'YOUR_OPENCODE_API_KEY',
  model: 'deepseek-v4-flash-free',
  tools: [ getWeather, convertCurrency ]
});

var result = await agent.run('What is the weather in Tokyo and convert 100 USD to EUR?');

console.log(result.content);
console.log('Executed Tools:', result.toolCalls);

🔌 Using Built-in Tools from coderun-tools

After installing the optional coderun-tools package, its tools can be passed directly to coderun-agent:

import { createAgent } from 'coderun-agent';
import { readFile, writeFile, executeCommand, listDirectory } from 'coderun-tools';

var agent = createAgent({
  name: 'Developer Agent',
  instructions: 'You inspect and modify project files.',
  provider: 'openai-compatible',
  baseurl: 'http://localhost:11434/v1',
  apikey: 'ollama',
  model: 'qwen2.5-coder:7b',
  
  // Pass coderun-tools functions directly:
  tools: [ readFile, writeFile, executeCommand, listDirectory ],
  
  workspace: process.cwd()
});

var result = await agent.run('List directory contents and read package.json');

⚡ Real-Time Streaming & Reasoning Tokens

Enable stream: true to receive real-time reasoning/thinking tokens and response text as the LLM generates them:

function handleAgentEvent(evt) {
  // 1. Live Thinking / Reasoning Tokens (DeepSeek / Qwen / Claude thinking)
  if (evt.type === 'thinking' && evt.chunk) {
    process.stdout.write(evt.chunk);
  }
  // 2. Live Content Response Tokens
  else if (evt.type === 'stream' && evt.chunk) {
    process.stdout.write(evt.chunk);
  }
  // 3. Live Tool Call Notification
  else if (evt.type === 'tool_call') {
    console.log(`\n[TOOL CALLED] ${evt.tool}`, evt.args);
  }
  // 4. Live Tool Result Notification
  else if (evt.type === 'tool_result') {
    console.log(`\n[TOOL RESULT] ${evt.tool}`, evt.result.output);
  }
}

var agent = createAgent({
  name: 'Streaming Agent',
  provider: 'openai-compatible',
  baseurl: 'https://opencode.ai/zen/v1',
  apikey: 'YOUR_API_KEY',
  model: 'deepseek-v4-flash-free',
  tools: [ getWeather ],
  stream: true
});

var result = await agent.run('What is the weather in Tokyo?', {
  onEvent: handleAgentEvent
});

🛡️ Human-in-the-Loop (HITL) Permission Controls

Protect your system from unauthorized operations (such as file deletion or terminal execution) using needsApproval and permissionHandler.

Configuring needsApproval

needsApproval can be configured in 3 flexible ways:

  1. Array of Tool Names: needsApproval: ['delete_file', 'execute_command']
  2. Global Boolean: needsApproval: true (requires approval for ALL tools)
  3. Per-Tool Definition: needsApproval: true inside tool({...})

1. Terminal / CLI permissionHandler

import readline from 'readline';

function cliPermissionHandler(toolName, args, toolId) {
  return new Promise(function(resolve) {
    var rl = readline.createInterface({ input: process.stdin, output: process.stdout });
    
    rl.question(`\n⚠️ Allow agent to execute "${toolName}" with ${JSON.stringify(args)}? (y/n): `, function(ans) {
      rl.close();
      var allowed = ans.trim().toLowerCase().startsWith('y');
      resolve(allowed); // Return true (Allow) or false (Deny)
    });
  });
}

var agent = createAgent({
  name: 'Secure CLI Agent',
  provider: 'openai-compatible',
  baseurl: 'http://localhost:11434/v1',
  apikey: 'ollama',
  model: 'qwen2.5-coder:7b',
  tools: [ deleteFile, executeCommand ],
  
  // Require approval for sensitive tool names:
  needsApproval: ['delete_file', 'execute_command'],
  permissionHandler: cliPermissionHandler
});

2. Web UI / React Modal permissionHandler

function webUiPermissionHandler(toolName, args, toolId) {
  return new Promise(function(resolve) {
    // 1. Show HTML modal on screen
    openReactModalDialog({
      toolName: toolName,
      args: args,
      onAllow: function onAllow() { resolve(true); }, // 🟢 Unfreezes agent loop with TRUE!
      onDeny: function onDeny() { resolve(false); }   // 🔴 Unfreezes agent loop with FALSE!
    });
  });
}

3. Async UI / Resumable permissionHandler (callback style)

Your handler receives a fourth permissionApi argument with approve(), deny() and resolve(true|false). The agent pauses the loop until one of them is called — perfect for UI buttons, HTTP approval endpoints, or long-running reviews:

function asyncUiPermissionHandler(toolName, args, toolId, permissionApi) {
  // 1. Render a "Pending Approval" card in your UI
  renderApprovalCard({
    toolName: toolName,
    args: args,
    onAllow: function onAllow() { permissionApi.resolve(true); }, // resumes loop
    onDeny: function onDeny() { permissionApi.deny(); }           // resumes loop (blocked)
  });
  // No promise returned needed — the loop waits for resolve()/approve()/deny().
}

var agent = createAgent({
  name: 'UI Review Agent',
  provider: 'openai-compatible',
  baseurl: 'http://localhost:11434/v1',
  apikey: 'ollama',
  model: 'qwen2.5-coder:7b',
  tools: [ deleteFile ],
  needsApproval: ['delete_file'],
  permissionHandler: asyncUiPermissionHandler
});

Both styles are supported and first-to-resolve wins:

  • Promise-return style: async (toolName, args, toolId) => true | false
  • Callback style: (toolName, args, toolId, permissionApi) → call permissionApi.resolve(true), permissionApi.approve(), or permissionApi.deny()

The loop emits permission_request / permission_response (with approved: true|false) events via onEvent, and a denial is fed back to the model as Permission denied by user so the agent stops and reports it.


🤖 Subagents as Tools (Full-Agent Delegation)

Subagents are created exactly like the main agentcreateAgent() — and then passed as tools. The LLM decides at run time whether to do the task itself, delegate it whole, or split it into sub-tasks across multiple subagents (coordinated / planned), including running several subagents in parallel.

1. Create subagents like normal agents, pass them as tools

// 1. Create specialized subagents (identical API to the main agent)
var researcher = createAgent({
  name: 'Researcher',                       // becomes delegate_to_researcher tool
  instructions: 'You research topics and summarize key findings.',
  provider: 'openai-compatible',
  baseurl: 'http://localhost:11434/v1',
  apikey: 'ollama',
  model: 'qwen2.5-coder:7b',
  tools: [ searchWeb ],                     // subagent has its own tools
  subagents: [ domainExpert ]               // subagent can have its own subagents (nesting)
});

var writer = createAgent({
  name: 'Writer',
  instructions: 'You draft and save summary memos.',
  provider: 'openai-compatible',
  baseurl: 'http://localhost:11434/v1',
  apikey: 'ollama',
  model: 'qwen2.5-coder:7b'
});

// 2. Pass them to the manager — they appear as delegate_to_<name> tools
var manager = createAgent({
  name: 'Manager',
  instructions: 'Split research and writing into sub-tasks and delegate.',
  provider: 'openai-compatible',
  baseurl: 'http://localhost:11434/v1',
  apikey: 'ollama',
  model: 'qwen2.5-coder:7b',
  parallelTools: true,                       // run delegated subagents concurrently
  subagents: [ researcher, writer ]          // 👈 same as tools: [ researcher, writer ]
});

var result = await manager.run('Research quantum computing and write a summary.');
// result.toolCalls: [ { name: 'delegate_to_researcher', args, output },
//                     { name: 'delegate_to_writer', args, output } ]

2. Subagents are full agents (recursion, HIL, guardrails)

A subagent runs its own complete agent loop with the same capabilities as the main agent:

  • Recursion: a subagent can itself have subagents / tools containing other agents (like a loop inside a loop — each with its own data and tools).
  • Own tools + tool round-trip: tool calls, args, and execution output are re-fed to the subagent's loop.
  • HIL approvals inside a subagent: tools available to the subagent can declare needsApproval: true, and the subagent's own permissionHandler (same permissionApi callback/promise flow) gates them.
  • Permission cascade: if a subagent defines no permissionHandler of its own, the parent's handler is automatically used for the subagent run. If the subagent has its own, its handler wins.
  • Guardrails & structured output: inputGuardrails / toolGuardrails / outputGuardrails / outputSchema all apply inside the subagent's own loop.
  • Usage bubbling: subagent token usage is added to the parent's result.usage and agent.getUsage().
  • Execution inheritance: when the parent runs non-streaming, delegations inherit that stream:false (no streaming-mismatch failures); a live client override on the parent run is reused by subagents that target the same endpoint (e.g. one shared mock/API key), while a subagent with its own distinct provider keeps its connection.
  • Event forwarding: subagent events surface on the parent's onEvent as { type: 'subagent_event', subagent: 'Researcher', event: { ... } }.

3. Guard the delegation itself

The delegation tool can require approval before the subagent is invoked:

var gated = createSubagentTool(researcher, { needsApproval: true });
// or in config: needsApproval: ['delegate_to_researcher']

4. Custom tool name / description

createSubagentTool(researcher, {
  name: 'do_research',
  description: 'Delegate research work. Returns findings.',
  needsApproval: false
});

Subagent delegation, nesting, parallel coordination, and in-subagent HIL flows are verified deterministically in test/test_subagent_full_agents.js.


🛡️ Guardrail Pipelines (Input, Tool & Output Safety)

Guardrails allow you to define programmable safety checkpoints at each stage of the agent loop:

  • inputGuardrails: Inspects user prompt before LLM invocation (e.g. blocking prompt injections or banned keywords).
  • toolGuardrails: Inspects tool arguments before execution (e.g. preventing path traversal outside the workspace).
  • outputGuardrails: Inspects the final assistant output before returning to the caller.
import { createAgent } from 'coderun-agent';

// 1. Input Guardrail: Block prompt injection
function checkPromptSafety(prompt, context) {
  var lower = prompt.toLowerCase();
  if (lower.indexOf('ignore all instructions') >= 0 || lower.indexOf('drop database') >= 0) {
    return { pass: false, error: 'Security tripwire: Unsafe prompt detected.' };
  }
  return { pass: true };
}

// 2. Tool Guardrail: Prevent directory escape
function checkWorkspaceBoundary(toolName, args, context) {
  if (args && args.path && typeof args.path === 'string') {
    if (args.path.indexOf('..') >= 0 || args.path.startsWith('/etc')) {
      return { pass: false, error: 'Path traversal forbidden outside workspace.' };
    }
  }
  return { pass: true };
}

// 3. Output Guardrail: Enforce response format
function checkOutputFormat(content, context) {
  if (content.indexOf('SUMMARY:') === -1) {
    return { pass: false, error: 'Response must include a "SUMMARY:" section.' };
  }
  return { pass: true };
}

var agent = createAgent({
  name: 'GuardedAgent',
  provider: 'openai-compatible',
  baseurl: 'https://opencode.ai/zen/v1',
  apikey: 'sk-your-key',
  model: 'deepseek-v4-flash-free',
  inputGuardrails: [checkPromptSafety],
  toolGuardrails: [checkWorkspaceBoundary],
  outputGuardrails: [checkOutputFormat]
});

📐 Structured Output Enforcement (outputSchema)

Pass an outputSchema (a Zod schema or standard JSON schema) to guarantee valid, typed JSON output. If the model emits invalid JSON or schema violations, the engine automatically prompts the model to self-correct within the loop:

import { createAgent } from 'coderun-agent';
import { z } from 'zod';

// Define expected structured output schema:
var LeadExtractionSchema = z.object({
  fullName: z.string().describe('Full name of contact'),
  email: z.string().describe('Email address'),
  score: z.number().describe('Lead score from 1-100')
});

var agent = createAgent({
  provider: 'openai-compatible',
  baseurl: 'https://generativelanguage.googleapis.com/v1beta/openai/',
  apikey: 'YOUR_GEMINI_API_KEY',
  model: 'gemini-flash-latest',
  outputSchema: LeadExtractionSchema
});

var result = await agent.run('Extract contact info: John Doe, reachable at [email protected], high purchase intent (95).');

// Access parsed object directly:
console.log(result.structuredOutput);
// { fullName: "John Doe", email: "[email protected]", score: 95 }

🌐 Supported Model Providers

// 1. Ollama (Local LLM)
createAgent({
  provider: 'openai-compatible',
  baseurl: 'http://localhost:11434/v1',
  apikey: 'ollama',
  model: 'qwen2.5-coder:7b'
});

// 2. Google Gemini (via OpenAI-compatible endpoint)
createAgent({
  provider: 'openai-compatible',
  baseurl: 'https://generativelanguage.googleapis.com/v1beta/openai/',
  apikey: 'YOUR_GEMINI_API_KEY',
  model: 'gemini-flash-latest'
});

// 3. OpenCode Zen / DeepSeek
createAgent({
  provider: 'openai-compatible',
  baseurl: 'https://opencode.ai/zen/v1',
  apikey: 'sk-your-opencode-key',
  model: 'deepseek-v4-flash-free'
});

// 4. OpenAI
createAgent({
  provider: 'openai-compatible',
  baseurl: 'https://api.openai.com/v1',
  apikey: 'sk-your-openai-key',
  model: 'gpt-4o'
});

// 5. Anthropic Claude
createAgent({
  provider: 'anthropic',
  apikey: 'sk-ant-your-claude-key',
  model: 'claude-3-5-sonnet-20241022'
});

🖼️ Multimodal Vision & Image Input

Pass local image file paths, HTTP URLs, or Base64 data URIs directly into agent.run():

// Local image files are automatically converted to Base64 Data URIs:
var result = await agent.run('Describe this diagram', {
  images: ['./screenshots/chart.png']
});

📜 Conversation History & Session Management

coderun-agent is stateless across separate .run() calls. It never automatically reuses a previous run. The caller owns continuation history and must pass it explicitly. The agent does not expose implicit history-management methods; this prevents accidental context leakage between tasks.

Note: while conversation history is per-run, aggregate token usage and the agent state machine are shared across runs — use agent.getUsage() (reset via agent.resetContext()) and agent.getState() / agent.onStateChange() for cross-run observability.

To pass multi-turn conversation history into a run, supply history in options:

var userSessionHistory = [
  { role: 'user', content: 'My favorite programming language is JavaScript.' },
  { role: 'assistant', content: 'Got it!' }
];

var result = await agent.run('What is my favorite programming language?', {
  history: userSessionHistory
});

The returned result.history is the transcript for that run. Pass it back explicitly when a later run should continue the same task.

Use timeoutMs or an AbortSignal in runOptions to cancel a long-running provider request or cooperative tool operation. Timeout failures return status: 'timeout'; caller cancellation returns status: 'aborted'.


📊 Result Object Reference

Every await agent.run() resolves to a structured result object:

{
  success: true,               // Boolean indicating clean turn completion
  content: "...",              // Final text answer from the agent
  structuredOutput: { ... },   // Parsed JSON object when outputSchema is provided
  thinking: "...",             // Reasoning/thinking tokens collected
  toolCalls: [                 // Clean array of executed tools
    {
      id: "call_12345",
      name: "get_weather",
      args: { city: "Tokyo" },
      output: { success: true, content: "Weather in Tokyo is sunny 25°C." }
    }
  ],
  usage: {                     // Token consumption metrics
    prompt_tokens: 140,
    completion_tokens: 45,
    total_tokens: 185
  },
  history: [...]               // Complete transcript produced during this run only
}

If the loop stops because maxIterations is reached, the result has success: false and status: 'max_iterations_reached'.


📄 License

MIT © CodeRun Agent

from github.com/nbsgr/agentframework

Install Coderun Agent in Claude Desktop, Claude Code & Cursor

Recommended · one command, every IDE
unyly install coderun-agent

Installs into Claude Desktop, Claude Code, Cursor & VS Code — handles npx, uvx and build-from-source repos for you.

First time? Get the CLI: curl -fsSL https://unyly.org/install | sh

Or configure manually

Run in your terminal:

claude mcp add coderun-agent -- npx -y coderun-agent

Step-by-step: how to install Coderun Agent

FAQ

Is Coderun Agent MCP free?

Yes, Coderun Agent MCP is free — one-click install via Unyly at no cost.

Does Coderun Agent need an API key?

No, Coderun Agent runs without API keys or environment variables.

Is Coderun Agent hosted or self-hosted?

Self-hosted: the server runs locally on your machine via the install command above.

How do I install Coderun Agent in Claude Desktop, Claude Code or Cursor?

Open Coderun Agent on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.

Related MCPs

Compare Coderun Agent with

Not sure what to pick?

Find your stack in 60 seconds

Author?

Embed badge for your README

Browse similar

All ai MCPs