Skip to content

Latest commit

 

History

33 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

corecoder-ts

A TypeScript port of CoreCoder — a minimal AI coding agent that runs in your terminal. Roughly 2,750 lines of source (plus ~1,100 lines of tests), zero runtime dependencies, and the whole thing is meant to be read: each module is a distilled version of the same idea from Claude Code, with the commentary on why it's shaped that way.

Built for people who want to understand how an agent actually works, not for people who want a feature-complete product. If you need the latter, go use Claude Code or Cline.

Branches

  • min (default) — the minimal viable version: the core behavior, kept as small as possible.
  • dev — the enhanced branch: functional enhancements on top of min, no longer bound by the minimal bar.

min is merged into dev regularly; nothing flows the other way.

Features

  • Agent loop as an async generatorchat() yields streamed events and returns the final answer, so rendering and cancellation are built into the core (AsyncGenerator<AgentEvent, string>).
  • Real tools, real filesystem — bash, read/write/edit, glob, grep, and a sub-agent tool, with Claude Code-style unique-match file editing and cwd tracking across commands.
  • Context compression — a 3-layer strategy (snip verbose tool output → LLM summary → hard collapse) driven by a static UTF-8-byte estimate (~3 bytes per token), so CJK and ASCII text are both sized sensibly without any calibration machinery.
  • Session persistence — save and resume conversations from disk.
  • No dependencies at runtime — the OpenAI SDK appears only as a type-only dev dependency.
  • Works with any OpenAI-compatible API — OpenAI, DeepSeek, Ollama, LM Studio, etc.

Install

Run from source:

git clone https://github.com/nullcache/corecoder-ts.git
cd corecoder-ts
npm install
npm run build
npm start

Requires Node.js >= 18.17.

Quick start

export OPENAI_API_KEY=sk-...
corecoder-ts

DeepSeek (or any OpenAI-compatible provider):

export OPENAI_API_KEY=sk-...
export OPENAI_BASE_URL=https://api.deepseek.com
export CORECODER_MODEL=deepseek-chat
corecoder-ts

Ollama (local, dummy API key):

export OPENAI_API_KEY=ollama
export OPENAI_BASE_URL=http://localhost:11434/v1
export CORECODER_MODEL=qwen2.5-coder
corecoder-ts

No API key handy? Watch the agent loop run end-to-end with a scripted model:

corecoder-ts --demo

CLI

corecoder-ts [options]

  -m, --model <name>   Model name (default: $CORECODER_MODEL or gpt-5.5)
      --base-url <url> API base URL (default: $OPENAI_BASE_URL)
      --api-key <key>  API key (default: $CORECODER_API_KEY, $OPENAI_API_KEY, or $DEEPSEEK_API_KEY)
  -p, --prompt <text>  One-shot prompt (non-interactive mode)
      --demo           Run the offline scripted demo (no API key needed)
  -r, --resume <id>    Resume a saved session
  -v, --version        Show version
  -h, --help           Show this help

Configuration is read from environment variables (a .env file in the working directory or any parent directory up to your home directory works too, without overriding variables already set):

Variable Default Purpose
CORECODER_API_KEY / OPENAI_API_KEY / DEEPSEEK_API_KEY API key (first one set wins)
OPENAI_BASE_URL / CORECODER_BASE_URL OpenAI API base URL
CORECODER_MODEL gpt-5.5 Model name
CORECODER_MAX_TOKENS 4096 max_tokens sent to the API
CORECODER_TEMPERATURE 0 Sampling temperature
CORECODER_MAX_CONTEXT 128000 Context budget that triggers compression
CORECODER_TIMEOUT_MS 300000 Per-request timeout for LLM calls, in ms (timeouts retry as transient errors)

REPL commands

Command What it does
/help Show all commands
/reset Clear conversation history
/model / /model <name> Show or switch the model mid-conversation
/tokens Show token usage
/compact Manually compress the conversation context
/diff List files modified this session
/save Save the session to disk
/sessions List saved sessions
quit / exit Leave the REPL

Ctrl+C cancels the current turn; Ctrl+C at the prompt exits.

Tools

The agent gets these tools, declared as JSON schemas and executed against your machine:

Tool What it does
bash Run a shell command with a timeout, output truncation, and a safety blacklist for destructive commands
read_file Read a file with line numbers, offset/limit, and a line-width cap
write_file Create or overwrite a file
edit_file Replace an exact unique string match — Claude Code's key editing primitive
glob Find files by pattern (** supported), skipping node_modules and VCS dirs
grep Regex search over file contents
agent Spawn a sub-agent with its own context for complex sub-tasks (no recursive agents)

How it works

user message -> LLM (with tools) -> tool calls? -> execute -> loop
                                -> text reply? -> return to user

The agent loop (src/agent.ts)

Agent.chat() is an async generator: it yields AgentEvents (text deltas, tool_start/tool_end) for live rendering, and returns the model's final text answer. Tool calls are executed — in parallel when there are several — and their results are fed back as tool messages until the model replies with plain text. Cancellation flows through an AbortSignal, so a ^C stops the current turn without leaving a half-answered message behind.

The LLM layer (src/llm.ts)

Speaks raw fetch + SSE against any OpenAI-compatible /chat/completions endpoint. Streamed tool-call fragments are re-stitched per index, usage is read from the final chunk, and transient errors retry with exponential backoff. ScriptedLLM plays back canned responses offline for tests and demos.

Context compression (src/context.ts)

Three layers, cheapest first:

  1. Snip verbose tool outputs in place (head + tail preserved)
  2. Summarize old turns with the LLM, keeping the recent tail verbatim
  3. Hard collapse near the hard limit — summary + last few messages only

Sizing uses a static UTF-8-byte estimate (~3 bytes per token): ASCII lands at ~3 chars/token and CJK at ~1 char/token, with the fixed system-prompt/tool-schema overhead added on top. Provider usage is post-hoc accounting only (see /tokens) and never feeds the estimate — the two datasets never meet.

Sessions (src/session.ts)

Conversations save to ~/.corecoder-ts/sessions/ as JSON. Session ids are sanitized against path traversal; resume with corecoder-ts -r <id>.

Using it as a library

import { Agent, LLM } from 'corecoder-ts'

const llm = new LLM({
  model: 'deepseek-chat',
  apiKey: process.env.DEEPSEEK_API_KEY!,
  baseUrl: 'https://api.deepseek.com',
})

const agent = new Agent({ llm })
for await (const event of agent.chat('list every TODO in this project')) {
  if (event.type === 'text') process.stdout.write(event.delta)
}

Development

npm install
npm test        # tsc build + run the test suite
npm run demo    # offline demo with a scripted model

Layout:

src/
  agent.ts      the agent loop
  llm.ts        fetch + SSE client and the scripted offline client
  context.ts    multi-layer context compression
  cli.ts        the REPL and one-shot mode
  session.ts    conversation persistence
  render.ts     streaming markdown renderer for the terminal
  tools/        bash, read/write/edit, glob, grep, sub-agent
tests/          node:test suites (no network required)
scripts/        cross-version test runner (node --test glob expansion is Node 21+)

Relationship to CoreCoder

This is a faithful port of the Python CoreCoder, with a few deliberate upgrades — most notably the async-generator event stream (the Python version uses on_token/on_tool callbacks), a streaming markdown renderer, and a UTF-8-byte token estimate that sizes CJK text sensibly. The design commentary throughout references the Claude Code mechanisms being distilled and the Python original being ported.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages