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

arena-agent

v0.0.1

Published

A TypeScript CodeAgent harness reconstructed from Arena message and tool traces.

Readme

Arena CodeAgent Harness

这是一个根据 message.jsontools.json 逆向实现的 TypeScript CodeAgent harness。运行时只依赖 Node.js,不再依赖 Python:

  • arena -p ...:headless 单轮入口,支持文本、完整 JSON 和 NDJSON 流。
  • arena tui:终端多轮对话,实时输出并自动保存、恢复历史。
  • arena serve:HTTP daemon、Web UI 与双通道 SSE 实时接口。
  • Agent loop:流式 assistant → tool call → tool result → assistant。
  • 11 个工具逐字使用 tools.json 的 Draft-07 schema。

安装

要求 Node.js 20.16+:

npm install
npm run build
npm link

npm run build 编译 src/**/*.tsnpm link 将本项目的 arena CLI 加入当前用户的 PATH。开发时可用:

npm run build
npm run typecheck
npm test

模型

TypeScript 运行时通过 OpenAI-compatible 流协议访问模型。LiteLLM 官方提供 Python SDK 或独立 Proxy,因此纯 TypeScript 版本使用 LiteLLM Proxy 作为多 provider 网关:

export LITELLM_PROXY_URL=http://127.0.0.1:4000/v1
export LITELLM_API_KEY=sk-litellm
export ARENA_MODEL=anthropic/claude-sonnet-4-20250514

设置 LITELLM_PROXY_URL 时,模型名会原样发送给 LiteLLM。没有 Proxy 时, 也支持直连 OpenAI、Anthropic OpenAI-compatibility 与 Gemini OpenAI-compatibility 接口:

# OpenAI
export ARENA_MODEL=openai/gpt-4.1-mini
export OPENAI_API_KEY=...

# Anthropic
export ARENA_MODEL=anthropic/claude-sonnet-4-20250514
export ANTHROPIC_API_KEY=...

# Gemini
export ARENA_MODEL=gemini/gemini-2.5-pro
export GEMINI_API_KEY=...

任何 OpenAI-compatible 服务都可通过 ARENA_API_BASEARENA_API_KEY 直连。当前 DashScope 配置示例:

export ARENA_MODEL=openai/qwen-latest-series-invite-beta-v92
export ARENA_API_BASE=https://dashscope.aliyuncs.com/compatible-mode/v1
export ARENA_API_KEY=...
export ARENA_ENABLE_THINKING=true
export ARENA_THINKING_BUDGET=81920
export ARENA_MAX_TOKENS=131072
export ARENA_CONTEXT_WINDOW=1000000
export ARENA_CONTEXT_COMPACTION_RATIO=0.8
export ARENA_COMPACTION_SUMMARY_MAX_TOKENS=8192

Harness 会在每次模型调用前估算消息、reasoning、工具 schema 和图片占用;达到 ARENA_CONTEXT_WINDOW × ARENA_CONTEXT_COMPACTION_RATIO(默认 80%)时,调用 当前模型把已完成的旧回合生成结构化续作摘要。旧前缀会被替换为与 Arena trace 一致的 leading <arena-system-message>,当前用户回合及其后的 tool-call / result 链保持原样。摘要重点保留目标、硬约束、发现、已完成/未完成事项、下一步和相关 文件路径;摘要调用默认关闭 thinking,输出上限由 ARENA_COMPACTION_SUMMARY_MAX_TOKENS 控制(默认 8192)。

ARENA_CONTEXT_WINDOW 应设置为所用模型的真实上下文窗口;默认 1000000 是 当前 trace 模型的暂定值。token 数目前是本地保守估算,并非 provider tokenizer 的精确计数。如果摘要请求失败,Harness 会保留原历史并继续正常模型请求,避免 摘要服务的瞬时故障直接中断任务。

模型请求遇到网络错误、HTTP 408/409/429 或 5xx 时会自动重试,默认最多重试 5 次。没有 Retry-After 时使用带随机抖动的指数退避;可通过 ARENA_MODEL_MAX_RETRIESARENA_MODEL_RETRY_BASE_MSARENA_MODEL_RETRY_MAX_MS 调整。设 ARENA_MODEL_MAX_RETRIES=0 可关闭。

流式响应开始产出之后发生的网络中断(如 socket 断开、连接重置、 fetch failedterminated)同样会整轮重试:请求本身是幂等的,会用相同的 消息历史重新发起,累加器每次重试都会重置,因此写入 history.json 的是一次 干净完整的 assistant 输出。重试沿用上面同一组配置,并在事件流中先发出一个 model.retry 事件(TUI 会显示「reconnecting after a network error」提示)。 用户主动取消(abort)不会被当作可重试的网络故障。

