npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

context-chef

v2.0.1

Published

Context compiler for TypeScript/JavaScript AI agents. Automatically compiles agent state into optimized LLM payloads with history compression, tool pruning, multi-provider support, and more.

Readme

ContextChef

npm version npm downloads GitHub stars License TypeScript CI

TypeScript/JavaScript AI Agent 的上下文编译器。

ContextChef 解决 AI Agent 开发中最常见的上下文工程问题:对话太长模型会忘事、工具太多模型会幻觉、切换模型要重写 prompt、长程任务状态丢失。它不接管你的控制流,只负责在每次 LLM 调用前把你的状态编译成最优的 payload。

English

博客系列

  1. 为什么要"编译上下文"
  2. Janitor——把触发逻辑和压缩策略彻底分离
  3. Pruner——把工具注册和路由彻底分开
  4. Offloader/VFS——不破坏信息,只搬移信息
  5. Core Memory——读取零成本,写入结构化
  6. Snapshot & Restore——捕获决定下次编译的一切
  7. Provider 适配层——让差异止于编译层
  8. 编译管道里的五个扩展点

Features

  • 对话太长? — 自动压缩历史消息,保留近期记忆,老对话交给小模型摘要,不丢关键信息
  • 工具太多? — 按任务动态裁剪工具列表,或用双层架构(稳定分组 + 按需加载)彻底消除工具幻觉
  • 换模型要重写? — 同一套 prompt 编译到 OpenAI / Anthropic / Gemini,prefill、cache、tool call 格式自动适配
  • 长程任务跑偏? — Zod schema 强类型状态注入,每次调用前强制对齐当前任务焦点
  • 终端输出太大? — 自动截断大文本并存储到 VFS,保留错误信息 + URI 指针供模型按需读取
  • 跨会话记不住? — Memory 让模型通过 tool call 主动持久化关键信息(项目规范、用户偏好),下次会话自动注入
  • 想回滚怎么办? — Snapshot & Restore 一键捕获和回滚全部上下文状态,支持分支探索
  • 需要外部上下文?onBeforeCompile 钩子让你在编译前注入 RAG 检索结果、AST 片段等

安装

npm install context-chef zod

快速开始

import { ContextChef } from "context-chef";
import { z } from "zod";

const TaskSchema = z.object({
  activeFile: z.string(),
  todo: z.array(z.string()),
});

const chef = new ContextChef({
  janitor: {
    contextWindow: 200000,
    compressionModel: async (msgs) => callGpt4oMini(msgs),
  },
});

const payload = await chef
  .setSystemPrompt([
    {
      role: "system",
      content: "You are an expert coder.",
      _cache_breakpoint: true,
    },
  ])
  .setHistory(conversationHistory)
  .setDynamicState(TaskSchema, {
    activeFile: "auth.ts",
    todo: ["Fix login bug"],
  })
  .withGuardrails({
    enforceXML: { outputTag: "response" },
    prefill: "<thinking>\n1.",
  })
  .compile({ target: "anthropic" });

const response = await anthropic.messages.create(payload);

API 参考

new ContextChef(config?)

const chef = new ContextChef({
  vfs?: { threshold?: number, storageDir?: string },
  janitor?: JanitorConfig,
  pruner?: { strategy?: 'union' | 'intersection' },
  memoryStore?: MemoryStore,
  transformContext?: (messages: Message[]) => Message[] | Promise<Message[]>,
  onBeforeCompile?: (context: BeforeCompileContext) => string | null | Promise<string | null>,
});

上下文构建

chef.setSystemPrompt(messages): this

设置静态系统提示词层。作为缓存前缀,应尽量少变。

chef.setSystemPrompt([
  {
    role: "system",
    content: "You are an expert coder.",
    _cache_breakpoint: true,
  },
]);

_cache_breakpoint: true 会让 Anthropic 适配器注入 cache_control: { type: 'ephemeral' }

