@caldrix-ai/aerix

中文文档
0.1.7发布于 2026/11/07Apache-2.0TypeScriptES Module零依赖近 30 天下载 2048

Aerix's turn loop on a caldrix.js engine, in the browser.

原始使用文档
文档目录7 节
  1. Install
  2. Example
  3. API
  4. The turn contract
  5. Not built
  6. What has run
  7. License

aerix.js

Aerix's turn loop in the browser: an agent with tools, hooks and named limits, running on a caldrix.js engine. On npm it is @caldrix-ai/aerix.

aerix.js loads nothing itself. You load a model with caldrix.js and hand its Engine to an Agent. The loop asks the engine for one turn at a time, runs the tools the turn calls, and feeds the results back until the model answers with a final or a limit is reached.

Install

npm install @caldrix-ai/caldrix @caldrix-ai/aerix

@caldrix-ai/caldrix is a peer dependency (^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0 || ^0.9.0, the caldrix.js that reads a model's chat_template.jinja).

Example

import { Caldrix } from "@caldrix-ai/caldrix";
import { Agent, tool } from "@caldrix-ai/aerix";

const engine = await Caldrix.load("smollm2-instruct");

const weather = tool({
  name: "weather",
  description: "Today's forecast for a city.",
  input: {
    city: { type: "string", description: "City name", required: true },
    unit: { type: "string", enum: ["c", "f"] },
  },
  run: async ({ city, unit }) => `Sunny in ${city}, 21°${unit ?? "c"}`,
});

const agent = new Agent({
  model: engine,
  instructions: "You are a travel helper. Use the tools to answer.",
  tools: [weather],
  limits: { turns: 8, retries: 2, maxTokens: 384, resultChars: 4000 },
  generation: { temperature: 0 },
  hooks: { beforeCall: (call) => (call.tool === "weather" ? undefined : { deny: "not allowed" }) },
});

const controller = new AbortController();
for await (const ev of agent.run("Should I pack an umbrella for Lisbon?", { signal: controller.signal })) {
  if (ev.type === "final") console.log(ev.text);
}
const { text } = await agent.ask("And for Porto?"); // the same conversation
console.log(text);
agent.reset();

API

The package exports Agent, tool and AerixError, and the types AgentOptions, Limits, Hooks, AgentEvent, Call, EndReason, Model, Tool, ToolDef, Field and FieldType.

Model is { chat(messages, opts?): AsyncIterable<Delta> } over caldrix.js's types. A caldrix.js Engine satisfies it.

tool(def) defines a tool:

  • name matches ^[a-z][a-z0-9_]{0,63}$ and is unique in the agent (a duplicate is refused when the agent is built).
  • description is the text the model sees.
  • input is a flat field map. Each field has a type (string, number, integer or boolean) and an optional description, required and enum. An empty map means no input.
  • run(input, { signal, callId }) returns a string, or a value that goes through JSON.stringify. The input is checked against the fields before run; a failed check or a throw becomes the result error: …, and a tool error never ends the run.

new Agent(options) takes AgentOptions:

  • model (required), instructions (default ""), tools (default none; then the only answer is a final).
  • limits, each a named bound: turns (8, generations per run, retries included), retries (2, extra attempts after a bad or cut turn), maxTokens (512, per generation) and resultChars (4000, one tool result is cut to this with a note).
  • generation: caldrix.js's GenerationOptions without maxTokens and constraint, which aerix.js sets.
  • constrain (default true): send the turn schema as the constraint of every generation. Set false for a Model without constrained decoding; then only parse-and-retry applies.
  • hooks, each optional and awaited: beforeCall(call) may return { deny: reason } (the result becomes refused: <reason>) or { input } (replaces the call's input); afterCall(call, result) may return { result }; onFinal(text) may return { continue: text }, which gives the agent another turn with that text as a user message, within turns.

agent.run(prompt, { signal }) yields AgentEvents and continues the agent's conversation:

  • { type: "text", text }: raw streamed model text.
  • { type: "usage", promptTokens, completionTokens }: one per generation.
  • { type: "turn", turn, thought?, calls }, then per call { type: "call", id, tool, input } and { type: "result", id, tool, text, error }.
  • { type: "retry", turn, reason }: a cut or bad turn is asked again.
  • Exactly one ending: { type: "final", text, thought? }, or { type: "end", reason, message? } where reason is "turns", "retries", "abort" or "error" (an engine failure or a throwing hook, with its message). An engine failure is this event, never a throw.

agent.ask(prompt, { signal }) runs to the end and resolves { text, events } on a final; any other ending rejects with AerixError kind end, carrying reason and events. agent.reset() forgets the conversation. agent.messages is the conversation so far.

One run at a time: a second concurrent run throws AerixError kind busy, and bad options throw kind options.

The turn contract

Every turn is one JSON object, either {"calls":[{"tool":"…","input":{…}}]} or {"final":"…"}, with an optional thought. The system message states this contract and lists the tools; it is the same contract and the same tool rendering as Aerix's.

Every turn is constrained (unless constrain: false): the engine decodes under a schema built from the tools, so a turn that ends on its own is a valid turn with a known tool and an input of the right shape, with one exception: an empty calls list passes the constraint, and parsing refuses it and retries. A turn cut by maxTokens before its JSON closes is dropped and asked again, shorter, as a retry. At most 8 calls of one turn are kept, a thought is cut at 600 characters, and calls run in order, one at a time.

Each turn's prompt is the system message plus every earlier turn and result. The engine keeps what the previous generation processed, so a turn that only appends to the conversation prefills the new part; the done delta's usage.cachedTokens says how much was reused.

Not built

  • Per-option scores for routing (the browser engine returns logprobs; nothing here reads them).
  • Subagents and a verifier, compaction and a context tool, sessions kept in IndexedDB.

What has run

  • The unit tests, against a scripted fake Model: the turn parsing, the system rendering and the turn schema (golden), the turn contract pinned to Aerix's, input checks, each hook, each ending and each limit.
  • One end-to-end test in Playwright's headless Chromium on the wasm tier: an Agent with one tool on the built caldrix.js, loading a tiny test model whose output is noise, from a loopback server. It checks that the engine receives the turn schema on every generation, that every retry follows a cut turn, and that the run ends with a named outcome.

Nothing more is claimed: no real model, no other browser or tier, and no quality or speed figure.

License

aerix.js (the npm package @caldrix-ai/aerix) is under Apache-2.0 (LICENSE, NOTICE).

版本与发布记录

@caldrix-ai/aerix 共发布 9 个版本,首次发布于 2026/10/30;过去 12 个月发布了 9 个版本。

版本发布日期距今
0.1.7latest2026/11/071 天前
0.1.62026/11/072 天前
0.1.52026/11/063 天前
0.1.42026/11/063 天前
0.1.32026/11/054 天前
0.1.22026/11/044 天前

关于 @caldrix-ai/aerix 的常见问题

@caldrix-ai/aerix 是什么?

Aerix's turn loop on a caldrix.js engine, in the browser.

如何安装 @caldrix-ai/aerix?

在项目目录运行 npm install @caldrix-ai/aerix,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@caldrix-ai/caldrix(^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0 || ^0.9.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@caldrix-ai/aerix 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@caldrix-ai/aerix 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@caldrix-ai/aerix 可以商用吗?

@caldrix-ai/aerix 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@caldrix-ai/aerix 的下载量有多少?

根据 npm 官方下载统计,@caldrix-ai/aerix 在 2026/10/09 – 2026/11/07 这 30 天内被下载 2,048 次,日均约 68 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@caldrix-ai/aerix 还在维护吗?

最新版本 0.1.7 发布于 2026/11/07(1 天前);至今共发布 9 个版本,其中过去 12 个月发布了 9 个;首次发布于 2026/10/30。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。