Agent turn 没有 step 数或整轮墙钟时长上限,会持续执行到模型正常结束、 请求用户输入、发生不可恢复错误,或用户主动取消。工具自身仍可定义单次调用 的超时。每个完整的 assistant/tool step 都会原子保存到 history.json; 进程被外部超时终止时,已完成步骤仍可恢复,最多丢失当时正在执行的一步。

Headless

arena -p 'create a hello.html landing page'
arena -p 'create a hello.html landing page' --output stream-json

每次 headless 执行都会生成一个 UUID session ID,并将完整历史自动写入与 daemon 相同的目录层级和 JSON 格式:

.arena/tenants/<tenantId>/sessions/<sessionId>/history.json

输出事件及 JSON 结果中的 session_id 就是路径里的 <sessionId>history.json 的格式为 {"messages":[...]},即使结果为 needs_inputerror 也会保存。需要跨命令续写同一份显式历史时,可另外传入 --history-file path;该文件会被加载并在执行结束后更新,同时本次自动 session 目录中的历史快照仍会保留。

需要让多次 -p 作为同一个 session 连续对话时,重复传入相同的 --session-id

arena -p '先分析项目结构' --session-id my-session
arena -p '继续实现刚才的方案' --session-id my-session

首次执行会创建对应的 history.json;后续执行会先加载已有消息,追加本轮 消息,并在初始 user 消息及每个完整 assistant/tool step 后原子重写完整 JSON 文件。--session-id--history-file 不能同时使用。

需要附带用户上传文件(尤其是参考图片)时,使用可重复的 --attach (别名 --image),值可以是本地路径或 http(s) URL:

arena -p 'redraw this photo with the reference face' \
  --attach ./my_gf_reference.png \
  --attach 'https://example.com/uploads/photo.jpg'

每个附件会被保存到工作区 uploads/(模型内可通过 /home/user/uploads/<name> 访问;重名自动加 -2-3 后缀,URL 无扩展名 时按图片字节特征补全扩展名)。本轮 user message 与 daemon 附件上传共用同 一构造逻辑,并使用与 Arena trace 相同的原生部件格式:图片部件 {"image": ...} 在前,随后是 prompt 文本 {"text": ...},最后追加一条 <arena-system-message>,列出保存到 /home/user/uploads/ 的文件名。单条 消息最多 10 个附件,单个附件上限 20 MB、总量上限 50 MB,与 daemon 一致。

多模态 user 消息(附件、图片 read_file 回灌、中断通知)在历史与事件流中 一律使用 Arena 原生部件 {"image": ...} / {"text": ...},与 baseline trace 逐字段同构;仅在向模型发起 chat-completions 请求的边界处转换为 OpenAI 风格的 image_url 部件。--attach 传 URL 时 image 直接记录该 URL(与 baseline 引用附件的方式一致,模型服务端会自行下载;重放历史要求 该 URL 仍可访问);本地文件没有可引用的 URL,image 存自包含的 base64 data-URI。

stream-json 每行一个 JSON 对象。startmessageresult 事件中的 messages 与输入样例保持相同契约:

  • system/user:role + content;图片读取后会持久化一条 multimodal user message
  • assistant:role + content + reasoning_content,工具轮含 tool_calls
  • tool:role + tool_call_id + JSON 字符串 content

assistant.delta 实时提供 content、reasoning 与 tool-call 参数碎片; model.startmodel.completemodel.retrytool.starttool.complete 标记阶段。最后的 result 带完整可重放消息。

ask_user 需要交互式前端(TUI/WebUI)来收集选项,因此 headless(-p)与 无会话的 /query 端点不会把 ask_user 提供给模型——只有 TUI 和 WebUI 交互会话(都注入了 ask-user handler)才会开放该工具。这样 headless 不会再 卡在 needs_input 上阻塞训练 worker。作为兜底,若续写的历史或模型幻觉在无 handler 的会话里仍调用了 ask_user,工具会返回一个 error 结果(提示模型用 合理默认值继续),而不是 needs_input

图片 read_file 的 tool result 使用占位文本;真实图片作为紧随工具结果组的 multimodal user message 注入并写入历史,因此后续重放不依赖原图片仍然存在。

TUI

构建后直接进入终端多轮对话:

arena tui
# 指定 workspace、模型和可恢复的 session
arena tui --workspace . --model mock/ask-user --session-id my-session

workspace 默认是启动命令时的当前目录(可用 --workspace 覆盖)。TUI 会把 实际 workspace root 和 cwd 动态注入 system prompt 与工具 schema;因此本机 运行时使用本机路径,部署进沙箱后会自然使用沙箱内的实际路径,不需要切换模式。 TUI 没有内置文件 viewer,因此不会向模型提供 present_file;模型会直接报告 生成文件的实际路径。

