@eminent337/aery 0.1.27 → 0.1.29
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/README.md +2 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +2 -2
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/compaction.md +10 -10
- package/docs/custom-provider.md +18 -18
- package/docs/development.md +3 -3
- package/docs/extensions.md +163 -163
- package/docs/json.md +3 -3
- package/docs/keybindings.md +5 -5
- package/docs/models.md +1 -1
- package/docs/packages.md +9 -9
- package/docs/prompt-templates.md +3 -3
- package/docs/providers.md +2 -2
- package/docs/rpc.md +8 -8
- package/docs/sdk.md +15 -15
- package/docs/session.md +3 -3
- package/docs/settings.md +7 -7
- package/docs/shell-aliases.md +1 -1
- package/docs/skills.md +5 -5
- package/docs/terminal-setup.md +1 -1
- package/docs/termux.md +2 -2
- package/docs/themes.md +5 -5
- package/docs/tree.md +1 -1
- package/docs/tui.md +9 -9
- package/docs/windows.md +1 -1
- package/package.json +3 -2
package/docs/extensions.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
>
|
|
1
|
+
> Aery can create extensions. Ask it to build one for your use case.
|
|
2
2
|
|
|
3
3
|
# Extensions
|
|
4
4
|
|
|
5
|
-
Extensions are TypeScript modules that extend
|
|
5
|
+
Extensions are TypeScript modules that extend Aery's behavior. They can subscribe to lifecycle events, register custom tools callable by the LLM, add commands, and more.
|
|
6
6
|
|
|
7
|
-
> **Placement for /reload:** Put extensions in `~/.
|
|
7
|
+
> **Placement for /reload:** Put extensions in `~/.aery/agent/extensions/` (global) or `.aery/extensions/` (project-local) for auto-discovery. Use `aery -e ./path.ts` only for quick tests. Extensions in auto-discovered locations can be hot-reloaded with `/reload`.
|
|
8
8
|
|
|
9
9
|
**Key capabilities:**
|
|
10
|
-
- **Custom tools** - Register tools the LLM can call via `
|
|
10
|
+
- **Custom tools** - Register tools the LLM can call via `aery.registerTool()`
|
|
11
11
|
- **Event interception** - Block or modify tool calls, inject context, customize compaction
|
|
12
12
|
- **User interaction** - Prompt users via `ctx.ui` (select, confirm, input, notify)
|
|
13
13
|
- **Custom UI components** - Full TUI components with keyboard input via `ctx.ui.custom()` for complex interactions
|
|
14
|
-
- **Custom commands** - Register commands like `/mycommand` via `
|
|
15
|
-
- **Session persistence** - Store state that survives restarts via `
|
|
14
|
+
- **Custom commands** - Register commands like `/mycommand` via `aery.registerCommand()`
|
|
15
|
+
- **Session persistence** - Store state that survives restarts via `aery.appendEntry()`
|
|
16
16
|
- **Custom rendering** - Control how tool calls/results and messages appear in TUI
|
|
17
17
|
|
|
18
18
|
**Example use cases:**
|
|
@@ -53,19 +53,19 @@ See [examples/extensions/](../examples/extensions/) for working implementations.
|
|
|
53
53
|
|
|
54
54
|
## Quick Start
|
|
55
55
|
|
|
56
|
-
Create `~/.
|
|
56
|
+
Create `~/.aery/agent/extensions/my-extension.ts`:
|
|
57
57
|
|
|
58
58
|
```typescript
|
|
59
59
|
import type { ExtensionAPI } from "@eminent337/aery";
|
|
60
60
|
import { Type } from "@sinclair/typebox";
|
|
61
61
|
|
|
62
|
-
export default function (
|
|
62
|
+
export default function (aery: ExtensionAPI) {
|
|
63
63
|
// React to events
|
|
64
|
-
|
|
64
|
+
aery.on("session_start", async (_event, ctx) => {
|
|
65
65
|
ctx.ui.notify("Extension loaded!", "info");
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
aery.on("tool_call", async (event, ctx) => {
|
|
69
69
|
if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
|
|
70
70
|
const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
|
|
71
71
|
if (!ok) return { block: true, reason: "Blocked by user" };
|
|
@@ -73,7 +73,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
// Register a custom tool
|
|
76
|
-
|
|
76
|
+
aery.registerTool({
|
|
77
77
|
name: "greet",
|
|
78
78
|
label: "Greet",
|
|
79
79
|
description: "Greet someone by name",
|
|
@@ -89,7 +89,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
89
89
|
});
|
|
90
90
|
|
|
91
91
|
// Register a command
|
|
92
|
-
|
|
92
|
+
aery.registerCommand("hello", {
|
|
93
93
|
description: "Say hello",
|
|
94
94
|
handler: async (args, ctx) => {
|
|
95
95
|
ctx.ui.notify(`Hello ${args || "world"}!`, "info");
|
|
@@ -101,7 +101,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
101
101
|
Test with `--extension` (or `-e`) flag:
|
|
102
102
|
|
|
103
103
|
```bash
|
|
104
|
-
|
|
104
|
+
aery -e ./my-extension.ts
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
## Extension Locations
|
|
@@ -112,10 +112,10 @@ Extensions are auto-discovered from:
|
|
|
112
112
|
|
|
113
113
|
| Location | Scope |
|
|
114
114
|
|----------|-------|
|
|
115
|
-
| `~/.
|
|
116
|
-
| `~/.
|
|
117
|
-
| `.
|
|
118
|
-
| `.
|
|
115
|
+
| `~/.aery/agent/extensions/*.ts` | Global (all projects) |
|
|
116
|
+
| `~/.aery/agent/extensions/*/index.ts` | Global (subdirectory) |
|
|
117
|
+
| `.aery/extensions/*.ts` | Project-local |
|
|
118
|
+
| `.aery/extensions/*/index.ts` | Project-local (subdirectory) |
|
|
119
119
|
|
|
120
120
|
Additional paths via `settings.json`:
|
|
121
121
|
|
|
@@ -132,7 +132,7 @@ Additional paths via `settings.json`:
|
|
|
132
132
|
}
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
To share extensions via npm or git as
|
|
135
|
+
To share extensions via npm or git as Aery packages, see [packages.md](packages.md).
|
|
136
136
|
|
|
137
137
|
## Available Imports
|
|
138
138
|
|
|
@@ -145,7 +145,7 @@ To share extensions via npm or git as pi packages, see [packages.md](packages.md
|
|
|
145
145
|
|
|
146
146
|
npm dependencies work too. Add a `package.json` next to your extension (or in a parent directory), run `npm install`, and imports from `node_modules/` are resolved automatically.
|
|
147
147
|
|
|
148
|
-
For distributed
|
|
148
|
+
For distributed Aery packages installed with `aery install` (npm or git), runtime deps must be in `dependencies`. Package installation uses production installs (`npm install --omit=dev`), so `devDependencies` are not available at runtime.
|
|
149
149
|
|
|
150
150
|
Node.js built-ins (`node:fs`, `node:path`, etc.) are also available.
|
|
151
151
|
|
|
@@ -156,9 +156,9 @@ An extension exports a default factory function that receives `ExtensionAPI`. Th
|
|
|
156
156
|
```typescript
|
|
157
157
|
import type { ExtensionAPI } from "@eminent337/aery";
|
|
158
158
|
|
|
159
|
-
export default function (
|
|
159
|
+
export default function (aery: ExtensionAPI) {
|
|
160
160
|
// Subscribe to events
|
|
161
|
-
|
|
161
|
+
aery.on("event_name", async (event, ctx) => {
|
|
162
162
|
// ctx.ui for user interaction
|
|
163
163
|
const ok = await ctx.ui.confirm("Title", "Are you sure?");
|
|
164
164
|
ctx.ui.notify("Done!", "success");
|
|
@@ -167,16 +167,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
167
167
|
});
|
|
168
168
|
|
|
169
169
|
// Register tools, commands, shortcuts, flags
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
aery.registerTool({ ... });
|
|
171
|
+
aery.registerCommand("name", { ... });
|
|
172
|
+
aery.registerShortcut("ctrl+x", { ... });
|
|
173
|
+
aery.registerFlag("my-flag", { ... });
|
|
174
174
|
}
|
|
175
175
|
```
|
|
176
176
|
|
|
177
177
|
Extensions are loaded via [jiti](https://github.com/unjs/jiti), so TypeScript works without compilation.
|
|
178
178
|
|
|
179
|
-
If the factory returns a `Promise`,
|
|
179
|
+
If the factory returns a `Promise`, Aery awaits it before continuing startup. That means async initialization completes before `session_start`, before `resources_discover`, and before provider registrations queued via `aery.registerProvider()` are flushed.
|
|
180
180
|
|
|
181
181
|
### Async factory functions
|
|
182
182
|
|
|
@@ -185,7 +185,7 @@ Use an async factory for one-time startup work such as fetching remote configura
|
|
|
185
185
|
```typescript
|
|
186
186
|
import type { ExtensionAPI } from "@eminent337/aery";
|
|
187
187
|
|
|
188
|
-
export default async function (
|
|
188
|
+
export default async function (aery: ExtensionAPI) {
|
|
189
189
|
const response = await fetch("http://localhost:1234/v1/models");
|
|
190
190
|
const payload = (await response.json()) as {
|
|
191
191
|
data: Array<{
|
|
@@ -196,7 +196,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
196
196
|
}>;
|
|
197
197
|
};
|
|
198
198
|
|
|
199
|
-
|
|
199
|
+
aery.registerProvider("local-openai", {
|
|
200
200
|
baseUrl: "http://localhost:1234/v1",
|
|
201
201
|
apiKey: "LOCAL_OPENAI_API_KEY",
|
|
202
202
|
api: "openai-completions",
|
|
@@ -213,21 +213,21 @@ export default async function (pi: ExtensionAPI) {
|
|
|
213
213
|
}
|
|
214
214
|
```
|
|
215
215
|
|
|
216
|
-
This pattern makes the fetched models available during normal startup and to `
|
|
216
|
+
This pattern makes the fetched models available during normal startup and to `aery --list-models`.
|
|
217
217
|
|
|
218
218
|
### Extension Styles
|
|
219
219
|
|
|
220
220
|
**Single file** - simplest, for small extensions:
|
|
221
221
|
|
|
222
222
|
```
|
|
223
|
-
~/.
|
|
223
|
+
~/.aery/agent/extensions/
|
|
224
224
|
└── my-extension.ts
|
|
225
225
|
```
|
|
226
226
|
|
|
227
227
|
**Directory with index.ts** - for multi-file extensions:
|
|
228
228
|
|
|
229
229
|
```
|
|
230
|
-
~/.
|
|
230
|
+
~/.aery/agent/extensions/
|
|
231
231
|
└── my-extension/
|
|
232
232
|
├── index.ts # Entry point (exports default function)
|
|
233
233
|
├── tools.ts # Helper module
|
|
@@ -237,7 +237,7 @@ This pattern makes the fetched models available during normal startup and to `pi
|
|
|
237
237
|
**Package with dependencies** - for extensions that need npm packages:
|
|
238
238
|
|
|
239
239
|
```
|
|
240
|
-
~/.
|
|
240
|
+
~/.aery/agent/extensions/
|
|
241
241
|
└── my-extension/
|
|
242
242
|
├── package.json # Declares dependencies and entry points
|
|
243
243
|
├── package-lock.json
|
|
@@ -254,7 +254,7 @@ This pattern makes the fetched models available during normal startup and to `pi
|
|
|
254
254
|
"zod": "^3.0.0",
|
|
255
255
|
"chalk": "^5.0.0"
|
|
256
256
|
},
|
|
257
|
-
"
|
|
257
|
+
"aery": {
|
|
258
258
|
"extensions": ["./src/index.ts"]
|
|
259
259
|
}
|
|
260
260
|
}
|
|
@@ -267,7 +267,7 @@ Run `npm install` in the extension directory, then imports from `node_modules/`
|
|
|
267
267
|
### Lifecycle Overview
|
|
268
268
|
|
|
269
269
|
```
|
|
270
|
-
|
|
270
|
+
Aery starts
|
|
271
271
|
│
|
|
272
272
|
├─► session_start { reason: "startup" }
|
|
273
273
|
└─► resources_discover { reason: "startup" }
|
|
@@ -337,7 +337,7 @@ Fired after `session_start` so extensions can contribute additional skill, promp
|
|
|
337
337
|
The startup path uses `reason: "startup"`. Reload uses `reason: "reload"`.
|
|
338
338
|
|
|
339
339
|
```typescript
|
|
340
|
-
|
|
340
|
+
aery.on("resources_discover", async (event, _ctx) => {
|
|
341
341
|
// event.cwd - current working directory
|
|
342
342
|
// event.reason - "startup" | "reload"
|
|
343
343
|
return {
|
|
@@ -357,7 +357,7 @@ See [session.md](session.md) for session storage internals and the SessionManage
|
|
|
357
357
|
Fired when a session is started, loaded, or reloaded.
|
|
358
358
|
|
|
359
359
|
```typescript
|
|
360
|
-
|
|
360
|
+
aery.on("session_start", async (event, ctx) => {
|
|
361
361
|
// event.reason - "startup" | "reload" | "new" | "resume" | "fork"
|
|
362
362
|
// event.previousSessionFile - present for "new", "resume", and "fork"
|
|
363
363
|
ctx.ui.notify(`Session: ${ctx.sessionManager.getSessionFile() ?? "ephemeral"}`, "info");
|
|
@@ -369,7 +369,7 @@ pi.on("session_start", async (event, ctx) => {
|
|
|
369
369
|
Fired before starting a new session (`/new`) or switching sessions (`/resume`).
|
|
370
370
|
|
|
371
371
|
```typescript
|
|
372
|
-
|
|
372
|
+
aery.on("session_before_switch", async (event, ctx) => {
|
|
373
373
|
// event.reason - "new" or "resume"
|
|
374
374
|
// event.targetSessionFile - session we're switching to (only for "resume")
|
|
375
375
|
|
|
@@ -380,7 +380,7 @@ pi.on("session_before_switch", async (event, ctx) => {
|
|
|
380
380
|
});
|
|
381
381
|
```
|
|
382
382
|
|
|
383
|
-
After a successful switch or new-session action,
|
|
383
|
+
After a successful switch or new-session action, Aery emits `session_shutdown` for the old extension instance, reloads and rebinds extensions for the new session, then emits `session_start` with `reason: "new" | "resume"` and `previousSessionFile`.
|
|
384
384
|
Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `session_start`.
|
|
385
385
|
|
|
386
386
|
#### session_before_fork
|
|
@@ -388,7 +388,7 @@ Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `
|
|
|
388
388
|
Fired when forking via `/fork` or cloning via `/clone`.
|
|
389
389
|
|
|
390
390
|
```typescript
|
|
391
|
-
|
|
391
|
+
aery.on("session_before_fork", async (event, ctx) => {
|
|
392
392
|
// event.entryId - ID of the selected entry
|
|
393
393
|
// event.position - "before" for /fork, "at" for /clone
|
|
394
394
|
return { cancel: true }; // Cancel fork/clone
|
|
@@ -397,7 +397,7 @@ pi.on("session_before_fork", async (event, ctx) => {
|
|
|
397
397
|
});
|
|
398
398
|
```
|
|
399
399
|
|
|
400
|
-
After a successful fork or clone,
|
|
400
|
+
After a successful fork or clone, Aery emits `session_shutdown` for the old extension instance, reloads and rebinds extensions for the new session, then emits `session_start` with `reason: "fork"` and `previousSessionFile`.
|
|
401
401
|
Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `session_start`.
|
|
402
402
|
|
|
403
403
|
#### session_before_compact / session_compact
|
|
@@ -405,7 +405,7 @@ Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `
|
|
|
405
405
|
Fired on compaction. See [compaction.md](compaction.md) for details.
|
|
406
406
|
|
|
407
407
|
```typescript
|
|
408
|
-
|
|
408
|
+
aery.on("session_before_compact", async (event, ctx) => {
|
|
409
409
|
const { preparation, branchEntries, customInstructions, signal } = event;
|
|
410
410
|
|
|
411
411
|
// Cancel:
|
|
@@ -421,7 +421,7 @@ pi.on("session_before_compact", async (event, ctx) => {
|
|
|
421
421
|
};
|
|
422
422
|
});
|
|
423
423
|
|
|
424
|
-
|
|
424
|
+
aery.on("session_compact", async (event, ctx) => {
|
|
425
425
|
// event.compactionEntry - the saved compaction
|
|
426
426
|
// event.fromExtension - whether extension provided it
|
|
427
427
|
});
|
|
@@ -432,14 +432,14 @@ pi.on("session_compact", async (event, ctx) => {
|
|
|
432
432
|
Fired on `/tree` navigation. See [tree.md](tree.md) for tree navigation concepts.
|
|
433
433
|
|
|
434
434
|
```typescript
|
|
435
|
-
|
|
435
|
+
aery.on("session_before_tree", async (event, ctx) => {
|
|
436
436
|
const { preparation, signal } = event;
|
|
437
437
|
return { cancel: true };
|
|
438
438
|
// OR provide custom summary:
|
|
439
439
|
return { summary: { summary: "...", details: {} } };
|
|
440
440
|
});
|
|
441
441
|
|
|
442
|
-
|
|
442
|
+
aery.on("session_tree", async (event, ctx) => {
|
|
443
443
|
// event.newLeafId, oldLeafId, summaryEntry, fromExtension
|
|
444
444
|
});
|
|
445
445
|
```
|
|
@@ -449,7 +449,7 @@ pi.on("session_tree", async (event, ctx) => {
|
|
|
449
449
|
Fired on exit (Ctrl+C, Ctrl+D, SIGHUP, SIGTERM).
|
|
450
450
|
|
|
451
451
|
```typescript
|
|
452
|
-
|
|
452
|
+
aery.on("session_shutdown", async (_event, ctx) => {
|
|
453
453
|
// Cleanup, save state, etc.
|
|
454
454
|
});
|
|
455
455
|
```
|
|
@@ -461,7 +461,7 @@ pi.on("session_shutdown", async (_event, ctx) => {
|
|
|
461
461
|
Fired after user submits prompt, before agent loop. Can inject a message and/or modify the system prompt.
|
|
462
462
|
|
|
463
463
|
```typescript
|
|
464
|
-
|
|
464
|
+
aery.on("before_agent_start", async (event, ctx) => {
|
|
465
465
|
// event.prompt - user's prompt text
|
|
466
466
|
// event.images - attached images (if any)
|
|
467
467
|
// event.systemPrompt - current system prompt
|
|
@@ -484,9 +484,9 @@ pi.on("before_agent_start", async (event, ctx) => {
|
|
|
484
484
|
Fired once per user prompt.
|
|
485
485
|
|
|
486
486
|
```typescript
|
|
487
|
-
|
|
487
|
+
aery.on("agent_start", async (_event, ctx) => {});
|
|
488
488
|
|
|
489
|
-
|
|
489
|
+
aery.on("agent_end", async (event, ctx) => {
|
|
490
490
|
// event.messages - messages from this prompt
|
|
491
491
|
});
|
|
492
492
|
```
|
|
@@ -496,11 +496,11 @@ pi.on("agent_end", async (event, ctx) => {
|
|
|
496
496
|
Fired for each turn (one LLM response + tool calls).
|
|
497
497
|
|
|
498
498
|
```typescript
|
|
499
|
-
|
|
499
|
+
aery.on("turn_start", async (event, ctx) => {
|
|
500
500
|
// event.turnIndex, event.timestamp
|
|
501
501
|
});
|
|
502
502
|
|
|
503
|
-
|
|
503
|
+
aery.on("turn_end", async (event, ctx) => {
|
|
504
504
|
// event.turnIndex, event.message, event.toolResults
|
|
505
505
|
});
|
|
506
506
|
```
|
|
@@ -513,16 +513,16 @@ Fired for message lifecycle updates.
|
|
|
513
513
|
- `message_update` fires for assistant streaming updates.
|
|
514
514
|
|
|
515
515
|
```typescript
|
|
516
|
-
|
|
516
|
+
aery.on("message_start", async (event, ctx) => {
|
|
517
517
|
// event.message
|
|
518
518
|
});
|
|
519
519
|
|
|
520
|
-
|
|
520
|
+
aery.on("message_update", async (event, ctx) => {
|
|
521
521
|
// event.message
|
|
522
522
|
// event.assistantMessageEvent (token-by-token stream event)
|
|
523
523
|
});
|
|
524
524
|
|
|
525
|
-
|
|
525
|
+
aery.on("message_end", async (event, ctx) => {
|
|
526
526
|
// event.message
|
|
527
527
|
});
|
|
528
528
|
```
|
|
@@ -537,15 +537,15 @@ In parallel tool mode:
|
|
|
537
537
|
- `tool_execution_end` is emitted in assistant source order, matching final tool result message order
|
|
538
538
|
|
|
539
539
|
```typescript
|
|
540
|
-
|
|
540
|
+
aery.on("tool_execution_start", async (event, ctx) => {
|
|
541
541
|
// event.toolCallId, event.toolName, event.args
|
|
542
542
|
});
|
|
543
543
|
|
|
544
|
-
|
|
544
|
+
aery.on("tool_execution_update", async (event, ctx) => {
|
|
545
545
|
// event.toolCallId, event.toolName, event.args, event.partialResult
|
|
546
546
|
});
|
|
547
547
|
|
|
548
|
-
|
|
548
|
+
aery.on("tool_execution_end", async (event, ctx) => {
|
|
549
549
|
// event.toolCallId, event.toolName, event.result, event.isError
|
|
550
550
|
});
|
|
551
551
|
```
|
|
@@ -555,7 +555,7 @@ pi.on("tool_execution_end", async (event, ctx) => {
|
|
|
555
555
|
Fired before each LLM call. Modify messages non-destructively. See [session.md](session.md) for message types.
|
|
556
556
|
|
|
557
557
|
```typescript
|
|
558
|
-
|
|
558
|
+
aery.on("context", async (event, ctx) => {
|
|
559
559
|
// event.messages - deep copy, safe to modify
|
|
560
560
|
const filtered = event.messages.filter(m => !shouldPrune(m));
|
|
561
561
|
return { messages: filtered };
|
|
@@ -567,7 +567,7 @@ pi.on("context", async (event, ctx) => {
|
|
|
567
567
|
Fired after the provider-specific payload is built, right before the request is sent. Handlers run in extension load order. Returning `undefined` keeps the payload unchanged. Returning any other value replaces the payload for later handlers and for the actual request.
|
|
568
568
|
|
|
569
569
|
```typescript
|
|
570
|
-
|
|
570
|
+
aery.on("before_provider_request", (event, ctx) => {
|
|
571
571
|
console.log(JSON.stringify(event.payload, null, 2));
|
|
572
572
|
|
|
573
573
|
// Optional: replace payload
|
|
@@ -582,7 +582,7 @@ This is mainly useful for debugging provider serialization and cache behavior.
|
|
|
582
582
|
Fired after an HTTP response is received and before its stream body is consumed. Handlers run in extension load order.
|
|
583
583
|
|
|
584
584
|
```typescript
|
|
585
|
-
|
|
585
|
+
aery.on("after_provider_response", (event, ctx) => {
|
|
586
586
|
// event.status - HTTP status code
|
|
587
587
|
// event.headers - normalized response headers
|
|
588
588
|
if (event.status === 429) {
|
|
@@ -600,7 +600,7 @@ Header availability depends on provider and transport. Providers that abstract H
|
|
|
600
600
|
Fired when the model changes via `/model` command, model cycling (`Ctrl+P`), or session restore.
|
|
601
601
|
|
|
602
602
|
```typescript
|
|
603
|
-
|
|
603
|
+
aery.on("model_select", async (event, ctx) => {
|
|
604
604
|
// event.model - newly selected model
|
|
605
605
|
// event.previousModel - previous model (undefined if first selection)
|
|
606
606
|
// event.source - "set" | "cycle" | "restore"
|
|
@@ -622,7 +622,7 @@ Use this to update UI elements (status bars, footers) or perform model-specific
|
|
|
622
622
|
|
|
623
623
|
Fired after `tool_execution_start`, before the tool executes. **Can block.** Use `isToolCallEventType` to narrow and get typed inputs.
|
|
624
624
|
|
|
625
|
-
Before `tool_call` runs,
|
|
625
|
+
Before `tool_call` runs, Aery waits for previously emitted Agent events to finish draining through `AgentSession`. This means `ctx.sessionManager` is up to date through the current assistant tool-calling message.
|
|
626
626
|
|
|
627
627
|
In the default parallel tool execution mode, sibling tool calls from the same assistant message are preflighted sequentially, then executed concurrently. `tool_call` is not guaranteed to see sibling tool results from that same assistant message in `ctx.sessionManager`.
|
|
628
628
|
|
|
@@ -637,7 +637,7 @@ Behavior guarantees:
|
|
|
637
637
|
```typescript
|
|
638
638
|
import { isToolCallEventType } from "@eminent337/aery";
|
|
639
639
|
|
|
640
|
-
|
|
640
|
+
aery.on("tool_call", async (event, ctx) => {
|
|
641
641
|
// event.toolName - "bash", "read", "write", "edit", etc.
|
|
642
642
|
// event.toolCallId
|
|
643
643
|
// event.input - tool parameters (mutable)
|
|
@@ -674,7 +674,7 @@ Use `isToolCallEventType` with explicit type parameters:
|
|
|
674
674
|
import { isToolCallEventType } from "@eminent337/aery";
|
|
675
675
|
import type { MyToolInput } from "my-extension";
|
|
676
676
|
|
|
677
|
-
|
|
677
|
+
aery.on("tool_call", (event) => {
|
|
678
678
|
if (isToolCallEventType<"my_tool", MyToolInput>("my_tool", event)) {
|
|
679
679
|
event.input.action; // typed
|
|
680
680
|
}
|
|
@@ -695,7 +695,7 @@ Use `ctx.signal` for nested async work inside the handler. This lets Esc cancel
|
|
|
695
695
|
```typescript
|
|
696
696
|
import { isBashToolResult } from "@eminent337/aery";
|
|
697
697
|
|
|
698
|
-
|
|
698
|
+
aery.on("tool_result", async (event, ctx) => {
|
|
699
699
|
// event.toolName, event.toolCallId, event.input
|
|
700
700
|
// event.content, event.details, event.isError
|
|
701
701
|
|
|
@@ -723,7 +723,7 @@ Fired when user executes `!` or `!!` commands. **Can intercept.**
|
|
|
723
723
|
```typescript
|
|
724
724
|
import { createLocalBashOperations } from "@eminent337/aery";
|
|
725
725
|
|
|
726
|
-
|
|
726
|
+
aery.on("user_bash", (event, ctx) => {
|
|
727
727
|
// event.command - the bash command
|
|
728
728
|
// event.excludeFromContext - true if !! prefix
|
|
729
729
|
// event.cwd - working directory
|
|
@@ -731,7 +731,7 @@ pi.on("user_bash", (event, ctx) => {
|
|
|
731
731
|
// Option 1: Provide custom operations (e.g., SSH)
|
|
732
732
|
return { operations: remoteBashOps };
|
|
733
733
|
|
|
734
|
-
// Option 2: Wrap
|
|
734
|
+
// Option 2: Wrap Aery's built-in local bash backend
|
|
735
735
|
const local = createLocalBashOperations();
|
|
736
736
|
return {
|
|
737
737
|
operations: {
|
|
@@ -760,7 +760,7 @@ Fired when user input is received, after extension commands are checked but befo
|
|
|
760
760
|
5. Agent processing begins (`before_agent_start`, etc.)
|
|
761
761
|
|
|
762
762
|
```typescript
|
|
763
|
-
|
|
763
|
+
aery.on("input", async (event, ctx) => {
|
|
764
764
|
// event.text - raw input (before skill/template expansion)
|
|
765
765
|
// event.images - attached images, if any
|
|
766
766
|
// event.source - "interactive" (typed), "rpc" (API), or "extension" (via sendUserMessage)
|
|
@@ -836,10 +836,10 @@ Use this for abort-aware nested work started by extension handlers, for example:
|
|
|
836
836
|
- file or process helpers that accept `AbortSignal`
|
|
837
837
|
|
|
838
838
|
`ctx.signal` is typically defined during active turn events such as `tool_call`, `tool_result`, `message_update`, and `turn_end`.
|
|
839
|
-
It is usually `undefined` in idle or non-turn contexts such as session events, extension commands, and shortcuts fired while
|
|
839
|
+
It is usually `undefined` in idle or non-turn contexts such as session events, extension commands, and shortcuts fired while Aery is idle.
|
|
840
840
|
|
|
841
841
|
```typescript
|
|
842
|
-
|
|
842
|
+
aery.on("tool_result", async (event, ctx) => {
|
|
843
843
|
const response = await fetch("https://example.com/api", {
|
|
844
844
|
method: "POST",
|
|
845
845
|
body: JSON.stringify(event),
|
|
@@ -857,7 +857,7 @@ Control flow helpers.
|
|
|
857
857
|
|
|
858
858
|
### ctx.shutdown()
|
|
859
859
|
|
|
860
|
-
Request a graceful shutdown of
|
|
860
|
+
Request a graceful shutdown of Aery.
|
|
861
861
|
|
|
862
862
|
- **Interactive mode:** Deferred until the agent becomes idle (after processing all queued steering and follow-up messages).
|
|
863
863
|
- **RPC mode:** Deferred until the next idle state (after completing the current command response, when waiting for the next command).
|
|
@@ -866,7 +866,7 @@ Request a graceful shutdown of pi.
|
|
|
866
866
|
Emits `session_shutdown` event to all extensions before exiting. Available in all contexts (event handlers, tools, commands, shortcuts).
|
|
867
867
|
|
|
868
868
|
```typescript
|
|
869
|
-
|
|
869
|
+
aery.on("tool_call", (event, ctx) => {
|
|
870
870
|
if (isFatal(event.input)) {
|
|
871
871
|
ctx.shutdown();
|
|
872
872
|
}
|
|
@@ -905,7 +905,7 @@ ctx.compact({
|
|
|
905
905
|
Returns the current effective system prompt. This includes any modifications made by `before_agent_start` handlers for the current turn.
|
|
906
906
|
|
|
907
907
|
```typescript
|
|
908
|
-
|
|
908
|
+
aery.on("before_agent_start", (event, ctx) => {
|
|
909
909
|
const prompt = ctx.getSystemPrompt();
|
|
910
910
|
console.log(`System prompt length: ${prompt.length}`);
|
|
911
911
|
});
|
|
@@ -920,7 +920,7 @@ Command handlers receive `ExtensionCommandContext`, which extends `ExtensionCont
|
|
|
920
920
|
Wait for the agent to finish streaming:
|
|
921
921
|
|
|
922
922
|
```typescript
|
|
923
|
-
|
|
923
|
+
aery.registerCommand("my-cmd", {
|
|
924
924
|
handler: async (args, ctx) => {
|
|
925
925
|
await ctx.waitForIdle();
|
|
926
926
|
// Agent is now idle, safe to modify session
|
|
@@ -1004,7 +1004,7 @@ To discover available sessions, use the static `SessionManager.list()` or `Sessi
|
|
|
1004
1004
|
```typescript
|
|
1005
1005
|
import { SessionManager } from "@eminent337/aery";
|
|
1006
1006
|
|
|
1007
|
-
|
|
1007
|
+
aery.registerCommand("switch", {
|
|
1008
1008
|
description: "Switch to another session",
|
|
1009
1009
|
handler: async (args, ctx) => {
|
|
1010
1010
|
const sessions = await SessionManager.list(ctx.cwd);
|
|
@@ -1025,7 +1025,7 @@ pi.registerCommand("switch", {
|
|
|
1025
1025
|
Run the same reload flow as `/reload`.
|
|
1026
1026
|
|
|
1027
1027
|
```typescript
|
|
1028
|
-
|
|
1028
|
+
aery.registerCommand("reload-runtime", {
|
|
1029
1029
|
description: "Reload extensions, skills, prompts, and themes",
|
|
1030
1030
|
handler: async (_args, ctx) => {
|
|
1031
1031
|
await ctx.reload();
|
|
@@ -1052,8 +1052,8 @@ Example tool the LLM can call to trigger reload:
|
|
|
1052
1052
|
import type { ExtensionAPI } from "@eminent337/aery";
|
|
1053
1053
|
import { Type } from "@sinclair/typebox";
|
|
1054
1054
|
|
|
1055
|
-
export default function (
|
|
1056
|
-
|
|
1055
|
+
export default function (aery: ExtensionAPI) {
|
|
1056
|
+
aery.registerCommand("reload-runtime", {
|
|
1057
1057
|
description: "Reload extensions, skills, prompts, and themes",
|
|
1058
1058
|
handler: async (_args, ctx) => {
|
|
1059
1059
|
await ctx.reload();
|
|
@@ -1061,13 +1061,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
1061
1061
|
},
|
|
1062
1062
|
});
|
|
1063
1063
|
|
|
1064
|
-
|
|
1064
|
+
aery.registerTool({
|
|
1065
1065
|
name: "reload_runtime",
|
|
1066
1066
|
label: "Reload Runtime",
|
|
1067
1067
|
description: "Reload extensions, skills, prompts, and themes",
|
|
1068
1068
|
parameters: Type.Object({}),
|
|
1069
1069
|
async execute() {
|
|
1070
|
-
|
|
1070
|
+
aery.sendUserMessage("/reload-runtime", { deliverAs: "followUp" });
|
|
1071
1071
|
return {
|
|
1072
1072
|
content: [{ type: "text", text: "Queued /reload-runtime as a follow-up command." }],
|
|
1073
1073
|
};
|
|
@@ -1078,17 +1078,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
1078
1078
|
|
|
1079
1079
|
## ExtensionAPI Methods
|
|
1080
1080
|
|
|
1081
|
-
###
|
|
1081
|
+
### aery.on(event, handler)
|
|
1082
1082
|
|
|
1083
1083
|
Subscribe to events. See [Events](#events) for event types and return values.
|
|
1084
1084
|
|
|
1085
|
-
###
|
|
1085
|
+
### aery.registerTool(definition)
|
|
1086
1086
|
|
|
1087
1087
|
Register a custom tool callable by the LLM. See [Custom Tools](#custom-tools) for full details.
|
|
1088
1088
|
|
|
1089
|
-
`
|
|
1089
|
+
`aery.registerTool()` works both during extension load and after startup. You can call it inside `session_start`, command handlers, or other event handlers. New tools are refreshed immediately in the same session, so they appear in `aery.getAllTools()` and are callable by the LLM without `/reload`.
|
|
1090
1090
|
|
|
1091
|
-
Use `
|
|
1091
|
+
Use `aery.setActiveTools()` to enable or disable tools (including dynamically added tools) at runtime.
|
|
1092
1092
|
|
|
1093
1093
|
Use `promptSnippet` to opt a custom tool into a one-line entry in `Available tools`, and `promptGuidelines` to append tool-specific bullets to the default `Guidelines` section when the tool is active.
|
|
1094
1094
|
|
|
@@ -1098,7 +1098,7 @@ See [dynamic-tools.ts](../examples/extensions/dynamic-tools.ts) for a full examp
|
|
|
1098
1098
|
import { Type } from "@sinclair/typebox";
|
|
1099
1099
|
import { StringEnum } from "@eminent337/aery-ai";
|
|
1100
1100
|
|
|
1101
|
-
|
|
1101
|
+
aery.registerTool({
|
|
1102
1102
|
name: "my_tool",
|
|
1103
1103
|
label: "My Tool",
|
|
1104
1104
|
description: "What this tool does",
|
|
@@ -1131,12 +1131,12 @@ pi.registerTool({
|
|
|
1131
1131
|
});
|
|
1132
1132
|
```
|
|
1133
1133
|
|
|
1134
|
-
###
|
|
1134
|
+
### aery.sendMessage(message, options?)
|
|
1135
1135
|
|
|
1136
1136
|
Inject a custom message into the session.
|
|
1137
1137
|
|
|
1138
1138
|
```typescript
|
|
1139
|
-
|
|
1139
|
+
aery.sendMessage({
|
|
1140
1140
|
customType: "my-extension",
|
|
1141
1141
|
content: "Message text",
|
|
1142
1142
|
display: true,
|
|
@@ -1154,23 +1154,23 @@ pi.sendMessage({
|
|
|
1154
1154
|
- `"nextTurn"` - Queued for next user prompt. Does not interrupt or trigger anything.
|
|
1155
1155
|
- `triggerTurn: true` - If agent is idle, trigger an LLM response immediately. Only applies to `"steer"` and `"followUp"` modes (ignored for `"nextTurn"`).
|
|
1156
1156
|
|
|
1157
|
-
###
|
|
1157
|
+
### aery.sendUserMessage(content, options?)
|
|
1158
1158
|
|
|
1159
1159
|
Send a user message to the agent. Unlike `sendMessage()` which sends custom messages, this sends an actual user message that appears as if typed by the user. Always triggers a turn.
|
|
1160
1160
|
|
|
1161
1161
|
```typescript
|
|
1162
1162
|
// Simple text message
|
|
1163
|
-
|
|
1163
|
+
aery.sendUserMessage("What is 2+2?");
|
|
1164
1164
|
|
|
1165
1165
|
// With content array (text + images)
|
|
1166
|
-
|
|
1166
|
+
aery.sendUserMessage([
|
|
1167
1167
|
{ type: "text", text: "Describe this image:" },
|
|
1168
1168
|
{ type: "image", source: { type: "base64", mediaType: "image/png", data: "..." } },
|
|
1169
1169
|
]);
|
|
1170
1170
|
|
|
1171
1171
|
// During streaming - must specify delivery mode
|
|
1172
|
-
|
|
1173
|
-
|
|
1172
|
+
aery.sendUserMessage("Focus on error handling", { deliverAs: "steer" });
|
|
1173
|
+
aery.sendUserMessage("And then summarize", { deliverAs: "followUp" });
|
|
1174
1174
|
```
|
|
1175
1175
|
|
|
1176
1176
|
**Options:**
|
|
@@ -1182,15 +1182,15 @@ When not streaming, the message is sent immediately and triggers a new turn. Whe
|
|
|
1182
1182
|
|
|
1183
1183
|
See [send-user-message.ts](../examples/extensions/send-user-message.ts) for a complete example.
|
|
1184
1184
|
|
|
1185
|
-
###
|
|
1185
|
+
### aery.appendEntry(customType, data?)
|
|
1186
1186
|
|
|
1187
1187
|
Persist extension state (does NOT participate in LLM context).
|
|
1188
1188
|
|
|
1189
1189
|
```typescript
|
|
1190
|
-
|
|
1190
|
+
aery.appendEntry("my-state", { count: 42 });
|
|
1191
1191
|
|
|
1192
1192
|
// Restore on reload
|
|
1193
|
-
|
|
1193
|
+
aery.on("session_start", async (_event, ctx) => {
|
|
1194
1194
|
for (const entry of ctx.sessionManager.getEntries()) {
|
|
1195
1195
|
if (entry.type === "custom" && entry.customType === "my-state") {
|
|
1196
1196
|
// Reconstruct from entry.data
|
|
@@ -1199,35 +1199,35 @@ pi.on("session_start", async (_event, ctx) => {
|
|
|
1199
1199
|
});
|
|
1200
1200
|
```
|
|
1201
1201
|
|
|
1202
|
-
###
|
|
1202
|
+
### aery.setSessionName(name)
|
|
1203
1203
|
|
|
1204
1204
|
Set the session display name (shown in session selector instead of first message).
|
|
1205
1205
|
|
|
1206
1206
|
```typescript
|
|
1207
|
-
|
|
1207
|
+
aery.setSessionName("Refactor auth module");
|
|
1208
1208
|
```
|
|
1209
1209
|
|
|
1210
|
-
###
|
|
1210
|
+
### aery.getSessionName()
|
|
1211
1211
|
|
|
1212
1212
|
Get the current session name, if set.
|
|
1213
1213
|
|
|
1214
1214
|
```typescript
|
|
1215
|
-
const name =
|
|
1215
|
+
const name = aery.getSessionName();
|
|
1216
1216
|
if (name) {
|
|
1217
1217
|
console.log(`Session: ${name}`);
|
|
1218
1218
|
}
|
|
1219
1219
|
```
|
|
1220
1220
|
|
|
1221
|
-
###
|
|
1221
|
+
### aery.setLabel(entryId, label)
|
|
1222
1222
|
|
|
1223
1223
|
Set or clear a label on an entry. Labels are user-defined markers for bookmarking and navigation (shown in `/tree` selector).
|
|
1224
1224
|
|
|
1225
1225
|
```typescript
|
|
1226
1226
|
// Set a label
|
|
1227
|
-
|
|
1227
|
+
aery.setLabel(entryId, "checkpoint-before-refactor");
|
|
1228
1228
|
|
|
1229
1229
|
// Clear a label
|
|
1230
|
-
|
|
1230
|
+
aery.setLabel(entryId, undefined);
|
|
1231
1231
|
|
|
1232
1232
|
// Read labels via sessionManager
|
|
1233
1233
|
const label = ctx.sessionManager.getLabel(entryId);
|
|
@@ -1235,14 +1235,14 @@ const label = ctx.sessionManager.getLabel(entryId);
|
|
|
1235
1235
|
|
|
1236
1236
|
Labels persist in the session and survive restarts. Use them to mark important points (turns, checkpoints) in the conversation tree.
|
|
1237
1237
|
|
|
1238
|
-
###
|
|
1238
|
+
### aery.registerCommand(name, options)
|
|
1239
1239
|
|
|
1240
1240
|
Register a command.
|
|
1241
1241
|
|
|
1242
|
-
If multiple extensions register the same command name,
|
|
1242
|
+
If multiple extensions register the same command name, Aery keeps them all and assigns numeric invocation suffixes in load order, for example `/review:1` and `/review:2`.
|
|
1243
1243
|
|
|
1244
1244
|
```typescript
|
|
1245
|
-
|
|
1245
|
+
aery.registerCommand("stats", {
|
|
1246
1246
|
description: "Show session statistics",
|
|
1247
1247
|
handler: async (args, ctx) => {
|
|
1248
1248
|
const count = ctx.sessionManager.getEntries().length;
|
|
@@ -1256,7 +1256,7 @@ Optional: add argument auto-completion for `/command ...`:
|
|
|
1256
1256
|
```typescript
|
|
1257
1257
|
import type { AutocompleteItem } from "@eminent337/aery-tui";
|
|
1258
1258
|
|
|
1259
|
-
|
|
1259
|
+
aery.registerCommand("deploy", {
|
|
1260
1260
|
description: "Deploy to an environment",
|
|
1261
1261
|
getArgumentCompletions: (prefix: string): AutocompleteItem[] | null => {
|
|
1262
1262
|
const envs = ["dev", "staging", "prod"];
|
|
@@ -1270,13 +1270,13 @@ pi.registerCommand("deploy", {
|
|
|
1270
1270
|
});
|
|
1271
1271
|
```
|
|
1272
1272
|
|
|
1273
|
-
###
|
|
1273
|
+
### aery.getCommands()
|
|
1274
1274
|
|
|
1275
1275
|
Get the slash commands available for invocation via `prompt` in the current session. Includes extension commands, prompt templates, and skill commands.
|
|
1276
1276
|
The list matches the RPC `get_commands` ordering: extensions first, then templates, then skills.
|
|
1277
1277
|
|
|
1278
1278
|
```typescript
|
|
1279
|
-
const commands =
|
|
1279
|
+
const commands = aery.getCommands();
|
|
1280
1280
|
const bySource = commands.filter((command) => command.source === "extension");
|
|
1281
1281
|
const userScoped = commands.filter((command) => command.sourceInfo.scope === "user");
|
|
1282
1282
|
```
|
|
@@ -1303,16 +1303,16 @@ Use `sourceInfo` as the canonical provenance field. Do not infer ownership from
|
|
|
1303
1303
|
Built-in interactive commands (like `/model` and `/settings`) are not included here. They are handled only in interactive
|
|
1304
1304
|
mode and would not execute if sent via `prompt`.
|
|
1305
1305
|
|
|
1306
|
-
###
|
|
1306
|
+
### aery.registerMessageRenderer(customType, renderer)
|
|
1307
1307
|
|
|
1308
1308
|
Register a custom TUI renderer for messages with your `customType`. See [Custom UI](#custom-ui).
|
|
1309
1309
|
|
|
1310
|
-
###
|
|
1310
|
+
### aery.registerShortcut(shortcut, options)
|
|
1311
1311
|
|
|
1312
1312
|
Register a keyboard shortcut. See [keybindings.md](keybindings.md) for the shortcut format and built-in keybindings.
|
|
1313
1313
|
|
|
1314
1314
|
```typescript
|
|
1315
|
-
|
|
1315
|
+
aery.registerShortcut("ctrl+shift+p", {
|
|
1316
1316
|
description: "Toggle plan mode",
|
|
1317
1317
|
handler: async (ctx) => {
|
|
1318
1318
|
ctx.ui.notify("Toggled!");
|
|
@@ -1320,39 +1320,39 @@ pi.registerShortcut("ctrl+shift+p", {
|
|
|
1320
1320
|
});
|
|
1321
1321
|
```
|
|
1322
1322
|
|
|
1323
|
-
###
|
|
1323
|
+
### aery.registerFlag(name, options)
|
|
1324
1324
|
|
|
1325
1325
|
Register a CLI flag.
|
|
1326
1326
|
|
|
1327
1327
|
```typescript
|
|
1328
|
-
|
|
1328
|
+
aery.registerFlag("plan", {
|
|
1329
1329
|
description: "Start in plan mode",
|
|
1330
1330
|
type: "boolean",
|
|
1331
1331
|
default: false,
|
|
1332
1332
|
});
|
|
1333
1333
|
|
|
1334
1334
|
// Check value
|
|
1335
|
-
if (
|
|
1335
|
+
if (aery.getFlag("--plan")) {
|
|
1336
1336
|
// Plan mode enabled
|
|
1337
1337
|
}
|
|
1338
1338
|
```
|
|
1339
1339
|
|
|
1340
|
-
###
|
|
1340
|
+
### aery.exec(command, args, options?)
|
|
1341
1341
|
|
|
1342
1342
|
Execute a shell command.
|
|
1343
1343
|
|
|
1344
1344
|
```typescript
|
|
1345
|
-
const result = await
|
|
1345
|
+
const result = await aery.exec("git", ["status"], { signal, timeout: 5000 });
|
|
1346
1346
|
// result.stdout, result.stderr, result.code, result.killed
|
|
1347
1347
|
```
|
|
1348
1348
|
|
|
1349
|
-
###
|
|
1349
|
+
### aery.getActiveTools() / aery.getAllTools() / aery.setActiveTools(names)
|
|
1350
1350
|
|
|
1351
1351
|
Manage active tools. This works for both built-in tools and dynamically registered tools.
|
|
1352
1352
|
|
|
1353
1353
|
```typescript
|
|
1354
|
-
const active =
|
|
1355
|
-
const all =
|
|
1354
|
+
const active = aery.getActiveTools();
|
|
1355
|
+
const all = aery.getAllTools();
|
|
1356
1356
|
// [{
|
|
1357
1357
|
// name: "read",
|
|
1358
1358
|
// description: "Read file contents...",
|
|
@@ -1362,59 +1362,59 @@ const all = pi.getAllTools();
|
|
|
1362
1362
|
const names = all.map(t => t.name);
|
|
1363
1363
|
const builtinTools = all.filter((t) => t.sourceInfo.source === "builtin");
|
|
1364
1364
|
const extensionTools = all.filter((t) => t.sourceInfo.source !== "builtin" && t.sourceInfo.source !== "sdk");
|
|
1365
|
-
|
|
1365
|
+
aery.setActiveTools(["read", "bash"]); // Switch to read-only
|
|
1366
1366
|
```
|
|
1367
1367
|
|
|
1368
|
-
`
|
|
1368
|
+
`aery.getAllTools()` returns `name`, `description`, `parameters`, and `sourceInfo`.
|
|
1369
1369
|
|
|
1370
1370
|
Typical `sourceInfo.source` values:
|
|
1371
1371
|
- `builtin` for built-in tools
|
|
1372
1372
|
- `sdk` for tools passed via `createAgentSession({ customTools })`
|
|
1373
1373
|
- extension source metadata for tools registered by extensions
|
|
1374
1374
|
|
|
1375
|
-
###
|
|
1375
|
+
### aery.setModel(model)
|
|
1376
1376
|
|
|
1377
1377
|
Set the current model. Returns `false` if no API key is available for the model. See [models.md](models.md) for configuring custom models.
|
|
1378
1378
|
|
|
1379
1379
|
```typescript
|
|
1380
1380
|
const model = ctx.modelRegistry.find("anthropic", "claude-sonnet-4-5");
|
|
1381
1381
|
if (model) {
|
|
1382
|
-
const success = await
|
|
1382
|
+
const success = await aery.setModel(model);
|
|
1383
1383
|
if (!success) {
|
|
1384
1384
|
ctx.ui.notify("No API key for this model", "error");
|
|
1385
1385
|
}
|
|
1386
1386
|
}
|
|
1387
1387
|
```
|
|
1388
1388
|
|
|
1389
|
-
###
|
|
1389
|
+
### aery.getThinkingLevel() / aery.setThinkingLevel(level)
|
|
1390
1390
|
|
|
1391
1391
|
Get or set the thinking level. Level is clamped to model capabilities (non-reasoning models always use "off").
|
|
1392
1392
|
|
|
1393
1393
|
```typescript
|
|
1394
|
-
const current =
|
|
1395
|
-
|
|
1394
|
+
const current = aery.getThinkingLevel(); // "off" | "minimal" | "low" | "medium" | "high" | "xhigh"
|
|
1395
|
+
aery.setThinkingLevel("high");
|
|
1396
1396
|
```
|
|
1397
1397
|
|
|
1398
|
-
###
|
|
1398
|
+
### aery.events
|
|
1399
1399
|
|
|
1400
1400
|
Shared event bus for communication between extensions:
|
|
1401
1401
|
|
|
1402
1402
|
```typescript
|
|
1403
|
-
|
|
1404
|
-
|
|
1403
|
+
aery.events.on("my:event", (data) => { ... });
|
|
1404
|
+
aery.events.emit("my:event", { ... });
|
|
1405
1405
|
```
|
|
1406
1406
|
|
|
1407
|
-
###
|
|
1407
|
+
### aery.registerProvider(name, config)
|
|
1408
1408
|
|
|
1409
1409
|
Register or override a model provider dynamically. Useful for proxies, custom endpoints, or team-wide model configurations.
|
|
1410
1410
|
|
|
1411
1411
|
Calls made during the extension factory function are queued and applied once the runner initialises. Calls made after that — for example from a command handler following a user setup flow — take effect immediately without requiring a `/reload`.
|
|
1412
1412
|
|
|
1413
|
-
If you need to discover models from a remote endpoint, prefer an async extension factory over deferring the fetch to `session_start`.
|
|
1413
|
+
If you need to discover models from a remote endpoint, prefer an async extension factory over deferring the fetch to `session_start`. Aery waits for the factory before startup continues, so the registered models are available immediately, including to `aery --list-models`.
|
|
1414
1414
|
|
|
1415
1415
|
```typescript
|
|
1416
1416
|
// Register a new provider with custom models
|
|
1417
|
-
|
|
1417
|
+
aery.registerProvider("my-proxy", {
|
|
1418
1418
|
baseUrl: "https://proxy.example.com",
|
|
1419
1419
|
apiKey: "PROXY_API_KEY", // env var name or literal
|
|
1420
1420
|
api: "anthropic-messages",
|
|
@@ -1432,12 +1432,12 @@ pi.registerProvider("my-proxy", {
|
|
|
1432
1432
|
});
|
|
1433
1433
|
|
|
1434
1434
|
// Override baseUrl for an existing provider (keeps all models)
|
|
1435
|
-
|
|
1435
|
+
aery.registerProvider("anthropic", {
|
|
1436
1436
|
baseUrl: "https://proxy.example.com"
|
|
1437
1437
|
});
|
|
1438
1438
|
|
|
1439
1439
|
// Register provider with OAuth support for /login
|
|
1440
|
-
|
|
1440
|
+
aery.registerProvider("corporate-ai", {
|
|
1441
1441
|
baseUrl: "https://ai.corp.com",
|
|
1442
1442
|
api: "openai-responses",
|
|
1443
1443
|
models: [...],
|
|
@@ -1472,17 +1472,17 @@ pi.registerProvider("corporate-ai", {
|
|
|
1472
1472
|
|
|
1473
1473
|
See [custom-provider.md](custom-provider.md) for advanced topics: custom streaming APIs, OAuth details, model definition reference.
|
|
1474
1474
|
|
|
1475
|
-
###
|
|
1475
|
+
### aery.unregisterProvider(name)
|
|
1476
1476
|
|
|
1477
1477
|
Remove a previously registered provider and its models. Built-in models that were overridden by the provider are restored. Has no effect if the provider was not registered.
|
|
1478
1478
|
|
|
1479
1479
|
Like `registerProvider`, this takes effect immediately when called after the initial load phase, so a `/reload` is not required.
|
|
1480
1480
|
|
|
1481
1481
|
```typescript
|
|
1482
|
-
|
|
1482
|
+
aery.registerCommand("my-setup-teardown", {
|
|
1483
1483
|
description: "Remove the custom proxy provider",
|
|
1484
1484
|
handler: async (_args, _ctx) => {
|
|
1485
|
-
|
|
1485
|
+
aery.unregisterProvider("my-proxy");
|
|
1486
1486
|
},
|
|
1487
1487
|
});
|
|
1488
1488
|
```
|
|
@@ -1492,11 +1492,11 @@ pi.registerCommand("my-setup-teardown", {
|
|
|
1492
1492
|
Extensions with state should store it in tool result `details` for proper branching support:
|
|
1493
1493
|
|
|
1494
1494
|
```typescript
|
|
1495
|
-
export default function (
|
|
1495
|
+
export default function (aery: ExtensionAPI) {
|
|
1496
1496
|
let items: string[] = [];
|
|
1497
1497
|
|
|
1498
1498
|
// Reconstruct state from session
|
|
1499
|
-
|
|
1499
|
+
aery.on("session_start", async (_event, ctx) => {
|
|
1500
1500
|
items = [];
|
|
1501
1501
|
for (const entry of ctx.sessionManager.getBranch()) {
|
|
1502
1502
|
if (entry.type === "message" && entry.message.role === "toolResult") {
|
|
@@ -1507,7 +1507,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1507
1507
|
}
|
|
1508
1508
|
});
|
|
1509
1509
|
|
|
1510
|
-
|
|
1510
|
+
aery.registerTool({
|
|
1511
1511
|
name: "my_tool",
|
|
1512
1512
|
// ...
|
|
1513
1513
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
@@ -1523,11 +1523,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
1523
1523
|
|
|
1524
1524
|
## Custom Tools
|
|
1525
1525
|
|
|
1526
|
-
Register tools the LLM can call via `
|
|
1526
|
+
Register tools the LLM can call via `aery.registerTool()`. Tools appear in the system prompt and can have custom rendering.
|
|
1527
1527
|
|
|
1528
1528
|
Use `promptSnippet` for a short one-line entry in the `Available tools` section in the default system prompt. If omitted, custom tools are left out of that section.
|
|
1529
1529
|
|
|
1530
|
-
Use `promptGuidelines` to add tool-specific bullets to the default system prompt `Guidelines` section. These bullets are included only while the tool is active (for example, after `
|
|
1530
|
+
Use `promptGuidelines` to add tool-specific bullets to the default system prompt `Guidelines` section. These bullets are included only while the tool is active (for example, after `aery.setActiveTools([...])`).
|
|
1531
1531
|
|
|
1532
1532
|
Note: Some models are idiots and include the @ prefix in tool path arguments. Built-in tools strip a leading @ before resolving paths. If your custom tool accepts a path, normalize a leading @ as well.
|
|
1533
1533
|
|
|
@@ -1568,7 +1568,7 @@ import { Type } from "@sinclair/typebox";
|
|
|
1568
1568
|
import { StringEnum } from "@eminent337/aery-ai";
|
|
1569
1569
|
import { Text } from "@eminent337/aery-tui";
|
|
1570
1570
|
|
|
1571
|
-
|
|
1571
|
+
aery.registerTool({
|
|
1572
1572
|
name: "my_tool",
|
|
1573
1573
|
label: "My Tool",
|
|
1574
1574
|
description: "What this tool does (shown to LLM)",
|
|
@@ -1601,8 +1601,8 @@ pi.registerTool({
|
|
|
1601
1601
|
details: { progress: 50 },
|
|
1602
1602
|
});
|
|
1603
1603
|
|
|
1604
|
-
// Run commands via
|
|
1605
|
-
const result = await
|
|
1604
|
+
// Run commands via aery.exec (captured from extension closure)
|
|
1605
|
+
const result = await aery.exec("some-command", [], { signal });
|
|
1606
1606
|
|
|
1607
1607
|
// Return result
|
|
1608
1608
|
return {
|
|
@@ -1631,12 +1631,12 @@ async execute(toolCallId, params) {
|
|
|
1631
1631
|
|
|
1632
1632
|
**Important:** Use `StringEnum` from `@eminent337/aery-ai` for string enums. `Type.Union`/`Type.Literal` doesn't work with Google's API.
|
|
1633
1633
|
|
|
1634
|
-
**Argument preparation:** `prepareArguments(args)` is optional. If defined, it runs before schema validation and before `execute()`. Use it to mimic an older accepted input shape when
|
|
1634
|
+
**Argument preparation:** `prepareArguments(args)` is optional. If defined, it runs before schema validation and before `execute()`. Use it to mimic an older accepted input shape when Aery resumes an older session whose stored tool call arguments no longer match the current schema. Return the object you want validated against `parameters`. Keep the public schema strict. Do not add deprecated compatibility fields to `parameters` just to keep old resumed sessions working.
|
|
1635
1635
|
|
|
1636
1636
|
Example: an older session may contain an `edit` tool call with top-level `oldText` and `newText`, while the current schema only accepts `edits: [{ oldText, newText }]`.
|
|
1637
1637
|
|
|
1638
1638
|
```typescript
|
|
1639
|
-
|
|
1639
|
+
aery.registerTool({
|
|
1640
1640
|
name: "edit",
|
|
1641
1641
|
label: "Edit",
|
|
1642
1642
|
description: "Edit a single file using exact text replacement",
|
|
@@ -1684,13 +1684,13 @@ Extensions can override built-in tools (`read`, `bash`, `edit`, `write`, `grep`,
|
|
|
1684
1684
|
|
|
1685
1685
|
```bash
|
|
1686
1686
|
# Extension's read tool replaces built-in read
|
|
1687
|
-
|
|
1687
|
+
aery -e ./tool-override.ts
|
|
1688
1688
|
```
|
|
1689
1689
|
|
|
1690
1690
|
Alternatively, use `--no-tools` to start without any built-in tools:
|
|
1691
1691
|
```bash
|
|
1692
1692
|
# No built-in tools, only extension tools
|
|
1693
|
-
|
|
1693
|
+
aery --no-tools -e ./my-extension.ts
|
|
1694
1694
|
```
|
|
1695
1695
|
|
|
1696
1696
|
See [examples/extensions/tool-override.ts](../examples/extensions/tool-override.ts) for a complete example that overrides `read` with logging and access control.
|
|
@@ -1726,7 +1726,7 @@ const remoteRead = createReadTool(cwd, {
|
|
|
1726
1726
|
});
|
|
1727
1727
|
|
|
1728
1728
|
// Register, checking flag at execution time
|
|
1729
|
-
|
|
1729
|
+
aery.registerTool({
|
|
1730
1730
|
...remoteRead,
|
|
1731
1731
|
async execute(id, params, signal, onUpdate, _ctx) {
|
|
1732
1732
|
const ssh = getSshConfig();
|
|
@@ -1741,7 +1741,7 @@ pi.registerTool({
|
|
|
1741
1741
|
|
|
1742
1742
|
**Operations interfaces:** `ReadOperations`, `WriteOperations`, `EditOperations`, `BashOperations`, `LsOperations`, `GrepOperations`, `FindOperations`
|
|
1743
1743
|
|
|
1744
|
-
For `user_bash`, extensions can reuse
|
|
1744
|
+
For `user_bash`, extensions can reuse Aery's local shell backend via `createLocalBashOperations()` instead of reimplementing local process spawning, shell resolution, and process-tree termination.
|
|
1745
1745
|
|
|
1746
1746
|
The bash tool also supports a spawn hook to adjust the command, cwd, or env before execution:
|
|
1747
1747
|
|
|
@@ -1816,14 +1816,14 @@ See [examples/extensions/truncated-tool.ts](../examples/extensions/truncated-too
|
|
|
1816
1816
|
One extension can register multiple tools with shared state:
|
|
1817
1817
|
|
|
1818
1818
|
```typescript
|
|
1819
|
-
export default function (
|
|
1819
|
+
export default function (aery: ExtensionAPI) {
|
|
1820
1820
|
let connection = null;
|
|
1821
1821
|
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1822
|
+
aery.registerTool({ name: "db_connect", ... });
|
|
1823
|
+
aery.registerTool({ name: "db_query", ... });
|
|
1824
|
+
aery.registerTool({ name: "db_close", ... });
|
|
1825
1825
|
|
|
1826
|
-
|
|
1826
|
+
aery.on("session_shutdown", async () => {
|
|
1827
1827
|
connection?.close();
|
|
1828
1828
|
});
|
|
1829
1829
|
}
|
|
@@ -1838,7 +1838,7 @@ By default, tool output is wrapped in a `Box` that handles padding and backgroun
|
|
|
1838
1838
|
Set `renderShell: "self"` when the tool should render its own shell instead of using the default `Box`. This is useful for tools that need complete control over framing or background behavior, for example large previews that must stay visually stable after the tool settles.
|
|
1839
1839
|
|
|
1840
1840
|
```typescript
|
|
1841
|
-
|
|
1841
|
+
aery.registerTool({
|
|
1842
1842
|
name: "my_tool",
|
|
1843
1843
|
label: "My Tool",
|
|
1844
1844
|
description: "Custom shell example",
|
|
@@ -2077,7 +2077,7 @@ ctx.ui.setFooter((tui, theme) => ({
|
|
|
2077
2077
|
ctx.ui.setFooter(undefined); // Restore built-in footer
|
|
2078
2078
|
|
|
2079
2079
|
// Terminal title
|
|
2080
|
-
ctx.ui.setTitle("
|
|
2080
|
+
ctx.ui.setTitle("aery - my-project");
|
|
2081
2081
|
|
|
2082
2082
|
// Editor text
|
|
2083
2083
|
ctx.ui.setEditorText("Prefill text");
|
|
@@ -2190,8 +2190,8 @@ class VimEditor extends CustomEditor {
|
|
|
2190
2190
|
}
|
|
2191
2191
|
}
|
|
2192
2192
|
|
|
2193
|
-
export default function (
|
|
2194
|
-
|
|
2193
|
+
export default function (aery: ExtensionAPI) {
|
|
2194
|
+
aery.on("session_start", (_event, ctx) => {
|
|
2195
2195
|
ctx.ui.setEditorComponent((_tui, theme, keybindings) =>
|
|
2196
2196
|
new VimEditor(theme, keybindings)
|
|
2197
2197
|
);
|
|
@@ -2214,7 +2214,7 @@ Register a custom renderer for messages with your `customType`:
|
|
|
2214
2214
|
```typescript
|
|
2215
2215
|
import { Text } from "@eminent337/aery-tui";
|
|
2216
2216
|
|
|
2217
|
-
|
|
2217
|
+
aery.registerMessageRenderer("my-extension", (message, options, theme) => {
|
|
2218
2218
|
const { expanded } = options;
|
|
2219
2219
|
let text = theme.fg("accent", `[${message.customType}] `);
|
|
2220
2220
|
text += message.content;
|
|
@@ -2227,10 +2227,10 @@ pi.registerMessageRenderer("my-extension", (message, options, theme) => {
|
|
|
2227
2227
|
});
|
|
2228
2228
|
```
|
|
2229
2229
|
|
|
2230
|
-
Messages are sent via `
|
|
2230
|
+
Messages are sent via `aery.sendMessage()`:
|
|
2231
2231
|
|
|
2232
2232
|
```typescript
|
|
2233
|
-
|
|
2233
|
+
aery.sendMessage({
|
|
2234
2234
|
customType: "my-extension", // Matches registerMessageRenderer
|
|
2235
2235
|
content: "Status update",
|
|
2236
2236
|
display: true, // Show in TUI
|
|
@@ -2357,7 +2357,7 @@ All examples in [examples/extensions/](../examples/extensions/).
|
|
|
2357
2357
|
| `custom-provider-gitlab-duo/` | GitLab Duo integration | `registerProvider` with OAuth |
|
|
2358
2358
|
| **Messages & Communication** |||
|
|
2359
2359
|
| `message-renderer.ts` | Custom message rendering | `registerMessageRenderer`, `sendMessage` |
|
|
2360
|
-
| `event-bus.ts` | Inter-extension events | `
|
|
2360
|
+
| `event-bus.ts` | Inter-extension events | `aery.events` |
|
|
2361
2361
|
| **Session Metadata** |||
|
|
2362
2362
|
| `session-name.ts` | Name sessions for selector | `setSessionName`, `getSessionName` |
|
|
2363
2363
|
| `bookmark.ts` | Bookmark entries for /tree | `setLabel` |
|