chef.setHistory(messages): this

设置对话历史。Janitor 在 compile() 时自动压缩。

chef.setDynamicState(schema, data, options?): this

将 Zod 校验后的状态以 XML 注入上下文。

const TaskSchema = z.object({
  activeFile: z.string(),
  todo: z.array(z.string()),
});

chef.setDynamicState(TaskSchema, { activeFile: "auth.ts", todo: ["Fix bug"] });
// placement 默认为 'last_user'(注入到最后一条 user 消息中)
// 使用 { placement: 'system' } 作为独立的 system 消息

chef.withGuardrails(options): this

应用输出格式护栏和可选的 prefill。

chef.withGuardrails({
  enforceXML: { outputTag: "final_code" }, // 将输出规则包裹在 EPHEMERAL_MESSAGE 中
  prefill: "<thinking>\n1.", // 尾部 assistant 消息(OpenAI/Gemini 自动降级)
});

chef.compile(options?): Promise<TargetPayload>

将所有内容编译为 provider 就绪的 payload。触发 Janitor 压缩。注册的工具自动包含。

const payload = await chef.compile({ target: "openai" }); // OpenAIPayload
const payload = await chef.compile({ target: "anthropic" }); // AnthropicPayload
const payload = await chef.compile({ target: "gemini" }); // GeminiPayload

历史压缩 (Janitor)

Janitor 提供两种压缩路径,根据你的场景选择:

路径 1:Tokenizer(精确控制)

传入自定义的 token 计算函数,Janitor 会精确计算每条消息的 token 数。保留 contextWindow × preserveRatio 范围内的近期消息,其余进行压缩。

const chef = new ContextChef({
  janitor: {
    contextWindow: 200000,
    tokenizer: (msgs) =>
      msgs.reduce((sum, m) => sum + encode(m.content).length, 0),
    preserveRatio: 0.8, // 保留 80% 的 contextWindow 给近期消息(默认值)
    compressionModel: async (msgs) => callGpt4oMini(msgs),
    onCompress: async (summary, count) => {
      await db.saveCompression(sessionId, summary, count);
    },
  },
});

路径 2:reportTokenUsage(简单,无需 tokenizer)

大多数 LLM API 的响应中已经包含 token 用量。直接传入该值,当超过 contextWindow 时,Janitor 压缩除最后 N 条消息外的所有内容。

const chef = new ContextChef({
  janitor: {
    contextWindow: 200000,
    preserveRecentMessages: 1,       // 压缩时保留最后 1 条消息(默认值)
    compressionModel: async (msgs) => callGpt4oMini(msgs),
  },
});

// 每次 LLM 调用后:
const response = await openai.chat.completions.create({ ... });
chef.reportTokenUsage(response.usage.prompt_tokens);

注意: 如果没有提供 compressionModel,旧消息将被直接丢弃而不生成摘要。如果同时没有 tokenizercompressionModel,构造时会打印控制台警告。

JanitorConfig

| 选项 | 类型 | 默认值 | 说明 | | ------------------------ | ------------------------------------------- | ------ | -------------------------------------------------------------------- | | contextWindow | number | 必填 | 模型的上下文窗口大小(token 数)。token 用量超过此值时触发压缩。 | | tokenizer | (msgs: Message[]) => number | — | 启用 tokenizer 路径,精确计算每条消息的 token 数。 | | preserveRatio | number | 0.8 | [Tokenizer 路径] contextWindow 中保留给近期消息的比例。 | | preserveRecentMessages | number | 1 | [reportTokenUsage 路径] 压缩时保留的近期消息数量。 | | compressionModel | (msgs: Message[]) => Promise<string> | — | 异步钩子,调用低成本 LLM 对旧消息进行摘要。 | | onCompress | (summary, count) => void | — | 压缩完成后触发,传入摘要消息和被截断的消息数量。 | | onBudgetExceeded | (history, tokenInfo) => Message[] \| null | — | 压缩前触发。返回修改后的历史来干预,或返回 null 让默认压缩继续执行。 |