assistant 的 reasoning、正文和工具调用状态会边生成边输出。每轮结束都会把 完整消息原子写入与 headless、daemon 相同的 history.json;再次传入相同的 --session-id 会恢复上下文。也可改用工作区内的显式 --history-file path,两者不能同时使用。

TUI 会直接接管 ask_user 工具,在当前轮中显示选项并继续执行。内置命令:

  • /help:显示帮助
  • /history:打印当前会话
  • /session:打印 session ID 和历史文件位置
  • /exit(或 /quit):保存并退出

Ctrl-C 在生成期间取消当前轮;在输入提示符处按下则退出。开发模式也可用 npm run tui -- --model mock/landing-page

TypeScript SDK

SDK 同时支持 Node.js 20+ 与浏览器。它调用 arena serve 的 HTTP 接口,并 负责 session credential、token 刷新、SSE 解析与断线续传:

import {
  ArenaClient,
  type ArenaInputRequest,
} from "arena-codeagent-harness";

const arena = new ArenaClient({
  baseUrl: "http://127.0.0.1:3000",
  onCredentials(credentials) {
    // 应用负责把 credentials 持久化到自己的安全存储。
    saveCredentials(credentials);
  },
});

const session = await arena.sessions.create();
const result = await session.run({
  input: "创建一个 hello.html",
  files: [
    {
      name: "notes.txt",
      data: new Blob(["project notes"], { type: "text/plain" }),
    },
  ],
  onInputRequired: async (request) => [
    {
      questionId: request.questions[0].id,
      optionId: request.questions[0].options[0].id,
    },
  ],
});

console.log(result.text, result.files);

stream() 返回一个 async iterable,适合逐事件消费;没有 onInputRequired 时,可以在收到 data-user-input-request 后显式调用 respondToInput()

for await (const event of session.stream({ input: "设计一个网站" })) {
  if (event.type === "text-delta") process.stdout.write(String(event.delta));
  if (event.type === "data-user-input-request") {
    const request = event.data as ArenaInputRequest;
    await session.respondToInput(request.requestId, [
      { questionId: "style", optionId: "minimal" },
    ]);
  }
}

持久连接、恢复和取消分别使用:

const events = session.connect({
  onEvent: (event) => render(event),
  onStateChange: (state) => showConnectionState(state),
});
await events.ready;
await session.send({ input: "分析 workspace" });

await session.cancel(); // 只取消服务端当前 turn,不销毁 session
events.close();         // 只关闭本地 SSE 连接

const resumed = arena.sessions.resume(savedCredentials);
const history = await resumed.getHistory();
const next = await resumed.run({ input: "继续" });

也可以用 arena.run() 创建临时 session 并一次性收集结果。SDK 导出完整 TypeScript 类型,服务端还会在 /sdk.js 提供同一份浏览器 ESM。Web UI 本身也通过这套 SDK 调用服务端。

完整说明见 docs/SDK.md

Daemon 与 Web UI

arena serve
# 或
npm start

浏览器打开 http://127.0.0.1:3000。页面实时展示 reasoning、工具输入输出、交互问题及 workspace 文件。session ID 由服务端 生成;打开页面或点击 New Chat 时只保留浏览器内的空白草稿,首次发送消息 (包括带附件的消息)时才创建 session。每个 session 的凭证哈希、历史、 workspace 和 artifacts 分别保存在:

.arena/tenants/<tenantId>/sessions/<sessionId>/
  session.json
  history.json
  workspace/
  artifacts/

ARENA_TENANT_ID 默认是 local,部署到已有认证网关时应设置为网关绑定的 tenant 标识。客户端不能通过传入 session ID 重新领取会话;刷新 token 必须 同时持有该 session 当前的 bearer credential。新 session 默认从基础 --workspace 初始化独立副本(文件系统支持时优先使用 copy-on-write), 并排除 .arena.git、依赖/构建目录和 .env* 密钥文件;若需要空 workspace,可设置 ARENA_SESSION_WORKSPACE_MODE=empty

| 接口 | 作用 | |---|---| | POST /api/chat/trigger-session | 创建会话并返回短期 token | | POST /api/chat/trigger-token | 刷新 token | | POST /ai-proxy/realtime/v1/sessions/:id/in/append | 写入用户消息 | | GET /ai-proxy/realtime/v1/sessions/:id/out | SSE 输出实时事件 | | GET /api/chat/sessions/:id/messages | 恢复完整消息历史 | | POST /api/chat/sessions/:id/uploads | 上传当前消息的附件 | | POST /api/chat/sessions/:id/input-response | 回答 ask_user | | POST /api/chat/sessions/:id/cancel | 取消当前 turn | | GET /api/chat/sessions/:id/workspace/:path | 鉴权获取该 session 的 workspace 文件 |

