@fifthrevision/axle 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accumulator-BLjnX0uz.js +1 -0
- package/dist/accumulator-svhGVEwB.d.ts +1156 -0
- package/dist/index.d.ts +289 -549
- package/dist/index.js +22 -1
- package/dist/{models-Whj6ZP3l.d.ts → models-BWhStxxX.d.ts} +3 -2
- package/dist/models-Cx50YJNx.js +1 -0
- package/dist/providers/models.d.ts +1 -1
- package/dist/providers/models.js +1 -1
- package/dist/ui.d.ts +2 -2
- package/dist/ui.js +1 -1
- package/package.json +9 -29
- package/README.md +0 -621
- package/dist/ProceduralMemory-CNxHj5B2.js +0 -45
- package/dist/accumulator-D2NgIGxp.d.ts +0 -547
- package/dist/accumulator-vmmD3pxt.js +0 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -6
- package/dist/models-Bp_jVWHN.js +0 -1
- package/dist/models-C7fdWvZ6.js +0 -1
package/README.md
DELETED
|
@@ -1,621 +0,0 @@
|
|
|
1
|
-
# Axle
|
|
2
|
-
|
|
3
|
-
Axle is a TypeScript library for building multi-turn LLM agents. It provides a
|
|
4
|
-
small, focused API for building agentic applications.
|
|
5
|
-
|
|
6
|
-
**Documentation:** https://axle.fifthrevision.com
|
|
7
|
-
|
|
8
|
-
## Quick Start
|
|
9
|
-
|
|
10
|
-
```typescript
|
|
11
|
-
import { Agent, Instruct, anthropic } from "@fifthrevision/axle";
|
|
12
|
-
|
|
13
|
-
const provider = anthropic(process.env.ANTHROPIC_API_KEY);
|
|
14
|
-
const agent = new Agent({ provider, model: "claude-sonnet-4-5-20250929" });
|
|
15
|
-
|
|
16
|
-
const r1 = await agent.send("What is the capital of France?").final;
|
|
17
|
-
if (!r1.ok) throw new Error(r1.error.kind);
|
|
18
|
-
console.log(r1.response); // "Paris is the capital of France."
|
|
19
|
-
|
|
20
|
-
// Multi-turn — history is managed automatically
|
|
21
|
-
const r2 = await agent.send("And what about Germany?").final;
|
|
22
|
-
if (!r2.ok) throw new Error(r2.error.kind);
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Philosophy
|
|
26
|
-
|
|
27
|
-
Axle has two big goals
|
|
28
|
-
|
|
29
|
-
1. A small, focused, and ergonomic interface for building agents. The Agent,
|
|
30
|
-
Instruct, and other APIs are the entire surface, and there is a lot of thought
|
|
31
|
-
to make them distinct and composable.
|
|
32
|
-
2. Systematic prompt improvement. Log what was sent, validate what came back, feed
|
|
33
|
-
learnings into the next run. (This is where the roadmap is headed.)
|
|
34
|
-
|
|
35
|
-
Axle started as a DSPy-inspired workflow tool. As models got better with reasoning
|
|
36
|
-
and tool use, rigid workflow graphs felt unnecessary — but the goals behind them
|
|
37
|
-
(structured output, verification, multi-step reasoning) didn't go away. The project
|
|
38
|
-
shifted toward making those capabilities composable primitives rather than
|
|
39
|
-
fixed pipelines.
|
|
40
|
-
|
|
41
|
-
### Roadmap
|
|
42
|
-
|
|
43
|
-
- **Memory:** Ways to remember previous runs to retrieve them and add them back
|
|
44
|
-
into the prompt for future runs.
|
|
45
|
-
- **Verification:** Automatic and manual ways to verify the output hits goals
|
|
46
|
-
|
|
47
|
-
## Core Concepts
|
|
48
|
-
|
|
49
|
-
### Agent
|
|
50
|
-
|
|
51
|
-
Agent is the primary interface. It owns the provider, model, system prompt,
|
|
52
|
-
tools, and conversation history. `send()` is the only verb — it accepts either a
|
|
53
|
-
plain string or an Instruct.
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
const agent = new Agent({
|
|
57
|
-
provider: anthropic(apiKey),
|
|
58
|
-
model: "claude-sonnet-4-5-20250929",
|
|
59
|
-
system: "You are a helpful assistant.",
|
|
60
|
-
tools: [calculatorTool],
|
|
61
|
-
});
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Instruct
|
|
65
|
-
|
|
66
|
-
Instruct is a rich message. Use it when you need structured output, file
|
|
67
|
-
attachments, bound template inputs, or additional instructions.
|
|
68
|
-
|
|
69
|
-
```typescript
|
|
70
|
-
import * as z from "zod";
|
|
71
|
-
|
|
72
|
-
const instruct = new Instruct({
|
|
73
|
-
prompt: "Summarize the following {{topic}}.",
|
|
74
|
-
schema: z.object({
|
|
75
|
-
summary: z.string(),
|
|
76
|
-
keyPoints: z.array(z.string()),
|
|
77
|
-
}),
|
|
78
|
-
}).withInputs({ topic: "document" });
|
|
79
|
-
instruct.addFile(await loadFileContent("./report.pdf"));
|
|
80
|
-
|
|
81
|
-
const result = await agent.send(instruct).final;
|
|
82
|
-
if (!result.ok) throw new Error(result.error.kind);
|
|
83
|
-
// result.response is { summary: string, keyPoints: string[] }
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
For plain text interactions, pass a string directly to `send()` instead.
|
|
87
|
-
|
|
88
|
-
### Providers
|
|
89
|
-
|
|
90
|
-
Axle ships with first-party support for Anthropic, OpenAI, and Gemini, plus a
|
|
91
|
-
generic ChatCompletions provider for any OpenAI-compatible API.
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
import { anthropic, openai, gemini, chatCompletions } from "@fifthrevision/axle";
|
|
95
|
-
|
|
96
|
-
const a = anthropic(process.env.ANTHROPIC_API_KEY);
|
|
97
|
-
const o = openai(process.env.OPENAI_API_KEY);
|
|
98
|
-
const g = gemini(process.env.GEMINI_API_KEY);
|
|
99
|
-
const local = chatCompletions("http://localhost:11434/v1");
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### `stream()` and `generate()`
|
|
103
|
-
|
|
104
|
-
Agent is built on two lower-level primitives that can be used directly when you
|
|
105
|
-
want full control without conversation management.
|
|
106
|
-
|
|
107
|
-
`stream()` runs a tool loop over a streaming request and returns a handle with
|
|
108
|
-
callbacks for real-time output:
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
import { stream } from "@fifthrevision/axle";
|
|
112
|
-
|
|
113
|
-
const handle = stream({
|
|
114
|
-
provider,
|
|
115
|
-
model,
|
|
116
|
-
messages: [{ role: "user", content: "Hello" }],
|
|
117
|
-
tools: [myTool],
|
|
118
|
-
onToolCall: async (name, params) => ({ type: "success", content: "result" }),
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
handle.on((event) => {
|
|
122
|
-
if (event.type === "text:delta") process.stdout.write(event.delta);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const result = await handle.final;
|
|
126
|
-
if (!result.ok) throw new Error(result.error.kind);
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
`generate()` does the same but without streaming — it returns the final result
|
|
130
|
-
directly as a promise:
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
import { generate } from "@fifthrevision/axle";
|
|
134
|
-
|
|
135
|
-
const result = await generate({
|
|
136
|
-
provider,
|
|
137
|
-
model,
|
|
138
|
-
messages: [{ role: "user", content: "Hello" }],
|
|
139
|
-
tools: [myTool],
|
|
140
|
-
onToolCall: async (name, params) => ({ type: "success", content: "result" }),
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
if (!result.ok) throw new Error(result.error.kind);
|
|
144
|
-
result.response; // final assistant message
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
Both `stream()` and `generate()` also accept an `Instruct` as the latest user
|
|
148
|
-
turn. When `messages` is provided with `instruct`, `messages` is treated as
|
|
149
|
-
prior context and the rendered `Instruct` is appended as the new user message.
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
import * as z from "zod";
|
|
153
|
-
import { generate, Instruct } from "@fifthrevision/axle";
|
|
154
|
-
|
|
155
|
-
const result = await generate({
|
|
156
|
-
provider,
|
|
157
|
-
model,
|
|
158
|
-
messages: previousMessages,
|
|
159
|
-
instruct: new Instruct({
|
|
160
|
-
prompt: "Answer {{question}}.",
|
|
161
|
-
schema: z.object({
|
|
162
|
-
answer: z.string(),
|
|
163
|
-
}),
|
|
164
|
-
}).withInput("question", "Should we proceed?"),
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
if (!result.ok) throw new Error(result.error.kind);
|
|
168
|
-
result.response.answer; // string
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Both handle the full tool-call loop automatically. Agent uses `stream()`
|
|
172
|
-
internally and adds history management, system prompt, and callback wiring on
|
|
173
|
-
top.
|
|
174
|
-
|
|
175
|
-
### Results
|
|
176
|
-
|
|
177
|
-
`generate(...)`, `stream(...).final`, and `agent.send(...).final` all resolve to
|
|
178
|
-
a two-state result:
|
|
179
|
-
|
|
180
|
-
```typescript
|
|
181
|
-
if (!result.ok) {
|
|
182
|
-
result.error.kind; // "model" | "tool" | "parse"
|
|
183
|
-
if (result.error.kind === "parse") {
|
|
184
|
-
result.error.message;
|
|
185
|
-
}
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
result.response; // always present when ok is true
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
For `generate()` and `stream()`, plain calls return the final assistant message.
|
|
193
|
-
For `Agent.send("...")`, plain calls return the assistant text. `Instruct`
|
|
194
|
-
calls return the parsed schema value. Model, tool, and parse failures return
|
|
195
|
-
`ok: false`; abort, fatal tool, configuration, and unexpected execution errors
|
|
196
|
-
still throw.
|
|
197
|
-
|
|
198
|
-
Cancellation follows standard JavaScript abort semantics:
|
|
199
|
-
|
|
200
|
-
- `handle.cancel(reason)` aborts a `stream()` or `agent.send()` handle.
|
|
201
|
-
- `stream().final`, `generate(...)`, and `agent.send(...).final` reject with an error whose `name` is `"AbortError"`.
|
|
202
|
-
- Axle abort errors preserve `reason`, `usage`, and partial state where available (`messages`, `partial`, and for `Agent.send`, `turn`).
|
|
203
|
-
|
|
204
|
-
## Details
|
|
205
|
-
|
|
206
|
-
### Structured Output
|
|
207
|
-
|
|
208
|
-
Pass a Zod schema to Instruct. Axle compiles the schema
|
|
209
|
-
into output format instructions, then parses the response back into typed
|
|
210
|
-
objects.
|
|
211
|
-
|
|
212
|
-
```typescript
|
|
213
|
-
import * as z from "zod";
|
|
214
|
-
|
|
215
|
-
const instruct = new Instruct({
|
|
216
|
-
prompt: "Tell me about Mars.",
|
|
217
|
-
schema: z.object({
|
|
218
|
-
name: z.string(),
|
|
219
|
-
distanceFromSun: z.number(),
|
|
220
|
-
moons: z.array(z.string()),
|
|
221
|
-
}),
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
const agent = new Agent({ provider, model });
|
|
225
|
-
const result = await agent.send(instruct).final;
|
|
226
|
-
if (!result.ok) throw new Error(result.error.kind);
|
|
227
|
-
|
|
228
|
-
result.response.name; // string
|
|
229
|
-
result.response.distanceFromSun; // number
|
|
230
|
-
result.response.moons; // string[]
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
For one-shot structured calls without agent-managed history, pass the same
|
|
234
|
-
`Instruct` directly to `generate()` or `stream()`.
|
|
235
|
-
|
|
236
|
-
### Tools
|
|
237
|
-
|
|
238
|
-
A tool is an object with a name, description, Zod schema, and an `execute`
|
|
239
|
-
function. Pass tools to the Agent constructor.
|
|
240
|
-
|
|
241
|
-
```typescript
|
|
242
|
-
import { z } from "zod";
|
|
243
|
-
|
|
244
|
-
const weatherTool = {
|
|
245
|
-
name: "getWeather",
|
|
246
|
-
description: "Get current weather for a city",
|
|
247
|
-
schema: z.object({ city: z.string() }),
|
|
248
|
-
async execute(input) {
|
|
249
|
-
return JSON.stringify({ temp: 72, condition: "sunny" });
|
|
250
|
-
},
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
const agent = new Agent({
|
|
254
|
-
provider,
|
|
255
|
-
model,
|
|
256
|
-
tools: [weatherTool],
|
|
257
|
-
});
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
Axle includes several built-in tools: `braveSearchTool`, `calculatorTool`,
|
|
261
|
-
`execTool`, `readFileTool`, `writeFileTool`, and `patchFileTool`.
|
|
262
|
-
|
|
263
|
-
### Provider Tools
|
|
264
|
-
|
|
265
|
-
Provider tools are tools that execute on the LLM provider's side (e.g. web
|
|
266
|
-
search, code interpreter). Pass them via the `providerTools` option using
|
|
267
|
-
`{ type: "provider", name: "..." }`.
|
|
268
|
-
|
|
269
|
-
```typescript
|
|
270
|
-
import { Agent, calculatorTool } from "@fifthrevision/axle";
|
|
271
|
-
import type { ProviderTool } from "@fifthrevision/axle";
|
|
272
|
-
|
|
273
|
-
const agent = new Agent({
|
|
274
|
-
provider,
|
|
275
|
-
model,
|
|
276
|
-
tools: [calculatorTool],
|
|
277
|
-
providerTools: [{ type: "provider", name: "web_search" }],
|
|
278
|
-
});
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
Axle maps common names to provider-specific identifiers automatically:
|
|
282
|
-
|
|
283
|
-
| Name | Anthropic | OpenAI | Gemini |
|
|
284
|
-
| ---------------- | --------------------- | -------------------- | --------------- |
|
|
285
|
-
| `web_search` | `web_search_20250305` | `web_search_preview` | `googleSearch` |
|
|
286
|
-
| `code_execution` | — | `code_interpreter` | `codeExecution` |
|
|
287
|
-
|
|
288
|
-
You can also pass provider-specific names directly. Use the optional `config`
|
|
289
|
-
field for provider-specific options:
|
|
290
|
-
|
|
291
|
-
```typescript
|
|
292
|
-
{ type: "provider", name: "web_search", config: { max_results: 5 } }
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
Provider tool events stream as `provider-tool:start` and `provider-tool:complete`.
|
|
296
|
-
|
|
297
|
-
### MCP (Model Context Protocol)
|
|
298
|
-
|
|
299
|
-
Axle supports connecting to MCP servers via stdio or HTTP transport. Create an
|
|
300
|
-
MCP instance, connect it, and pass it to Agent.
|
|
301
|
-
|
|
302
|
-
```typescript
|
|
303
|
-
import { Agent, MCP } from "@fifthrevision/axle";
|
|
304
|
-
|
|
305
|
-
const mcp = new MCP({
|
|
306
|
-
transport: "stdio",
|
|
307
|
-
name: "wc",
|
|
308
|
-
command: "npx",
|
|
309
|
-
args: ["tsx", "path/to/wordcount-server.ts"],
|
|
310
|
-
});
|
|
311
|
-
await mcp.connect();
|
|
312
|
-
|
|
313
|
-
const agent = new Agent({ provider, model, mcps: [mcp] });
|
|
314
|
-
const result = await agent.send("Count the words in 'hello world'").final;
|
|
315
|
-
if (!result.ok) throw new Error(result.error.kind);
|
|
316
|
-
|
|
317
|
-
await mcp.close();
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
The optional `name` field prefixes all tool names from that server (e.g.
|
|
321
|
-
`wc_word_count`) to avoid collisions when using multiple MCPs. When omitted,
|
|
322
|
-
the server's self-reported name is used as the prefix if available.
|
|
323
|
-
|
|
324
|
-
HTTP transport works the same way:
|
|
325
|
-
|
|
326
|
-
```typescript
|
|
327
|
-
const mcp = new MCP({
|
|
328
|
-
transport: "http",
|
|
329
|
-
url: "http://localhost:3100/mcp",
|
|
330
|
-
});
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
### Streaming
|
|
334
|
-
|
|
335
|
-
Axle has two event models, used at different levels:
|
|
336
|
-
|
|
337
|
-
- `Agent.on(...)` emits `TurnEvent` — a high-level turn view organized
|
|
338
|
-
around parts (text, thinking, action).
|
|
339
|
-
- `stream(...).on(...)` emits `StreamEvent` — a lower-level view that
|
|
340
|
-
surfaces every text/thinking/tool transition the provider produces.
|
|
341
|
-
|
|
342
|
-
`Agent` uses `stream()` internally and translates each `StreamEvent` into
|
|
343
|
-
one or more `TurnEvent`s.
|
|
344
|
-
|
|
345
|
-
#### Turn events
|
|
346
|
-
|
|
347
|
-
```typescript
|
|
348
|
-
const agent = new Agent({ provider, model });
|
|
349
|
-
|
|
350
|
-
agent.on((event) => {
|
|
351
|
-
switch (event.type) {
|
|
352
|
-
case "text:delta":
|
|
353
|
-
process.stdout.write(event.delta);
|
|
354
|
-
break;
|
|
355
|
-
case "part:start":
|
|
356
|
-
if (event.part.type === "action") {
|
|
357
|
-
console.log(`Tool: ${event.part.detail.name}`);
|
|
358
|
-
}
|
|
359
|
-
break;
|
|
360
|
-
case "action:complete":
|
|
361
|
-
console.log("Tool complete");
|
|
362
|
-
break;
|
|
363
|
-
case "turn:end":
|
|
364
|
-
console.log(`Turn ${event.status} (in: ${event.usage.in})`);
|
|
365
|
-
break;
|
|
366
|
-
case "error":
|
|
367
|
-
console.error(event.error);
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
const handle = agent.send("Write me a poem.");
|
|
373
|
-
// handle.cancel(reason) aborts mid-stream and rejects handle.final with an AbortError
|
|
374
|
-
try {
|
|
375
|
-
const result = await handle.final;
|
|
376
|
-
if (!result.ok) {
|
|
377
|
-
console.error(result.error);
|
|
378
|
-
}
|
|
379
|
-
} catch (err) {
|
|
380
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
381
|
-
// Cancellation preserves partial state on AxleAbortError: reason, turn, partial, usage
|
|
382
|
-
console.log("Cancelled");
|
|
383
|
-
} else {
|
|
384
|
-
throw err;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
`TurnEvent` types: `session:restore`, `turn:user`, `turn:start`, `turn:end`,
|
|
390
|
-
`part:start`, `part:end`, `text:delta`, `thinking:delta`, `action:args-delta`,
|
|
391
|
-
`action:running`, `action:progress`, `action:complete`, `action:error`,
|
|
392
|
-
`action:child-event`, `annotation:start`, `annotation:update`,
|
|
393
|
-
`annotation:end`, `error`.
|
|
394
|
-
|
|
395
|
-
`part:start` carries a `TurnPart`, discriminated by `part.type` (`"text"`,
|
|
396
|
-
`"thinking"`, `"file"`, `"action"`). Action parts further discriminate on
|
|
397
|
-
`part.kind` (`"tool" | "agent" | "provider-tool"`).
|
|
398
|
-
|
|
399
|
-
Callbacks are registered once and fire on every subsequent `send()`.
|
|
400
|
-
|
|
401
|
-
#### Turn accumulator
|
|
402
|
-
|
|
403
|
-
`Turn` objects are accumulated render state. They are the snapshot counterpart
|
|
404
|
-
to `TurnEvent` streams: text deltas are folded into text parts, tool call
|
|
405
|
-
lifecycles become stable action parts, and tool results are collapsed back into
|
|
406
|
-
the action part that produced them. `AxleMessage[]` remains the canonical model
|
|
407
|
-
conversation state; turns do not affect model input or tool routing.
|
|
408
|
-
|
|
409
|
-
Hosts that transport Axle events over SSE, WebSockets, or another mixed event
|
|
410
|
-
stream can use `TurnAccumulator` instead of reimplementing this reducer:
|
|
411
|
-
|
|
412
|
-
```typescript
|
|
413
|
-
import { TurnAccumulator, type Annotation } from "@fifthrevision/axle/ui";
|
|
414
|
-
|
|
415
|
-
type AppAnnotation =
|
|
416
|
-
| Annotation<{ image: string }, "sandbox">
|
|
417
|
-
| Annotation<{ score: number; passed: boolean }, "eval">;
|
|
418
|
-
|
|
419
|
-
type HostEvent = { type: "run:terminal"; status: string };
|
|
420
|
-
|
|
421
|
-
const accumulator = new TurnAccumulator<AppAnnotation, HostEvent>();
|
|
422
|
-
|
|
423
|
-
for await (const event of events) {
|
|
424
|
-
const { handled, state } = accumulator.apply(event);
|
|
425
|
-
|
|
426
|
-
if (!handled) {
|
|
427
|
-
// event is typed as HostEvent here
|
|
428
|
-
applyHostEvent(event);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
render(state.turns);
|
|
432
|
-
}
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
Use `@fifthrevision/axle/ui` for browser-safe presentation primitives. It
|
|
436
|
-
exports turns, annotations, turn events, and `TurnAccumulator` without importing
|
|
437
|
-
providers, MCP, tools, or other server-side runtime code.
|
|
438
|
-
|
|
439
|
-
The accumulator accepts open event objects. Unknown host events, such as
|
|
440
|
-
`run:terminal` or `session:expired`, return `handled: false` and leave the
|
|
441
|
-
state unchanged. Session-level annotations are accumulated in
|
|
442
|
-
`state.sessionAnnotations`; turn and part annotations are embedded on their
|
|
443
|
-
targets. The accumulator is not idempotent; callers should deduplicate replayed
|
|
444
|
-
transport events before applying them.
|
|
445
|
-
|
|
446
|
-
#### Annotations
|
|
447
|
-
|
|
448
|
-
Annotations are embedded render metadata for sessions, turns, and parts. They
|
|
449
|
-
are useful for out-of-band UI such as sandbox startup, eval results, deployment
|
|
450
|
-
state, or any other consumer-owned status that should render alongside turns
|
|
451
|
-
without becoming model state.
|
|
452
|
-
|
|
453
|
-
```typescript
|
|
454
|
-
type EvalAnnotation = Annotation<{ score: number; passed: boolean }, "eval">;
|
|
455
|
-
|
|
456
|
-
const annotation: EvalAnnotation = {
|
|
457
|
-
id: crypto.randomUUID(),
|
|
458
|
-
kind: "eval",
|
|
459
|
-
label: "Plan adherence",
|
|
460
|
-
placement: "after",
|
|
461
|
-
status: "complete",
|
|
462
|
-
data: { score: 0.92, passed: true },
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
agentEventSink({
|
|
466
|
-
type: "annotation:end",
|
|
467
|
-
target: { type: "turn", turnId },
|
|
468
|
-
annotation,
|
|
469
|
-
});
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
Annotation `label` is required so generic renderers have a common UI surface.
|
|
473
|
-
`placement` defaults to `"after"`, and `annotation:end` defaults missing
|
|
474
|
-
`status` to `"complete"` in accumulated state. `annotation:update` and
|
|
475
|
-
`annotation:end` carry the full updated annotation object; Axle does not define
|
|
476
|
-
patch or merge semantics for annotation data.
|
|
477
|
-
|
|
478
|
-
#### stream() events
|
|
479
|
-
|
|
480
|
-
The low-level `stream()` primitive emits a different event shape — closer
|
|
481
|
-
to the raw provider stream, with separate `start`/`end` events for each
|
|
482
|
-
text and thinking block, and distinct events for tool request, execution,
|
|
483
|
-
and completion.
|
|
484
|
-
|
|
485
|
-
`StreamEvent` types: `text:start`, `text:delta`, `text:end`,
|
|
486
|
-
`thinking:start`, `thinking:delta`, `thinking:end`, `tool:request`,
|
|
487
|
-
`tool:exec-start`, `tool:exec-delta`, `tool:exec-complete`,
|
|
488
|
-
`provider-tool:start`, `provider-tool:complete`, `turn:complete`,
|
|
489
|
-
`tool-results:start`, `tool-results:complete`, `error`.
|
|
490
|
-
|
|
491
|
-
The `turn:complete` and `tool-results:complete` events carry complete
|
|
492
|
-
`AxleAssistantMessage` and `AxleToolCallMessage` objects for client-server
|
|
493
|
-
architectures that need authoritative message boundaries.
|
|
494
|
-
|
|
495
|
-
### Hosting / Sessions
|
|
496
|
-
|
|
497
|
-
Axle stops at the agent runtime boundary. If you need long-lived sessions,
|
|
498
|
-
SSE transport, resumable cursors, or React client hooks, build those concerns
|
|
499
|
-
in your host application on top of `Agent`, `agent.on(...)`, and the streamed
|
|
500
|
-
turn events that Axle emits.
|
|
501
|
-
|
|
502
|
-
## Known Limitations
|
|
503
|
-
|
|
504
|
-
1. Axle does not support multi-modal output right now.
|
|
505
|
-
|
|
506
|
-
## CLI
|
|
507
|
-
|
|
508
|
-
In accordance to Axle's lineage of a workflow tool, Axle exposes a command
|
|
509
|
-
line interface that accepts a declarative config file.
|
|
510
|
-
|
|
511
|
-
### Installation
|
|
512
|
-
|
|
513
|
-
```bash
|
|
514
|
-
npm install -g @fifthrevision/axle
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
### Usage
|
|
518
|
-
|
|
519
|
-
The CLI looks for `axle.job.yaml` and `axle.config.yaml` in the current
|
|
520
|
-
directory by default. You can also specify them using the `-j` and `-c` flags
|
|
521
|
-
|
|
522
|
-
```bash
|
|
523
|
-
axle
|
|
524
|
-
axle -j path/to/job.yaml -c path/to/config.yaml
|
|
525
|
-
axle --args key=value other=thing
|
|
526
|
-
axle --debug
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
A job file specifies the provider, task prompt, and optional tools/files:
|
|
530
|
-
|
|
531
|
-
```yaml
|
|
532
|
-
# axle.job.yaml
|
|
533
|
-
provider:
|
|
534
|
-
type: anthropic
|
|
535
|
-
model: claude-sonnet-4-5-20250929
|
|
536
|
-
|
|
537
|
-
task: |
|
|
538
|
-
Summarize the attached document.
|
|
539
|
-
|
|
540
|
-
tools:
|
|
541
|
-
- calculator
|
|
542
|
-
|
|
543
|
-
provider_tools:
|
|
544
|
-
- web_search
|
|
545
|
-
|
|
546
|
-
files:
|
|
547
|
-
- ./data/report.txt
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
### Batch
|
|
551
|
-
|
|
552
|
-
Add a `batch` key to the job file to run the same task across multiple files.
|
|
553
|
-
Each matched file is attached to the instruct automatically.
|
|
554
|
-
|
|
555
|
-
```yaml
|
|
556
|
-
# axle.job.yaml
|
|
557
|
-
provider:
|
|
558
|
-
type: openai
|
|
559
|
-
|
|
560
|
-
task: |
|
|
561
|
-
Summarize this file.
|
|
562
|
-
|
|
563
|
-
batch:
|
|
564
|
-
files: "./data/*.txt"
|
|
565
|
-
concurrency: 3
|
|
566
|
-
resume: true
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
- `files` — glob pattern for input files
|
|
570
|
-
- `concurrency` — max parallel runs (default 3)
|
|
571
|
-
- `resume` — skip files already processed in a previous run
|
|
572
|
-
|
|
573
|
-
### MCP Servers
|
|
574
|
-
|
|
575
|
-
Add an `mcps` key to connect to MCP servers. Both stdio and HTTP transports
|
|
576
|
-
are supported.
|
|
577
|
-
|
|
578
|
-
```yaml
|
|
579
|
-
# axle.job.yaml
|
|
580
|
-
provider:
|
|
581
|
-
type: anthropic
|
|
582
|
-
|
|
583
|
-
mcps:
|
|
584
|
-
- name: wc
|
|
585
|
-
transport: stdio
|
|
586
|
-
command: npx
|
|
587
|
-
args: ["tsx", "examples/mcps/wordcount-server.ts"]
|
|
588
|
-
- transport: http
|
|
589
|
-
url: http://localhost:3100/mcp
|
|
590
|
-
|
|
591
|
-
task: |
|
|
592
|
-
Count the words in "hello world"
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
Each entry supports:
|
|
596
|
-
|
|
597
|
-
- `transport` — `"stdio"` or `"http"` (required)
|
|
598
|
-
- `name` — prefix for tool names from this server (optional)
|
|
599
|
-
- `command` / `args` / `env` — for stdio transport
|
|
600
|
-
- `url` / `headers` — for HTTP transport
|
|
601
|
-
|
|
602
|
-
### Configuration
|
|
603
|
-
|
|
604
|
-
For CLI use, create an `axle.config.yaml` in your working directory with API
|
|
605
|
-
keys:
|
|
606
|
-
|
|
607
|
-
```yaml
|
|
608
|
-
# axle.config.yaml
|
|
609
|
-
openai:
|
|
610
|
-
api-key: "<api-key>"
|
|
611
|
-
anthropic:
|
|
612
|
-
api-key: "<api-key>"
|
|
613
|
-
gemini:
|
|
614
|
-
api-key: "<api-key>"
|
|
615
|
-
chatcompletions:
|
|
616
|
-
base-url: "http://localhost:11434/v1"
|
|
617
|
-
model: "llama3"
|
|
618
|
-
api-key: "<api-key>" # optional
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
Provider-level keys in the job file override the config file.
|