@economic/agents 0.0.1 → 1.0.1
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 +323 -429
- package/bin/cli.mjs +2 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +561 -0
- package/dist/hono.d.mts +21 -0
- package/dist/hono.mjs +71 -0
- package/dist/index.d.mts +124 -46
- package/dist/index.mjs +280 -117
- package/package.json +29 -17
- package/schema/agent.sql +12 -0
- package/schema/{schema.sql → chat.sql} +1 -5
- package/dist/react.d.mts +0 -25
- package/dist/react.mjs +0 -38
package/README.md
CHANGED
|
@@ -1,62 +1,26 @@
|
|
|
1
1
|
# @economic/agents
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A batteries-included toolkit for building AI agents on Cloudflare Workers. Provides Durable Object base classes for both chat and non-chat agents, with on-demand skill loading, automatic message compaction, conversation management, and audit logging to D1.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
npm install @economic/agents ai @cloudflare/ai-chat agents
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
For React UIs that import `@economic/agents/react`, also install `react`. It is an **optional** peer of the package so Workers-only installs are not required to add React. The hook still needs `agents`, `ai`, and `@cloudflare/ai-chat` at runtime (same as the Cloudflare SDK imports it wraps).
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Overview
|
|
14
|
-
|
|
15
|
-
`@economic/agents` provides:
|
|
16
|
-
|
|
17
|
-
- **`AIChatAgent`** — an abstract Cloudflare Durable Object base class. Implement `onChatMessage`, call `this.buildLLMParams()`, and pass the result to `streamText` from the AI SDK.
|
|
18
|
-
- **`guard`** — optional TypeScript 5+ method decorator for `onChatMessage`. Runs your function with `options.body`; return a `Response` to short-circuit (e.g. auth), or nothing to continue.
|
|
19
|
-
- **`buildLLMParams`** — the standalone version of the above, for use outside of `AIChatAgent` or in custom agent implementations.
|
|
20
|
-
- **`useAIChatAgent`** (subpath `@economic/agents/react`) — React hook that wraps `useAgent` (`agents/react`) and `useAgentChat` (`@cloudflare/ai-chat/react`). Connection status is **callback-only** (`onConnectionStatusChange`). On WebSocket close codes **`>= 3000`**, the hook calls `agent.close()` to stop reconnection, then forwards `onClose` (use `event.reason` for the server message). Pass-through **`onOpen`**, **`onClose`**, **`onError`** are supported.
|
|
5
|
+
For chat agents, extend `ChatAgentHarness` (recommended) or `ChatAgent` (lower-level). For headless agents, extend `Agent`.
|
|
21
6
|
|
|
22
|
-
|
|
7
|
+
For React integration, see [`@economic/agents-react`](../react/README.md).
|
|
23
8
|
|
|
24
|
-
|
|
9
|
+
## Install
|
|
25
10
|
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
import { useState } from "react";
|
|
29
|
-
|
|
30
|
-
const [connectionStatus, setConnectionStatus] = useState<AgentConnectionStatus>("connecting");
|
|
31
|
-
|
|
32
|
-
const { agent, chat } = useAIChatAgent({
|
|
33
|
-
agent: "MyAgent",
|
|
34
|
-
host: "localhost:8787",
|
|
35
|
-
chatId: "session-id",
|
|
36
|
-
toolContext: {},
|
|
37
|
-
connectionParams: { userId: "…" },
|
|
38
|
-
onConnectionStatusChange: setConnectionStatus,
|
|
39
|
-
onOpen: (event) => {},
|
|
40
|
-
onClose: (event) => {},
|
|
41
|
-
onError: (event) => {},
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const { messages, sendMessage, status, stop } = chat;
|
|
11
|
+
```sh
|
|
12
|
+
npm install @economic/agents @cloudflare/ai-chat ai agents
|
|
45
13
|
```
|
|
46
14
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
---
|
|
15
|
+
## Quick Start
|
|
50
16
|
|
|
51
|
-
|
|
17
|
+
### Server
|
|
52
18
|
|
|
53
19
|
```typescript
|
|
54
|
-
import { streamText } from "ai";
|
|
55
20
|
import { openai } from "@ai-sdk/openai";
|
|
56
21
|
import { tool } from "ai";
|
|
57
22
|
import { z } from "zod";
|
|
58
|
-
import {
|
|
59
|
-
import type { Skill } from "@economic/agents";
|
|
23
|
+
import { ChatAgentHarness, type AgentToolContext, type Skill } from "@economic/agents";
|
|
60
24
|
|
|
61
25
|
const searchSkill: Skill = {
|
|
62
26
|
name: "search",
|
|
@@ -71,32 +35,28 @@ const searchSkill: Skill = {
|
|
|
71
35
|
},
|
|
72
36
|
};
|
|
73
37
|
|
|
74
|
-
export class MyAgent extends
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
async onChatMessage(onFinish, options) {
|
|
79
|
-
const params = await this.buildLLMParams({
|
|
80
|
-
options,
|
|
81
|
-
onFinish,
|
|
82
|
-
model: openai("gpt-4o"),
|
|
83
|
-
system: "You are a helpful assistant.",
|
|
84
|
-
skills: [searchSkill],
|
|
85
|
-
});
|
|
86
|
-
return streamText(params).toUIMessageStreamResponse();
|
|
38
|
+
export class MyAgent extends ChatAgentHarness<Env> {
|
|
39
|
+
getModel(ctx: AgentToolContext) {
|
|
40
|
+
return openai("gpt-4o");
|
|
87
41
|
}
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
42
|
|
|
91
|
-
|
|
43
|
+
getFastModel() {
|
|
44
|
+
return openai("gpt-4o-mini");
|
|
45
|
+
}
|
|
92
46
|
|
|
93
|
-
|
|
47
|
+
getSystemPrompt(ctx: AgentToolContext) {
|
|
48
|
+
return "You are a helpful assistant.";
|
|
49
|
+
}
|
|
94
50
|
|
|
95
|
-
|
|
51
|
+
getSkills(ctx: AgentToolContext) {
|
|
52
|
+
return [searchSkill];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
96
56
|
|
|
97
|
-
|
|
57
|
+
For lower-level control (custom `onChatMessage` implementations), extend `ChatAgent` directly — see [ChatAgent](#chatagent).
|
|
98
58
|
|
|
99
|
-
|
|
59
|
+
### Wrangler Config
|
|
100
60
|
|
|
101
61
|
```jsonc
|
|
102
62
|
{
|
|
@@ -109,184 +69,234 @@ Your agent class is a Durable Object. Declare it in `wrangler.jsonc`:
|
|
|
109
69
|
|
|
110
70
|
Run `wrangler types` after to generate typed `Env` bindings.
|
|
111
71
|
|
|
72
|
+
### Client
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { useAIChatAgent, type AgentConnectionStatus } from "@economic/agents-react";
|
|
76
|
+
import { useState } from "react";
|
|
77
|
+
|
|
78
|
+
const [connectionStatus, setConnectionStatus] = useState<AgentConnectionStatus>("connecting");
|
|
79
|
+
|
|
80
|
+
const { agent, chat } = useAIChatAgent({
|
|
81
|
+
agent: "MyAgent",
|
|
82
|
+
host: "localhost:8787",
|
|
83
|
+
chatId: "user_123:session-1",
|
|
84
|
+
toolContext: {},
|
|
85
|
+
connectionParams: { userId: "…" },
|
|
86
|
+
onConnectionStatusChange: setConnectionStatus,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const { messages, sendMessage, status, stop } = chat;
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`chatId` is the Durable Object name — use `userId:uniqueChatId` (see [Providing userId](#providing-userid)).
|
|
93
|
+
|
|
94
|
+
> **Note:** React hooks are in a separate package. Install with `npm install @economic/agents-react`.
|
|
95
|
+
|
|
112
96
|
---
|
|
113
97
|
|
|
114
|
-
##
|
|
98
|
+
## Harnesses
|
|
99
|
+
|
|
100
|
+
The server-side API is built around Durable Object base classes — `Agent` for headless workflows and `ChatAgent`/`ChatAgentHarness` for conversational UIs — plus a skill system that lets the LLM load tools on demand.
|
|
101
|
+
|
|
102
|
+
### ChatAgentHarness
|
|
115
103
|
|
|
116
|
-
|
|
104
|
+
The recommended starting point for chat agents. Extends `ChatAgent` with an opinionated structure: implement abstract methods for model selection, system prompt, tools, and skills. The harness handles `onChatMessage` for you.
|
|
117
105
|
|
|
118
106
|
```typescript
|
|
119
|
-
import {
|
|
120
|
-
import {
|
|
107
|
+
import { openai } from "@ai-sdk/openai";
|
|
108
|
+
import { ChatAgentHarness, type AgentToolContext, type Skill } from "@economic/agents";
|
|
121
109
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const model = body.userTier === "pro" ? openai("gpt-4o") : openai("gpt-4o-mini");
|
|
110
|
+
interface RequestBody {
|
|
111
|
+
userTier: "free" | "pro";
|
|
112
|
+
}
|
|
126
113
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
114
|
+
export class MyAgent extends ChatAgentHarness<Env, RequestBody> {
|
|
115
|
+
getModel(ctx: AgentToolContext<RequestBody>) {
|
|
116
|
+
return ctx.userTier === "pro" ? openai("gpt-4o") : openai("gpt-4o-mini");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getFastModel() {
|
|
120
|
+
return openai("gpt-4o-mini");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getSystemPrompt(ctx: AgentToolContext<RequestBody>) {
|
|
124
|
+
return "You are a helpful assistant.";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
getTools(ctx: AgentToolContext<RequestBody>) {
|
|
128
|
+
return { myTool };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getSkills(ctx: AgentToolContext<RequestBody>) {
|
|
132
|
+
return [searchSkill, calculatorSkill];
|
|
136
133
|
}
|
|
137
134
|
}
|
|
138
135
|
```
|
|
139
136
|
|
|
140
|
-
|
|
137
|
+
- `getModel(ctx)` — returns the primary language model. Context includes request body for tier-based model selection.
|
|
138
|
+
- `getFastModel()` — returns a fast/cheap model for compaction and conversation summarization.
|
|
139
|
+
- `getSystemPrompt(ctx)` — returns the system prompt.
|
|
140
|
+
- `getTools(ctx)` — returns always-on tools (optional, defaults to `{}`).
|
|
141
|
+
- `getSkills(ctx)` — returns skills available for on-demand loading (optional, defaults to `[]`).
|
|
142
|
+
- `conversationRetentionDays` — defaults to 90. Set to `undefined` to disable auto-deletion.
|
|
141
143
|
|
|
142
|
-
|
|
144
|
+
#### Binding Name
|
|
143
145
|
|
|
144
|
-
|
|
145
|
-
- `activeSkills` pre-filled from `await this.getLoadedSkills()`
|
|
146
|
-
- `fastModel` injected from `this.fastModel`
|
|
147
|
-
- `log` injected into `experimental_context` alongside `options.body`
|
|
148
|
-
- Automatic error logging for non-clean finish reasons
|
|
149
|
-
- Compaction threshold defaulting: when `maxMessagesBeforeCompaction` is not in the config, defaults to `30`. Pass `maxMessagesBeforeCompaction: undefined` explicitly to disable compaction.
|
|
146
|
+
`ChatAgentHarness` automatically derives the Durable Object binding name from the class name. **The binding name in your `wrangler.jsonc` must exactly match your class name:**
|
|
150
147
|
|
|
151
|
-
|
|
148
|
+
```typescript
|
|
149
|
+
// Class name is "MyAgent"
|
|
150
|
+
export class MyAgent extends ChatAgentHarness<Env> {
|
|
151
|
+
/* ... */
|
|
152
|
+
}
|
|
153
|
+
```
|
|
152
154
|
|
|
153
|
-
|
|
155
|
+
```jsonc
|
|
156
|
+
// wrangler.jsonc — binding name must be "MyAgent" to match
|
|
157
|
+
{
|
|
158
|
+
"durable_objects": {
|
|
159
|
+
"bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }],
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
If the names don't match, the harness won't be able to resolve the binding and will throw at runtime. If you need a different binding name, override the `binding` getter:
|
|
165
|
+
|
|
166
|
+
````typescript
|
|
167
|
+
export class MyAgent extends ChatAgentHarness<Env> {
|
|
168
|
+
protected get binding() {
|
|
169
|
+
return this.env.CUSTOM_BINDING_NAME;
|
|
170
|
+
}
|
|
171
|
+
// ...
|
|
172
|
+
}
|
|
154
173
|
|
|
155
|
-
|
|
174
|
+
---
|
|
156
175
|
|
|
157
|
-
|
|
158
|
-
- Return a **`Response`** — that response is returned immediately; `onChatMessage` is not called.
|
|
176
|
+
### ChatAgent
|
|
159
177
|
|
|
160
|
-
|
|
178
|
+
Lower-level base class for chat agents. Use when you need full control over `onChatMessage` — custom streaming, multiple LLM calls per turn, or non-standard response formats.
|
|
161
179
|
|
|
162
180
|
```typescript
|
|
163
181
|
import { streamText } from "ai";
|
|
164
182
|
import { openai } from "@ai-sdk/openai";
|
|
165
|
-
import {
|
|
183
|
+
import { ChatAgent } from "@economic/agents";
|
|
166
184
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return new Response("Unauthorized", { status: 401 });
|
|
185
|
+
export class MyAgent extends ChatAgent<Env> {
|
|
186
|
+
protected get binding() {
|
|
187
|
+
return this.env.MyAgent;
|
|
171
188
|
}
|
|
172
|
-
};
|
|
173
189
|
|
|
174
|
-
|
|
175
|
-
|
|
190
|
+
protected getFastModel() {
|
|
191
|
+
return openai("gpt-4o-mini");
|
|
192
|
+
}
|
|
176
193
|
|
|
177
|
-
@guard(requireToken)
|
|
178
194
|
async onChatMessage(onFinish, options) {
|
|
179
195
|
const params = await this.buildLLMParams({
|
|
180
196
|
options,
|
|
181
197
|
onFinish,
|
|
182
198
|
model: openai("gpt-4o"),
|
|
183
199
|
system: "You are a helpful assistant.",
|
|
200
|
+
skills: [searchSkill],
|
|
201
|
+
tools: { alwaysOnTool },
|
|
184
202
|
});
|
|
185
203
|
return streamText(params).toUIMessageStreamResponse();
|
|
186
204
|
}
|
|
187
205
|
}
|
|
188
|
-
|
|
206
|
+
````
|
|
189
207
|
|
|
190
|
-
|
|
208
|
+
- `binding` — abstract getter returning the DO namespace binding. Required on every subclass.
|
|
209
|
+
- `getFastModel()` — abstract method returning the fast model for compaction and summarization.
|
|
210
|
+
- `maxMessagesBeforeCompaction` — class property to override the default threshold (15). Set to `undefined` to disable.
|
|
211
|
+
- `conversationRetentionDays` — class property to auto-delete inactive conversations after N days.
|
|
212
|
+
- `this.buildLLMParams()` — pre-fills `messages`, `activeSkills`, and injects `logEvent` into `experimental_context`.
|
|
213
|
+
- `getConversations()` / `deleteConversation(id)` — callable methods for listing/deleting a user's conversations.
|
|
191
214
|
|
|
192
|
-
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### Agent
|
|
218
|
+
|
|
219
|
+
Abstract Durable Object base for non-chat agents. Use for headless workflows driven from HTTP handlers, schedules, or alarms.
|
|
193
220
|
|
|
194
221
|
```typescript
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
222
|
+
import { generateText } from "ai";
|
|
223
|
+
import { openai } from "@ai-sdk/openai";
|
|
224
|
+
import { callable } from "agents";
|
|
225
|
+
import { Agent } from "@economic/agents";
|
|
226
|
+
|
|
227
|
+
export class MyAgent extends Agent<Env> {
|
|
228
|
+
@callable
|
|
229
|
+
async summarize(document: string) {
|
|
230
|
+
const params = await this.buildLLMParams({
|
|
231
|
+
model: openai("gpt-4o"),
|
|
232
|
+
messages: [{ role: "user", content: `Summarise: ${document}` }],
|
|
233
|
+
system: "You are a helpful assistant.",
|
|
234
|
+
skills: [searchSkill],
|
|
235
|
+
});
|
|
236
|
+
const result = await generateText(params);
|
|
237
|
+
return result.text;
|
|
238
|
+
}
|
|
198
239
|
}
|
|
199
240
|
```
|
|
200
241
|
|
|
201
|
-
|
|
242
|
+
- `this.buildLLMParams()` pre-fills `activeSkills` from DO SQLite and injects `logEvent` into `experimental_context`.
|
|
243
|
+
- `this.logEvent(message, payload?)` writes audit events to D1 when `AGENT_DB` is bound, silent no-op otherwise.
|
|
202
244
|
|
|
203
|
-
|
|
245
|
+
---
|
|
204
246
|
|
|
205
|
-
###
|
|
247
|
+
### Tool context
|
|
206
248
|
|
|
207
|
-
|
|
249
|
+
Pass data via the `body` option of `useAgentChat` (with `useAIChatAgent`, use `toolContext` — it is forwarded as `body`). It arrives as `experimental_context` in tool `execute` functions. Use `AgentToolContext<TBody>` to type it:
|
|
208
250
|
|
|
209
251
|
```typescript
|
|
210
|
-
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
- **User scope**: `userId` is taken from the segment before the first `:` in the Durable Object name (`userId:chatId`), the same format enforced in `onConnect`.
|
|
214
|
-
- **Data**: Reads from `AGENT_DB`, returning all `conversations` rows whose `durable_object_name` matches `userId:%`, ordered by `updated_at` descending (newest first). Each row includes `durable_object_name`, `title`, `summary`, `created_at`, and `updated_at`.
|
|
215
|
-
- **No D1**: If `AGENT_DB` is not bound, the method returns `undefined` and does not throw.
|
|
216
|
-
|
|
217
|
-
### `getLoadedSkills()`
|
|
252
|
+
import type { AgentToolContext } from "@economic/agents";
|
|
218
253
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
When `persistMessages` runs at the end of each turn, it:
|
|
254
|
+
interface AgentBody {
|
|
255
|
+
authorization: string;
|
|
256
|
+
userId: string;
|
|
257
|
+
}
|
|
224
258
|
|
|
225
|
-
|
|
226
|
-
2. Writes the updated skill name list to DO SQLite (no D1 needed).
|
|
227
|
-
3. Logs a turn summary via `log()`.
|
|
228
|
-
4. Strips all `activate_skill` and `list_capabilities` messages from history.
|
|
229
|
-
5. Delegates to the CF base `persistMessages` for message storage and WS broadcast.
|
|
259
|
+
type ToolContext = AgentToolContext<AgentBody>;
|
|
230
260
|
|
|
231
|
-
|
|
261
|
+
// Tool
|
|
262
|
+
execute: async (args, { experimental_context }) => {
|
|
263
|
+
const ctx = experimental_context as ToolContext;
|
|
264
|
+
await ctx.logEvent("tool called", { userId: ctx.userId });
|
|
265
|
+
return await fetchSomething(ctx.authorization);
|
|
266
|
+
};
|
|
267
|
+
```
|
|
232
268
|
|
|
233
|
-
|
|
269
|
+
`logEvent` is a no-op when `AGENT_DB` is not bound.
|
|
234
270
|
|
|
235
271
|
---
|
|
236
272
|
|
|
237
|
-
|
|
273
|
+
### Source URLs from Tools
|
|
238
274
|
|
|
239
|
-
|
|
275
|
+
Any tool can surface source URLs into the message stream by including a `sources` array in its return value. Detected automatically by `buildLLMParams` — no additional wiring needed.
|
|
240
276
|
|
|
241
277
|
```typescript
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
activeSkills: await this.getLoadedSkills(),
|
|
250
|
-
system: "You are a helpful assistant.",
|
|
251
|
-
skills: [searchSkill, codeSkill],
|
|
252
|
-
tools: { myAlwaysOnTool },
|
|
253
|
-
stopWhen: stepCountIs(20), // defaults to stepCountIs(20)
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
return streamText(params).toUIMessageStreamResponse();
|
|
257
|
-
// or: generateText(params);
|
|
278
|
+
execute: async ({ query }) => {
|
|
279
|
+
const data = await fetchResults(query);
|
|
280
|
+
return {
|
|
281
|
+
results: data.results,
|
|
282
|
+
sources: data.results.map((r) => ({ url: r.url, title: r.title })),
|
|
283
|
+
};
|
|
284
|
+
};
|
|
258
285
|
```
|
|
259
286
|
|
|
260
|
-
|
|
261
|
-
| ----------------------------- | ------------------------------------- | -------- | ------------------------------------------------------------------------------------------------- |
|
|
262
|
-
| `options` | `OnChatMessageOptions \| undefined` | Yes | CF options object. Extracts `abortSignal` and `experimental_context`. |
|
|
263
|
-
| `onFinish` | `StreamTextOnFinishCallback<ToolSet>` | Yes | Called when the stream completes. |
|
|
264
|
-
| `model` | `LanguageModel` | Yes | The language model to use. |
|
|
265
|
-
| `messages` | `UIMessage[]` | Yes | Conversation history. Converted to `ModelMessage[]` internally. |
|
|
266
|
-
| `activeSkills` | `string[]` | No | Names of skills loaded in previous turns. Pass `await this.getLoadedSkills()`. |
|
|
267
|
-
| `skills` | `Skill[]` | No | Skills available for on-demand loading. Wires up meta-tools automatically. |
|
|
268
|
-
| `system` | `string` | No | Base system prompt. |
|
|
269
|
-
| `tools` | `ToolSet` | No | Always-on tools, active every turn regardless of loaded skills. |
|
|
270
|
-
| `maxMessagesBeforeCompaction` | `number \| undefined` | No | Verbatim tail kept during compaction. Defaults to `30` when omitted. Pass `undefined` to disable. |
|
|
271
|
-
| `stopWhen` | `StopCondition` | No | Stop condition. Defaults to `stepCountIs(20)`. |
|
|
272
|
-
|
|
273
|
-
When `skills` are provided, `buildLLMParams`:
|
|
274
|
-
|
|
275
|
-
- Registers `activate_skill` and `list_capabilities` meta-tools.
|
|
276
|
-
- Sets initial `activeTools` (meta + always-on + loaded skill tools).
|
|
277
|
-
- Wires up `prepareStep` to update `activeTools` after each step.
|
|
278
|
-
- Composes `system` with guidance from loaded skills.
|
|
287
|
+
Each source entry: `{ url: string, title?: string }`.
|
|
279
288
|
|
|
280
289
|
---
|
|
281
290
|
|
|
282
|
-
|
|
291
|
+
### Skills
|
|
292
|
+
|
|
293
|
+
Named groups of tools loaded on demand by the LLM. The agent starts with only always-on tools active. When the LLM needs more, it calls `activate_skill`.
|
|
283
294
|
|
|
284
295
|
```typescript
|
|
285
296
|
import { tool } from "ai";
|
|
286
297
|
import { z } from "zod";
|
|
287
298
|
import type { Skill } from "@economic/agents";
|
|
288
299
|
|
|
289
|
-
// Skill with guidance — injected into the system prompt when the skill is loaded
|
|
290
300
|
export const calculatorSkill: Skill = {
|
|
291
301
|
name: "calculator",
|
|
292
302
|
description: "Mathematical calculation and expression evaluation",
|
|
@@ -295,7 +305,7 @@ export const calculatorSkill: Skill = {
|
|
|
295
305
|
"Always show the expression you are evaluating.",
|
|
296
306
|
tools: {
|
|
297
307
|
calculate: tool({
|
|
298
|
-
description: "Evaluate a mathematical expression
|
|
308
|
+
description: "Evaluate a mathematical expression",
|
|
299
309
|
inputSchema: z.object({
|
|
300
310
|
expression: z.string().describe('e.g. "2 + 2", "Math.sqrt(144)"'),
|
|
301
311
|
}),
|
|
@@ -306,343 +316,227 @@ export const calculatorSkill: Skill = {
|
|
|
306
316
|
}),
|
|
307
317
|
},
|
|
308
318
|
};
|
|
309
|
-
|
|
310
|
-
// Skill without guidance — tools are self-explanatory
|
|
311
|
-
export const datetimeSkill: Skill = {
|
|
312
|
-
name: "datetime",
|
|
313
|
-
description: "Current date and time information in any timezone",
|
|
314
|
-
tools: {
|
|
315
|
-
get_current_datetime: tool({
|
|
316
|
-
description: "Get the current date and time in an optional IANA timezone.",
|
|
317
|
-
inputSchema: z.object({
|
|
318
|
-
timezone: z.string().optional().describe('e.g. "Europe/Copenhagen"'),
|
|
319
|
-
}),
|
|
320
|
-
execute: async ({ timezone = "UTC" }) =>
|
|
321
|
-
new Date().toLocaleString("en-GB", {
|
|
322
|
-
timeZone: timezone,
|
|
323
|
-
dateStyle: "full",
|
|
324
|
-
timeStyle: "long",
|
|
325
|
-
}),
|
|
326
|
-
}),
|
|
327
|
-
},
|
|
328
|
-
};
|
|
329
319
|
```
|
|
330
320
|
|
|
331
|
-
|
|
321
|
+
When `skills` are provided to `buildLLMParams`, two meta-tools are registered automatically:
|
|
332
322
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
| `name` | `string` | Yes | Unique identifier used by `activate_skill` and for DO SQLite persistence. |
|
|
336
|
-
| `description` | `string` | Yes | One-line description shown in the `activate_skill` schema. |
|
|
337
|
-
| `guidance` | `string` | No | Instructions appended to the `system` prompt when this skill is loaded. |
|
|
338
|
-
| `tools` | `ToolSet` | Yes | Record of tool names to `tool()` definitions. Names must be globally unique. |
|
|
323
|
+
- **`activate_skill`** — loads skills by name, making their tools available for the rest of the conversation. Idempotent. State is persisted to DO SQLite.
|
|
324
|
+
- **`list_capabilities`** — returns active tools, loaded skills, and skills available to load.
|
|
339
325
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
## Surfacing source URLs from tools
|
|
343
|
-
|
|
344
|
-
Any tool can surface source URLs into the message stream by including a `sources` array in its return value. `buildLLMParams` automatically detects this and emits `source-url` stream parts that the playground's Sources block renders.
|
|
345
|
-
|
|
346
|
-
```typescript
|
|
347
|
-
execute: async ({ query }) => {
|
|
348
|
-
const data = await fetchResults(query);
|
|
349
|
-
return {
|
|
350
|
-
results: data.results, // LLM receives full content
|
|
351
|
-
sources: data.results.map(r => ({ url: r.url, title: r.title })), // rendered as source links
|
|
352
|
-
};
|
|
353
|
-
},
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
The `sources` array is picked up by a built-in `experimental_transform` inside `buildLLMParams` — no agent changes, no writer passed to the tool, no additional wiring. The LLM continues to receive the full result object including `sources`. The transform fires on every `tool-result` stream part and emits a `source` part for each entry.
|
|
357
|
-
|
|
358
|
-
Each source entry shape:
|
|
359
|
-
|
|
360
|
-
| Field | Type | Required | Description |
|
|
361
|
-
| ------- | -------- | -------- | ----------------------- |
|
|
362
|
-
| `url` | `string` | Yes | The URL to link to. |
|
|
363
|
-
| `title` | `string` | No | Display name in the UI. |
|
|
364
|
-
|
|
365
|
-
The playground's `UIMessageRenderer` collects all `source-url` parts from a message and displays them in a collapsible **Sources** block above the response text.
|
|
326
|
+
The `activate_skill` and `list_capabilities` meta-tools are stripped from message history before persistence.
|
|
366
327
|
|
|
367
328
|
---
|
|
368
329
|
|
|
369
|
-
|
|
330
|
+
### Audit Logging (D1)
|
|
370
331
|
|
|
371
|
-
|
|
332
|
+
All agent base classes write audit events to a D1 database when `AGENT_DB` is bound. If not bound, `logEvent` is a no-op.
|
|
372
333
|
|
|
373
|
-
|
|
374
|
-
2. `fastModel` generates a concise summary of the older window.
|
|
375
|
-
3. That summary + the verbatim tail is what gets sent to the LLM.
|
|
376
|
-
4. Full history in DO SQLite is unaffected — compaction is in-memory only.
|
|
334
|
+
#### D1 Setup
|
|
377
335
|
|
|
378
|
-
|
|
336
|
+
1. Create a D1 database in the Cloudflare dashboard.
|
|
337
|
+
2. Run the schema in the D1 console. For `Agent`, use [`schema/agent.sql`](schema/agent.sql). For `ChatAgent`/`ChatAgentHarness`, use [`schema/chat.sql`](schema/chat.sql) (includes the conversations table).
|
|
338
|
+
3. Bind it in `wrangler.jsonc`:
|
|
379
339
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
protected fastModel = openai("gpt-4o-mini");
|
|
385
|
-
|
|
386
|
-
async onChatMessage(onFinish, options) {
|
|
387
|
-
const params = await this.buildLLMParams({
|
|
388
|
-
options,
|
|
389
|
-
onFinish,
|
|
390
|
-
model: openai("gpt-4o"),
|
|
391
|
-
system: "...",
|
|
392
|
-
// No compaction config needed — runs automatically with default threshold
|
|
393
|
-
});
|
|
394
|
-
return streamText(params).toUIMessageStreamResponse();
|
|
395
|
-
}
|
|
396
|
-
}
|
|
340
|
+
```jsonc
|
|
341
|
+
"d1_databases": [
|
|
342
|
+
{ "binding": "AGENT_DB", "database_name": "agents", "database_id": "YOUR_DB_ID" }
|
|
343
|
+
]
|
|
397
344
|
```
|
|
398
345
|
|
|
399
|
-
|
|
346
|
+
4. For local dev, apply the schema to your local D1 (from your app’s directory), e.g. `wrangler d1 execute <database_name> --local --file=node_modules/@economic/agents/schema/chat.sql`. You can wrap that in a `db:setup` npm script if you prefer.
|
|
400
347
|
|
|
401
|
-
|
|
348
|
+
#### Providing userId
|
|
402
349
|
|
|
403
|
-
|
|
404
|
-
const params = await this.buildLLMParams({
|
|
405
|
-
options,
|
|
406
|
-
onFinish,
|
|
407
|
-
model: openai("gpt-4o"),
|
|
408
|
-
maxMessagesBeforeCompaction: 50, // keep last 50 messages verbatim
|
|
409
|
-
});
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
### Disabling compaction
|
|
413
|
-
|
|
414
|
-
Pass `maxMessagesBeforeCompaction: undefined` explicitly to disable compaction for that call, even when `fastModel` is set:
|
|
350
|
+
The client's `chatId` becomes the Durable Object name. Use `userId:uniqueChatId` so the first segment is your stable user id (audit and conversations key off `getUserId()`, i.e. the substring before the first `:`). If that segment is empty (e.g. `:chat-1`), the connection is rejected. Same idea as [Quick Start](#quick-start) (`chatId`).
|
|
415
351
|
|
|
416
352
|
```typescript
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
353
|
+
import { useAIChatAgent } from "@economic/agents-react";
|
|
354
|
+
|
|
355
|
+
const { agent, chat } = useAIChatAgent({
|
|
356
|
+
agent: "MyAgent",
|
|
357
|
+
host: "localhost:8787",
|
|
358
|
+
chatId: "148583_matt:conversation-1",
|
|
422
359
|
});
|
|
423
360
|
```
|
|
424
361
|
|
|
425
|
-
Compaction is always off when `fastModel` is `undefined` (the base class default).
|
|
426
|
-
|
|
427
362
|
---
|
|
428
363
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
Two meta tools are automatically registered when `skills` are provided. You do not need to define or wire them.
|
|
432
|
-
|
|
433
|
-
### `activate_skill`
|
|
364
|
+
### Chat Features
|
|
434
365
|
|
|
435
|
-
|
|
366
|
+
Compaction and the conversation list (below) require `getFastModel()` on your subclass.
|
|
436
367
|
|
|
437
|
-
|
|
438
|
-
- The skills available are exactly those passed as `skills` — filter by request body to control access.
|
|
439
|
-
- When skills are successfully loaded, the new state is embedded in the tool result. `persistMessages` extracts it and writes to DO SQLite.
|
|
440
|
-
- All `activate_skill` messages are stripped from history before persistence — state is restored from DO SQLite, not from message history.
|
|
441
|
-
|
|
442
|
-
### `list_capabilities`
|
|
443
|
-
|
|
444
|
-
Returns a summary of active tools, loaded skills, and skills available to load. Always stripped from history before persistence.
|
|
445
|
-
|
|
446
|
-
---
|
|
368
|
+
#### Compaction
|
|
447
369
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
Pass arbitrary data via the `body` option of `useAgentChat`. It arrives as `experimental_context` in tool `execute` functions.
|
|
451
|
-
|
|
452
|
-
When using `this.buildLLMParams()`, the context is automatically composed: your body fields plus a `log` function for writing audit events. Use `AgentContext<TBody>` to type it:
|
|
370
|
+
Compaction summarises older messages before each turn. Full history in DO SQLite is unaffected — compaction is in-memory only. The default threshold is **15** recent messages (`maxMessagesBeforeCompaction` on the class).
|
|
453
371
|
|
|
454
372
|
```typescript
|
|
455
|
-
|
|
456
|
-
|
|
373
|
+
export class MyAgent extends ChatAgentHarness<Env> {
|
|
374
|
+
getModel() {
|
|
375
|
+
return openai("gpt-4o");
|
|
376
|
+
}
|
|
457
377
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}
|
|
378
|
+
getFastModel() {
|
|
379
|
+
return openai("gpt-4o-mini");
|
|
380
|
+
}
|
|
462
381
|
|
|
463
|
-
|
|
464
|
-
|
|
382
|
+
getSystemPrompt() {
|
|
383
|
+
return "You are a helpful assistant.";
|
|
384
|
+
}
|
|
465
385
|
|
|
466
|
-
|
|
467
|
-
//
|
|
468
|
-
useAgentChat({ body: { authorization: token, userId: "u_123" } });
|
|
386
|
+
// Optional: keep more messages verbatim before summarising (default 15).
|
|
387
|
+
// protected maxMessagesBeforeCompaction = 50;
|
|
469
388
|
|
|
470
|
-
//
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
await ctx.log("tool called", { userId: ctx.userId });
|
|
474
|
-
const data = await fetchSomething(ctx.authorization);
|
|
475
|
-
return data;
|
|
476
|
-
};
|
|
389
|
+
// Optional: disable compaction (still uses fastModel for conversation title/summary).
|
|
390
|
+
// protected maxMessagesBeforeCompaction = undefined;
|
|
391
|
+
}
|
|
477
392
|
```
|
|
478
393
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
---
|
|
482
|
-
|
|
483
|
-
## Audit logging — D1 setup
|
|
394
|
+
#### Conversations (D1)
|
|
484
395
|
|
|
485
|
-
`
|
|
396
|
+
`ChatAgent` and `ChatAgentHarness` maintain a `conversations` table in `AGENT_DB`. One row per Durable Object instance, upserted automatically after every turn. Requires [`schema/chat.sql`](schema/chat.sql).
|
|
486
397
|
|
|
487
|
-
|
|
398
|
+
**Automatic title and summary** — On the first turn, title and summary are generated and inserted. On subsequent turns, only `updated_at` is refreshed. Title and summary are regenerated periodically as the conversation grows.
|
|
488
399
|
|
|
489
|
-
|
|
400
|
+
**Retention** — Set `conversationRetentionDays` to auto-delete inactive conversations:
|
|
490
401
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
402
|
+
```typescript
|
|
403
|
+
export class MyAgent extends ChatAgentHarness<Env> {
|
|
404
|
+
getModel() {
|
|
405
|
+
return openai("gpt-4o");
|
|
406
|
+
}
|
|
407
|
+
getFastModel() {
|
|
408
|
+
return openai("gpt-4o-mini");
|
|
409
|
+
}
|
|
410
|
+
getSystemPrompt() {
|
|
411
|
+
return "You are a helpful assistant.";
|
|
412
|
+
}
|
|
494
413
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
durable_object_id TEXT NOT NULL,
|
|
499
|
-
user_id TEXT NOT NULL,
|
|
500
|
-
message TEXT NOT NULL,
|
|
501
|
-
payload TEXT,
|
|
502
|
-
created_at TEXT NOT NULL
|
|
503
|
-
);
|
|
504
|
-
CREATE INDEX IF NOT EXISTS audit_events_user ON audit_events(user_id);
|
|
505
|
-
CREATE INDEX IF NOT EXISTS audit_events_do ON audit_events(durable_object_id);
|
|
506
|
-
CREATE INDEX IF NOT EXISTS audit_events_ts ON audit_events(created_at);
|
|
507
|
-
|
|
508
|
-
CREATE TABLE IF NOT EXISTS conversations (
|
|
509
|
-
durable_object_id TEXT PRIMARY KEY,
|
|
510
|
-
user_id TEXT NOT NULL,
|
|
511
|
-
title TEXT,
|
|
512
|
-
summary TEXT,
|
|
513
|
-
created_at TEXT NOT NULL,
|
|
514
|
-
updated_at TEXT NOT NULL
|
|
515
|
-
);
|
|
516
|
-
CREATE INDEX IF NOT EXISTS conversations_user ON conversations(user_id);
|
|
517
|
-
CREATE INDEX IF NOT EXISTS conversations_ts ON conversations(updated_at);
|
|
414
|
+
// ChatAgentHarness defaults to 90 days. Override or set to undefined to disable.
|
|
415
|
+
protected conversationRetentionDays = 30;
|
|
416
|
+
}
|
|
518
417
|
```
|
|
519
418
|
|
|
520
|
-
|
|
419
|
+
When the retention period expires, the D1 row is deleted, WebSocket connections are closed, and the DO's SQLite storage is wiped.
|
|
521
420
|
|
|
522
|
-
|
|
421
|
+
**Querying** — From a connected client:
|
|
523
422
|
|
|
524
|
-
```
|
|
525
|
-
"
|
|
526
|
-
{ "binding": "AGENT_DB", "database_name": "agents", "database_id": "YOUR_DB_ID" }
|
|
527
|
-
]
|
|
423
|
+
```typescript
|
|
424
|
+
const conversations = await agent.call("getConversations");
|
|
528
425
|
```
|
|
529
426
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
### 4. Seed local development
|
|
533
|
-
|
|
534
|
-
```bash
|
|
535
|
-
npm run db:setup
|
|
536
|
-
```
|
|
427
|
+
---
|
|
537
428
|
|
|
538
|
-
|
|
429
|
+
## Hono
|
|
539
430
|
|
|
540
|
-
|
|
431
|
+
Hono tooling is exported on `@economic/agents/hono`.
|
|
541
432
|
|
|
542
|
-
###
|
|
433
|
+
### JWT Auth Middleware
|
|
543
434
|
|
|
544
|
-
|
|
435
|
+
Bearer JWT verification middleware for Hono, imported from `@economic/agents/hono`. Verifies tokens via JWKS derived from the token's `iss` claim.
|
|
545
436
|
|
|
546
437
|
```typescript
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
438
|
+
import { jwtAuth } from "@economic/agents/hono";
|
|
439
|
+
|
|
440
|
+
app.use(
|
|
441
|
+
"/api/*",
|
|
442
|
+
jwtAuth({
|
|
443
|
+
allowedIssuers: ["https://login.example.com"],
|
|
444
|
+
audience: "my-api",
|
|
445
|
+
requiredScopes: ["read", "write"],
|
|
446
|
+
}),
|
|
447
|
+
);
|
|
554
448
|
```
|
|
555
449
|
|
|
556
|
-
If the client omits `userId`, the audit insert is skipped and a `console.error` is emitted. This will be visible in Wrangler's output during local development and in Workers Logs in production.
|
|
557
|
-
|
|
558
450
|
---
|
|
559
451
|
|
|
560
|
-
##
|
|
452
|
+
## API Reference
|
|
561
453
|
|
|
562
|
-
|
|
454
|
+
### `@economic/agents`
|
|
563
455
|
|
|
564
|
-
|
|
456
|
+
| Export | Description |
|
|
457
|
+
| ---------------------- | -------------------------------------------------------------------------- |
|
|
458
|
+
| `Agent` | Abstract DO base for non-chat agents with audit logging and buildLLMParams |
|
|
459
|
+
| `ChatAgent` | Abstract chat DO with compaction, conversations, and custom onChatMessage |
|
|
460
|
+
| `ChatAgentHarness` | Opinionated chat harness with getModel/getSystemPrompt/getTools/getSkills |
|
|
461
|
+
| `buildLLMParams` | Standalone function to build streamText/generateText params |
|
|
462
|
+
| `Skill` | Type: named group of tools with optional guidance |
|
|
463
|
+
| `AgentToolContext` | Type: request body merged with `logEvent` for tool context |
|
|
464
|
+
| `OnChatMessageOptions` | Type: options passed to `onChatMessage` |
|
|
465
|
+
| `BuildLLMParamsConfig` | Type: config for standalone `buildLLMParams` |
|
|
565
466
|
|
|
566
|
-
###
|
|
467
|
+
### `@economic/agents-react`
|
|
567
468
|
|
|
568
|
-
|
|
569
|
-
- **Subsequent turns**: the upsert only refreshes `updated_at`. `created_at`, `title`, and `summary` are preserved by the upsert path.
|
|
570
|
-
- Every `SUMMARY_CONTEXT_MESSAGES` messages, `AIChatAgent` separately re-generates `title` and `summary` and writes them back without changing `created_at`.
|
|
469
|
+
React hooks are in a separate package. See [`@economic/agents-react`](../react/README.md) for full documentation.
|
|
571
470
|
|
|
572
|
-
|
|
471
|
+
| Export | Description |
|
|
472
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
473
|
+
| `useAIChatAgent` | React hook wrapping `useAgent` + `useAgentChat` |
|
|
474
|
+
| `UseAIChatAgentOptions` | Type: options for `useAIChatAgent` (`agent`, `host`, `chatId`, optional `basePath`, `toolContext`, `connectionParams`, …) |
|
|
475
|
+
| `AgentConnectionStatus` | Type: `"connecting" \| "connected" \| "disconnected" \| "unauthorized"` |
|
|
573
476
|
|
|
574
|
-
|
|
477
|
+
### `@economic/agents/hono`
|
|
575
478
|
|
|
576
|
-
|
|
479
|
+
| Export | Description |
|
|
480
|
+
| --------------- | ------------------------------------------- |
|
|
481
|
+
| `jwtAuth` | Hono middleware for Bearer JWT verification |
|
|
482
|
+
| `JwtAuthConfig` | Type: config for `jwtAuth` |
|
|
577
483
|
|
|
578
|
-
|
|
484
|
+
### CLI
|
|
579
485
|
|
|
580
|
-
|
|
486
|
+
| Command | Description |
|
|
487
|
+
| -------------------------------------------- | ---------------------------------------- |
|
|
488
|
+
| `npx @economic/agents generate skill <name>` | Scaffold a new skill with tools |
|
|
489
|
+
| `npx @economic/agents generate tool <name>` | Scaffold a new tool (global or in skill) |
|
|
581
490
|
|
|
582
|
-
|
|
491
|
+
---
|
|
583
492
|
|
|
584
|
-
|
|
585
|
-
export class MyAgent extends AIChatAgent<Env> {
|
|
586
|
-
protected fastModel = openai("gpt-4o-mini");
|
|
587
|
-
protected conversationRetentionDays = 90;
|
|
588
|
-
}
|
|
589
|
-
```
|
|
493
|
+
## CLI
|
|
590
494
|
|
|
591
|
-
|
|
495
|
+
The package includes a CLI for scaffolding skills and tools.
|
|
592
496
|
|
|
593
|
-
|
|
594
|
-
2. Closes any active WebSocket connections for that conversation.
|
|
595
|
-
3. Wipes the Durable Object's SQLite storage with `deleteAll()`.
|
|
497
|
+
### Generate a Skill
|
|
596
498
|
|
|
597
|
-
|
|
499
|
+
```bash
|
|
500
|
+
npx @economic/agents generate skill weather
|
|
501
|
+
```
|
|
598
502
|
|
|
599
|
-
|
|
503
|
+
This will:
|
|
600
504
|
|
|
601
|
-
|
|
505
|
+
1. Prompt for a skill description
|
|
506
|
+
2. Ask for initial tool names (comma-separated)
|
|
507
|
+
3. Prompt for each tool's description and whether it needs `AgentToolContext`
|
|
508
|
+
4. Create the skill file at `src/skills/weather/weather.ts`
|
|
509
|
+
5. Create tool files at `src/skills/weather/tools/*.ts`
|
|
510
|
+
6. Auto-register the skill in your agent's `getSkills()` method
|
|
602
511
|
|
|
603
|
-
|
|
512
|
+
### Generate a Tool
|
|
604
513
|
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
FROM conversations
|
|
608
|
-
WHERE durable_object_name LIKE '148583_matt:%'
|
|
609
|
-
ORDER BY updated_at DESC;
|
|
514
|
+
```bash
|
|
515
|
+
npx @economic/agents generate tool geocode
|
|
610
516
|
```
|
|
611
517
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
---
|
|
615
|
-
|
|
616
|
-
## API reference
|
|
617
|
-
|
|
618
|
-
### Classes
|
|
518
|
+
This will:
|
|
619
519
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
520
|
+
1. Prompt for a tool description
|
|
521
|
+
2. Ask where to create it (global `src/tools/` or within an existing skill)
|
|
522
|
+
3. Ask whether it needs `AgentToolContext`
|
|
523
|
+
4. Create the tool file
|
|
524
|
+
5. Auto-register the tool in your agent's `getTools()` or the skill's `tools` object
|
|
623
525
|
|
|
624
|
-
###
|
|
526
|
+
### Auto-registration
|
|
625
527
|
|
|
626
|
-
|
|
627
|
-
| ---------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
628
|
-
| `guard` | `(guardFn: GuardFn)` | Method decorator: runs `guardFn` with `options.body`; a returned `Response` short-circuits the method. |
|
|
629
|
-
| `buildLLMParams` | `async (config) => Promise<LLMParams>` | Builds the full parameter object for `streamText` or `generateText`. |
|
|
528
|
+
The CLI automatically detects agent files by scanning `src/` for classes extending `ChatAgentHarness`, `ChatAgent`, or `Agent` from `@economic/agents`. If one agent is found, it's used automatically. If multiple are found, you'll be prompted to select one.
|
|
630
529
|
|
|
631
|
-
|
|
530
|
+
For `ChatAgentHarness`, the CLI modifies `getSkills()` or `getTools()` methods. For `ChatAgent` or `Agent`, it modifies `buildLLMParams()` calls.
|
|
632
531
|
|
|
633
|
-
|
|
634
|
-
| ---------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
635
|
-
| `GuardFn` | `(body) => Response \| void \| Promise<...>`. Receives chat request `body`; return `Response` to block the turn. |
|
|
636
|
-
| `Skill` | A named group of tools with optional guidance. |
|
|
637
|
-
| `AgentContext<TBody>` | Request body type merged with `log`. Use as the type of `experimental_context`. |
|
|
638
|
-
| `BuildLLMParamsConfig` | Config type for the standalone `buildLLMParams` function. |
|
|
532
|
+
If the CLI detects complex patterns (spread operators, function calls, variables), it will print manual registration instructions instead.
|
|
639
533
|
|
|
640
534
|
---
|
|
641
535
|
|
|
642
536
|
## Development
|
|
643
537
|
|
|
644
538
|
```bash
|
|
645
|
-
npm install
|
|
646
|
-
npm test
|
|
647
|
-
npm pack
|
|
539
|
+
npm install
|
|
540
|
+
npm test
|
|
541
|
+
npm pack
|
|
648
542
|
```
|