创建会话时,客户端需要提供用于初始 system message 的本地日期与 IANA 时区; 位置为可选的客户端上下文:

{
  "currentDate": "2026-07-23",
  "timezone": "Asia/Singapore",
  "location": {
    "city": "Singapore",
    "region": "Singapore",
    "country": "SG"
  }
}

这些值会随 session metadata 持久化,后续 turn 继续复用首次生成的 system message。未提供位置时,prompt 会明确标记位置不可用,不会使用样例中的位置。

附件采用两阶段提交。客户端先将每个文件作为原始请求体上传到 uploads 接口,并通过 URI 编码的 x-file-name header 传递文件名;再把返回的 attachment.path 作为 { "type": "file", "path": "uploads/..." } part 放入实时消息。每个文件最大 20 MB,每条消息最多 10 个文件、合计 50 MB。服务端会把文件写入该 session 的 /home/user/uploads/ 映射目录, 图片作为 multimodal content 注入,并在 user message 最后追加 arena-system-message 附件清单。附件引用只能指向本 session 的 uploads/,不能读取其他 workspace 路径或其他 session 的文件。

SSE 支持 Last-Event-ID 断线续传。ask_user 会暂停同一个 Agent Promise; 用户提交答案后生成标准 role=tool 消息并继续循环。

取消正在执行的 turn 时,daemon 会保存已经完成的 assistant/tool 消息以及已 流式生成的 partial assistant 内容。下一条用户消息会在最前面注入标准 arena-system-message interruption notice,且仅注入一次;如果取消发生在 ask_user 等待期间,则记录 {"answers":[],"skipped":true} tool result。 该待注入状态写入 session metadata,daemon 重启后仍可恢复。

present_file 会输出独立的 data-present-file 事件并自动打开前端 viewer。 HTML/SVG、图片、音频、视频、PDF 和文本可内嵌预览;Office 文件提供打开或 下载入口。HTML viewer 使用 sandbox="allow-scripts" 与 CSP 网络隔离, 外部脚本、样式和图片不会被加载。

兼容的简单流式接口:

curl -N -X POST \
  -H 'Content-Type: text/plain' \
  --data 'create a hello.html landing page' \
  http://127.0.0.1:3000/query

也支持 JSON POST 和 GET /query?q=...;健康检查是 GET /health

工具

原始规范逐字保存在:

  • spec/message.json
  • spec/tools.json

运行时加载这两个文件,并在执行前使用 AJV 校验原始 Draft-07 schema:

bashask_useredit_fileread_filefetch_pageweb_searchwrite_fileimage_searchpresent_filegenerate_imagegenerate_speech

web_searchimage_searchgenerate_imagegenerate_speech 都使用 阿里云百炼 DashScope,并从工作区 .env 读取 DASHSCOPE_API_KEY。默认 调用公共北京地域地址;若使用业务空间专属域名或新加坡地域,可设置 DASHSCOPE_API_BASE(可带或不带 /api/v1)。

fetch_page 使用 Defuddle 将 HTML 正文转换为 Markdown,并使用 pdf-parse 提取 PDF 的前 30 页。响应按 Markdown 边界稳定分块,同一轮内 重复读取后续 chunkIndex 不会重新下载;抓取过程限制重定向、超时与响应 大小,并拒绝 localhost、私网和 link-local 地址。

read_file 可直接提取本地 PDF(前 30 页)、DOCX、PPTX、XLSX 的文字和 表格内容;旧版 DOC、PPT、XLS 会先通过 LibreOffice 转换。默认从 PATH 查找 soffice,也可用 ARENA_SOFFICE_PATH 指定其路径。文档解压和输出 均设有大小上限;当前不做 OCR,扫描版 PDF 只会返回无可提取文字的提示。

可用 ARENA_WEB_SEARCH_MODELARENA_IMAGE_SEARCH_MODELARENA_IMAGE_MODELARENA_SPEECH_MODEL 覆盖默认模型。图片生成结果和 搜索到的图片会立即下载到工作区;每次图片搜索的结果位于 assets/image-search/<tool-call-id>/,并作为可预览文件显示在 Web UI。 语音生成目前按 DashScope HTTP API 支持 .mp3.wav.opus 输出。

所有路径工具都限制在 --workspace 内并检查符号链接逃逸。bash 是本机 子进程,不是操作系统安全沙箱;多租户或不可信模型部署应使用容器或 VM。

测试

内置 mock 模型只用于无密钥集成测试:

ARENA_MODEL=mock/landing-page arena -p \
  'create a hello.html landing page' --output stream-json
npm test

测试覆盖消息契约、工具校验、headless、/query、实时双通道、 ask_userpresent_file 和 daemon 优雅退出。