chef.reportTokenUsage(tokenCount): this

传入 API 返回的 token 用量。下次 compile() 时,如果该值超过 contextWindow,则触发压缩。在 tokenizer 路径中,取本地计算值和传入值中的较大值。

const response = await openai.chat.completions.create({ ... });
chef.reportTokenUsage(response.usage.prompt_tokens);

onBudgetExceeded 钩子

当 token 预算超标时,在自动压缩之前触发。返回修改后的 Message[] 替换历史(例如将工具结果卸载到 VFS),或返回 null 让默认压缩继续执行。

const chef = new ContextChef({
  janitor: {
    contextWindow: 200000,
    tokenizer: (msgs) => countTokens(msgs),
    onBudgetExceeded: (history, { currentTokens, limit }) => {
      // 示例:压缩前将大型工具结果卸载到 VFS
      return history.map((msg) =>
        msg.role === "tool" && msg.content.length > 5000
          ? { ...msg, content: pointer.offload(msg.content).content }
          : msg,
      );
    },
  },
});

chef.clearHistory(): this

切换话题或完成子任务时显式清空历史并重置 Janitor 状态。


大文本卸载 (Offloader / VFS)

// 超过阈值时截断并卸载,默认保留最后 20 行
const safeLog = chef.offload(rawTerminalOutput);
history.push({ role: "tool", content: safeLog, tool_call_id: "call_123" });
// safeLog: 内容较小时原样返回,否则截断并附带 context://vfs/ URI

// 自定义保留的尾部行数(0 = 不保留尾部,适合静态文档)
const safeDoc = chef.offload(largeFileContent, { tailLines: 0 });

// 单次调用覆盖阈值
const safeOutput = chef.offload(content, { threshold: 2000, tailLines: 50 });

注册一个工具让 LLM 按需读取完整内容:

// 在你的工具处理函数中:
import { Offloader } from "context-chef";
const offloader = new Offloader({ storageDir: ".context_vfs" });
const fullContent = offloader.resolve(uri);

工具管理 (Pruner)

扁平模式

chef.registerTools([
  { name: "read_file", description: "Read a file", tags: ["file", "read"] },
  { name: "run_bash", description: "Run a command", tags: ["shell"] },
  { name: "get_time", description: "Get timestamp" /* 无 tags = 始终保留 */ },
]);

const { tools, removed } = chef.getPruner().pruneByTask("Read the auth.ts file");
// tools: [read_file, get_time]

也支持 allowOnly(names)pruneByTaskAndAllowlist(task, names)

Namespace + Lazy Loading(双层架构)

Layer 1 — Namespace:核心工具分组为稳定的工具定义。工具列表在多轮对话中永不变化。

Layer 2 — Lazy Loading:长尾工具注册为轻量 XML 目录。LLM 通过 load_toolkit 按需加载完整 schema。

// Layer 1: 稳定的 Namespace 工具
chef.registerNamespaces([
  {
    name: "file_ops",
    description: "File system operations",
    tools: [
      {
        name: "read_file",
        description: "Read a file",
        parameters: { path: { type: "string" } },
      },
      {
        name: "write_file",
        description: "Write to a file",
        parameters: { path: { type: "string" }, content: { type: "string" } },
      },
    ],
  },
  {
    name: "terminal",
    description: "Shell command execution",
    tools: [
      {
        name: "run_bash",
        description: "Execute a command",
        parameters: { command: { type: "string" } },
      },
    ],
  },
]);

// Layer 2: 按需加载的工具包
chef.registerToolkits([
  {
    name: "Weather",
    description: "Weather forecast APIs",
    tools: [
      /* ... */
    ],
  },
  {
    name: "Database",
    description: "SQL query and schema inspection",
    tools: [
      /* ... */
    ],
  },
]);

// 编译 — tools: [file_ops, terminal, load_toolkit](始终稳定)
const { tools, directoryXml } = chef.getPruner().compile();
// directoryXml: 注入系统提示词,让 LLM 知道可用的工具包

Agent Loop 集成:

for (const toolCall of response.tool_calls) {
  if (chef.getPruner().isNamespaceCall(toolCall)) {
    // 路由 Namespace 调用到真实工具
    const { toolName, args } = chef.getPruner().resolveNamespace(toolCall);
    const result = await executeTool(toolName, args);
  } else if (chef.getPruner().isToolkitLoader(toolCall)) {
    // LLM 请求加载工具包 — 展开并重新调用
    const parsed = JSON.parse(toolCall.function.arguments);
    const newTools = chef.getPruner().extractToolkit(parsed.toolkit_name);
    // 合并 newTools 到下一次 LLM 请求
  }
}

Memory

跨会话持久化的键值记忆。记忆通过 tool call(create_memory / modify_memory)修改,compile() 时自动注入到 payload 中。

import { InMemoryStore, VFSMemoryStore } from "context-chef";

const chef = new ContextChef({
  memory: {
    store: new InMemoryStore(), // 临时存储(测试)
    // store: new VFSMemoryStore(dir),   // 持久化存储(生产)
  },
});

// 在 agent loop 中拦截 memory tool call:
for (const toolCall of response.tool_calls) {
  if (toolCall.function.name === "create_memory") {
    const { key, value, description } = JSON.parse(toolCall.function.arguments);
    await chef.getMemory().createMemory(key, value, description);
  } else if (toolCall.function.name === "modify_memory") {
    const { action, key, value, description } = JSON.parse(toolCall.function.arguments);
    if (action === "update") {
      await chef.getMemory().updateMemory(key, value, description);
    } else {
      await chef.getMemory().deleteMemory(key);
    }
  }
}

// 直接读写(开发者使用,跳过验证钩子)
await chef.getMemory().set("persona", "You are a senior engineer", {
  description: "Agent 的角色和人设",
});
const value = await chef.getMemory().get("persona");

// compile() 时:
// - Memory tools(create_memory、modify_memory)自动注入到 payload.tools
// - 已有记忆作为 <memory> XML 注入到 systemPrompt 和 history 之间

Snapshot & Restore

捕获和回滚全部上下文状态,用于分支探索或错误恢复。

const snap = chef.snapshot("before risky tool call");

// ... agent 执行工具,出了问题 ...

chef.restore(snap); // 回滚所有状态:历史、动态状态、janitor 状态、记忆

onBeforeCompile 钩子

在编译前注入外部上下文(RAG、AST 片段、MCP 查询),无需修改消息数组。

const chef = new ContextChef({
  onBeforeCompile: async (ctx) => {
    const snippets = await vectorDB.search(ctx.dynamicStateXml);
    return snippets.map((s) => s.content).join("\n");
    // 作为 <implicit_context>...</implicit_context> 注入到 dynamic state 同一位置
    // 返回 null 跳过注入
  },
});

Target Adapters

| 特性 | OpenAI | Anthropic | Gemini | | ------------------------- | ---------------------- | -------------------------------------- | ------------------------------------ | | 格式 | Chat Completions | Messages API | generateContent | | 缓存断点 | 忽略 | cache_control: { type: 'ephemeral' } | 忽略(使用独立的 CachedContent API) | | Prefill(尾部 assistant) | 降级为 [System Note] | 原生支持 | 降级为 [System Note] | | thinking 字段 | 忽略 | 映射为 ThinkingBlockParam | 忽略 | | 工具调用 | tool_calls 数组 | tool_use blocks | functionCall parts |

适配器由 compile({ target }) 自动选择。也可以独立使用:

import { getAdapter } from "context-chef";
const adapter = getAdapter("gemini");
const payload = adapter.compile(messages);